From 8ff3c44e07442794c9612a127c4dd7502d46d7cf Mon Sep 17 00:00:00 2001 From: GRACENOBLE Date: Mon, 15 Jun 2026 15:06:43 +0300 Subject: [PATCH 1/2] feat(observability): add Sentry error tracking across backend, web, and mobile - Backend: SentryMiddleware (sentry-go v0.46.2 + sentry-go/gin), registered first in Gin middleware chain, opt-in via SENTRY_DSN env var - Web: @sentry/nextjs v10.57.0 with client/server/edge config files, next.config wrapped with withSentryConfig, opt-in via NEXT_PUBLIC_SENTRY_DSN - Mobile: sentry-android v8.14.0 initialised in MainActivity.onCreate, guarded by BuildConfig.SENTRY_DSN (empty by default) - Docs: created observability.md for all three layers; fixed stale bootstrap, routing, middleware, environment docs; added auth.md --- backend/.env.example | 4 +- backend/docs/_index.md | 2 + backend/docs/auth.md | 110 ++ backend/docs/bootstrap.md | 29 +- backend/docs/environment.md | 3 + backend/docs/middleware.md | 37 + backend/docs/observability.md | 72 + backend/docs/routing.md | 28 +- backend/go.mod | 2 + backend/go.sum | 4 + backend/internal/bootstrap/bootstrap.go | 2 + backend/internal/bootstrap/bootstrap_test.go | 17 + backend/internal/server/server.go | 2 +- backend/internal/transport/handlers/routes.go | 5 +- .../internal/transport/middleware/sentry.go | 18 + .../transport/middleware/sentry_test.go | 34 + mobile/app/build.gradle.kts | 3 + .../java/com/company/template/MainActivity.kt | 14 + .../com/company/template/SentryInitTest.kt | 30 + mobile/docs/_index.md | 1 + mobile/docs/observability.md | 100 ++ mobile/gradle/libs.versions.toml | 2 + web/.env.example | 2 + web/.gitignore | 2 +- web/__tests__/sentry.test.ts | 20 + web/docs/_index.md | 1 + web/docs/observability.md | 83 + web/next.config.ts | 16 +- web/package.json | 1 + web/pnpm-lock.yaml | 1445 ++++++++++++++++- web/sentry.client.config.ts | 7 + web/sentry.edge.config.ts | 7 + web/sentry.server.config.ts | 7 + 33 files changed, 1998 insertions(+), 112 deletions(-) create mode 100644 backend/docs/auth.md create mode 100644 backend/docs/observability.md create mode 100644 backend/internal/transport/middleware/sentry.go create mode 100644 backend/internal/transport/middleware/sentry_test.go create mode 100644 mobile/app/src/test/java/com/company/template/SentryInitTest.kt create mode 100644 mobile/docs/observability.md create mode 100644 web/.env.example create mode 100644 web/__tests__/sentry.test.ts create mode 100644 web/docs/observability.md create mode 100644 web/sentry.client.config.ts create mode 100644 web/sentry.edge.config.ts create mode 100644 web/sentry.server.config.ts diff --git a/backend/.env.example b/backend/.env.example index 28610c8..b1be62c 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -11,4 +11,6 @@ REDIS_URL= RATE_LIMIT_RPS=100 # requests/sec per IP (omit or 0 = disabled) RATE_LIMIT_BURST=500 # burst capacity (defaults to RPS×5) FIREBASE_PROJECT_ID=# Firebase project ID — e.g. my-app-12345 (omit to disable auth) -FIREBASE_SERVICE_ACCOUNT_JSON=# Service account key as a single-line JSON string. Get from: Firebase Console → Project Settings → Service Accounts → Generate new private key \ No newline at end of file +FIREBASE_SERVICE_ACCOUNT_JSON=# Service account key as a single-line JSON string. Get from: Firebase Console → Project Settings → Service Accounts → Generate new private key +# Sentry error tracking DSN (optional; leave empty to disable) +SENTRY_DSN= \ No newline at end of file diff --git a/backend/docs/_index.md b/backend/docs/_index.md index b38d5bc..6356f74 100644 --- a/backend/docs/_index.md +++ b/backend/docs/_index.md @@ -13,3 +13,5 @@ The `docs` agent reads this index first to locate the right file before diving i | Error handling conventions | [error-handling.md](error-handling.md) | `internal/infrastructure/database/postgres/health_repository.go`, `internal/transport/handlers/health_handler.go`, `cmd/api/main.go` | | Environment variables | [environment.md](environment.md) | `.env`, `internal/bootstrap/bootstrap.go`, `internal/infrastructure/database/postgres/db.go` | | Middleware (logger, rate limiter) | [middleware.md](middleware.md) | `internal/transport/middleware/logger.go`, `internal/transport/middleware/ratelimit.go`, `internal/transport/handlers/routes.go` | +| Firebase Auth (token verification, middleware, MeHandler) | [auth.md](auth.md) | `internal/usecase/auth_usecase.go`, `internal/transport/middleware/auth.go`, `internal/transport/handlers/auth_handler.go`, `pkg/firebase/admin.go`, `internal/bootstrap/bootstrap.go` | +| Observability (Sentry error tracking) | [observability.md](observability.md) | `internal/transport/middleware/sentry.go`, `internal/bootstrap/bootstrap.go`, `internal/transport/handlers/routes.go` | diff --git a/backend/docs/auth.md b/backend/docs/auth.md new file mode 100644 index 0000000..59fef79 --- /dev/null +++ b/backend/docs/auth.md @@ -0,0 +1,110 @@ +--- +topic: auth +last_verified: 2026-06-15 +sources: + - internal/usecase/auth_usecase.go + - internal/transport/middleware/auth.go + - internal/transport/handlers/auth_handler.go + - pkg/firebase/admin.go + - internal/bootstrap/bootstrap.go +--- + +# Firebase Auth + +## Domain type + +`FirebaseToken` is defined in `internal/usecase/auth_usecase.go` and holds the claims extracted from a verified Firebase ID token: + +```go +type FirebaseToken struct { + UID string `json:"uid"` + Email string `json:"email"` + Name string `json:"name"` + PhotoURL string `json:"photoUrl"` + Claims map[string]any `json:"claims"` +} +``` + +`UID`, `Email`, `Name`, and `PhotoURL` are promoted from the raw Firebase JWT claims (`uid`, `email`, `name`, `picture`). `Claims` contains the full unmodified payload. + +## Usecase interfaces + +Both interfaces live in `internal/usecase/auth_usecase.go`. + +```go +// FirebaseTokenVerifier can verify a raw Firebase ID token string. +type FirebaseTokenVerifier interface { + VerifyIDToken(ctx context.Context, idToken string) (*FirebaseToken, error) +} + +// FirebaseAdminClient extends FirebaseTokenVerifier with user-management operations. +type FirebaseAdminClient interface { + FirebaseTokenVerifier + GetUserByEmail(ctx context.Context, email string) (string, error) + UpdateUserPassword(ctx context.Context, uid, newPassword string) error +} +``` + +`FirebaseTokenVerifier` is the narrow interface used by the `FirebaseAuth` middleware. `FirebaseAdminClient` is the broader interface stored on `bootstrap.App` and wired into the server. + +## pkg/firebase/admin.go + +`NewAuthClient` initialises the Firebase Admin SDK and returns a `usecase.FirebaseAdminClient`: + +```go +func NewAuthClient(ctx context.Context, projectID, credentialsJSON string) (usecase.FirebaseAdminClient, error) +``` + +- When `credentialsJSON` is non-empty the SDK is initialised with `option.WithCredentialsJSON` (service account key). +- When `credentialsJSON` is empty the SDK falls back to Application Default Credentials (ADC) — used on GCP. + +The returned value is `*authClientAdapter`, a private type that wraps `*auth.Client` from the Firebase Admin SDK. This adapter satisfies both `FirebaseTokenVerifier` and `FirebaseAdminClient` without leaking the SDK type into the application layer. + +## FirebaseAuth middleware + +Defined in `internal/transport/middleware/auth.go`. + +```go +const FirebaseClaimsKey = "firebase_claims" + +func FirebaseAuth(verifier usecase.FirebaseTokenVerifier) gin.HandlerFunc +``` + +For every request the middleware: +1. Reads the `Authorization` header; aborts with `401` if the header is missing or does not start with `Bearer `. +2. Calls `verifier.VerifyIDToken(ctx, idToken)`. +3. On success: stores `*usecase.FirebaseToken` in the Gin context under `FirebaseClaimsKey` and calls `c.Next()`. +4. On error: aborts with `401` and `{"error": "invalid or expired token"}`. + +Retrieve claims inside a handler: +```go +val, _ := c.Get(middleware.FirebaseClaimsKey) +token, ok := val.(*usecase.FirebaseToken) +``` + +## MeHandler (GET /api/v1/me) + +Defined in `internal/transport/handlers/auth_handler.go`. + +```go +func (h *Handler) MeHandler(c *gin.Context) +``` + +- Reads `*usecase.FirebaseToken` from the Gin context (`FirebaseClaimsKey`). +- Returns `200 OK` with the token struct serialised as JSON. +- Returns `401 Unauthorized` with `{"error": "unauthorized"}` if the context value is missing or of the wrong type (should not happen when `FirebaseAuth` is applied to the group). + +The handler is registered on the `/api/v1` group in `RegisterRoutes`: +```go +api := r.Group("/api/v1") +if verifier != nil { + api.Use(middleware.FirebaseAuth(verifier)) +} +api.GET("/me", h.MeHandler) +``` + +## Disabling auth in development + +When `FIREBASE_PROJECT_ID` is not set `bootstrap.Run` skips Firebase initialisation and `app.Firebase` is `nil`. `server.NewServer` passes `app.Firebase` directly to `RegisterRoutes` as the `verifier` argument. When `verifier` is `nil` the `if verifier != nil` guard in `RegisterRoutes` skips `api.Use(middleware.FirebaseAuth(...))`, so `/api/v1/me` is reachable without a token. + +To enable auth locally set both `FIREBASE_PROJECT_ID` and `FIREBASE_SERVICE_ACCOUNT_JSON` in `backend/.env`. diff --git a/backend/docs/bootstrap.md b/backend/docs/bootstrap.md index 64f9fa3..6d59bdb 100644 --- a/backend/docs/bootstrap.md +++ b/backend/docs/bootstrap.md @@ -1,6 +1,6 @@ --- topic: bootstrap -last_verified: 2026-06-14 +last_verified: 2026-06-15 sources: - internal/bootstrap/bootstrap.go - internal/server/server.go @@ -15,22 +15,29 @@ sources: ## App struct ```go type App struct { - DB *sql.DB - Config Config - Log *slog.Logger + DB *sql.DB + Cache usecase.CacheService // nil when REDIS_URL is not set + Firebase usecase.FirebaseAdminClient // nil when FIREBASE_PROJECT_ID is not set + Config Config + Log *slog.Logger } ``` -`App` is constructed once by `Run` and passed to `server.NewServer`. Nothing re-initialises dependencies after this point. +`App` is constructed once by `Run` and passed to `server.NewServer`. Nothing re-initialises dependencies after this point. Optional fields (`Cache`, `Firebase`) are nil when their corresponding env vars are absent. ## Config struct ```go type Config struct { - Port int - AppEnv string - DB postgres.DBConfig + Port int + Env string + DB postgres.DBConfig + RedisURL string + RateLimitRPS float64 + RateLimitBurst int + FirebaseProjectID string + FirebaseServiceAccountJSON string } ``` -`loadConfig()` reads all values from environment variables. `PORT` defaults to `8080`; `BLUEPRINT_DB_SCHEMA` defaults to `public`; `BLUEPRINT_DB_SSLMODE` defaults to `disable`. +`loadConfig()` reads all values from environment variables. `PORT` defaults to `8080`; `BLUEPRINT_DB_SCHEMA` defaults to `public`; `BLUEPRINT_DB_SSLMODE` defaults to `disable`. `RateLimitBurst` is derived as `int(RPS)*5` when omitted and RPS is set. Optional fields (`RedisURL`, `FirebaseProjectID`, `FirebaseServiceAccountJSON`) default to empty string — their respective services are skipped when empty. ## Run sequence `bootstrap.Run(ctx)` executes these steps in order: @@ -39,7 +46,9 @@ type Config struct { 2. Validate required fields via `validateConfig()` — fast, no I/O 3. Open `*sql.DB` via `postgres.NewPostgresDB(cfg.DB)` 4. Probe Postgres with `probeWithRetry` under a 60-second total timeout -5. Return `*App` on success; return a non-nil error on any failure +5. Init Redis via `redis.New(cfg.RedisURL)` and probe it — skipped when `REDIS_URL` is empty +6. Init Firebase Admin SDK via `firebase.NewAuthClient(ctx, projectID, credentialsJSON)` — skipped when `FIREBASE_PROJECT_ID` is empty +7. Return `*App` on success; return a non-nil error on any failure ```go ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) diff --git a/backend/docs/environment.md b/backend/docs/environment.md index bf9354d..0cd36a7 100644 --- a/backend/docs/environment.md +++ b/backend/docs/environment.md @@ -5,6 +5,7 @@ sources: - .env - internal/bootstrap/bootstrap.go - internal/infrastructure/database/postgres/db.go + - pkg/firebase/admin.go --- # Environment Variables @@ -31,6 +32,8 @@ This runs on package init before any env var is read — no explicit `godotenv.L | `BLUEPRINT_DB_SSLMODE` | `bootstrap.go` | `disable` | Postgres SSL mode (`disable`, `require`, `verify-full`) | | `RATE_LIMIT_RPS` | `bootstrap.go` | `0` (disabled) | Max requests per second per IP. Set to `0` or omit to disable rate limiting. | | `RATE_LIMIT_BURST` | `bootstrap.go` | `int(RPS) * 5`, min 1 | Token-bucket burst capacity. Derived as `int(RPS)*5` when omitted; clamped to 1 so fractional RPS values never block all traffic. | +| `FIREBASE_PROJECT_ID` | `bootstrap.go`, `pkg/firebase/admin.go` | — | Firebase project ID. When omitted the Firebase Admin client is not initialised and `FirebaseAuth` middleware is skipped (auth disabled). | +| `FIREBASE_SERVICE_ACCOUNT_JSON` | `bootstrap.go`, `pkg/firebase/admin.go` | — | Raw JSON content of a Firebase service account key file. When omitted the SDK falls back to Application Default Credentials (ADC) — appropriate for GCP-hosted deployments. Only relevant when `FIREBASE_PROJECT_ID` is set. | Variables marked **required** are validated by `bootstrap.validateConfig` at startup — the process exits before attempting a DB connection if any are missing. diff --git a/backend/docs/middleware.md b/backend/docs/middleware.md index 9bbc903..d914634 100644 --- a/backend/docs/middleware.md +++ b/backend/docs/middleware.md @@ -4,6 +4,7 @@ last_verified: 2026-06-15 sources: - internal/transport/middleware/logger.go - internal/transport/middleware/ratelimit.go + - internal/transport/middleware/auth.go - internal/transport/handlers/routes.go --- @@ -20,6 +21,18 @@ r.Use(gin.Recovery(), middleware.Logger()) r.Use(middleware.RateLimit(rps, burst)) // 3. CORS r.Use(cors.New(...)) + +// Global routes (no auth): +r.GET("/", h.HelloWorldHandler) +r.GET("/health", h.HealthHandler) +r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) + +// Protected group — FirebaseAuth applied when verifier != nil: +api := r.Group("/api/v1") +if verifier != nil { + api.Use(middleware.FirebaseAuth(verifier)) +} +api.GET("/me", h.MeHandler) ``` ## Logger @@ -36,6 +49,30 @@ In debug mode (`ENV` not set to `staging`/`production`) Gin's built-in colorful - **429 Too Many Requests** returned when the bucket is empty; body: `{"error": "rate limit exceeded"}`. - Configured via env vars `RATE_LIMIT_RPS` and `RATE_LIMIT_BURST` (see [environment](environment.md)). +## FirebaseAuth + +`FirebaseAuth(verifier usecase.FirebaseTokenVerifier) gin.HandlerFunc` validates a Firebase ID token on every request to the routes it guards. + +```go +const FirebaseClaimsKey = "firebase_claims" + +func FirebaseAuth(verifier usecase.FirebaseTokenVerifier) gin.HandlerFunc +``` + +Behaviour: +- Expects `Authorization: Bearer ` header. +- Calls `verifier.VerifyIDToken(ctx, idToken)` — the concrete implementation is `pkg/firebase.authClientAdapter`. +- On success: stores `*usecase.FirebaseToken` in the Gin context under `FirebaseClaimsKey` and calls `c.Next()`. +- On failure (missing header, malformed header, or token verification error): aborts with `401 Unauthorized` and a JSON body `{"error": "..."}`. + +Retrieve verified claims inside a handler: +```go +val, _ := c.Get(middleware.FirebaseClaimsKey) +token, ok := val.(*usecase.FirebaseToken) +``` + +Pass `nil` as the `verifier` to `RegisterRoutes` to skip Firebase auth entirely (development without credentials). + ## Adding new middleware 1. Create `internal/transport/middleware/.go` with a function returning `gin.HandlerFunc`. diff --git a/backend/docs/observability.md b/backend/docs/observability.md new file mode 100644 index 0000000..d073bb9 --- /dev/null +++ b/backend/docs/observability.md @@ -0,0 +1,72 @@ +--- +topic: observability +last_verified: 2026-06-15 +sources: + - internal/transport/middleware/sentry.go + - internal/bootstrap/bootstrap.go + - internal/transport/handlers/routes.go +--- + +# Observability + +## Sentry SDK + +| Package | Version | +|---|---| +| `github.com/getsentry/sentry-go` | v0.46.2 | +| `github.com/getsentry/sentry-go/gin` | v0.46.2 | + +## SentryMiddleware + +`internal/transport/middleware/sentry.go` exports a single function: + +```go +func SentryMiddleware(dsn string) gin.HandlerFunc +``` + +Behavior: +- When `dsn` is empty, returns `func(c *gin.Context) { c.Next() }` — a no-op that adds no overhead. +- When `dsn` is non-empty, calls `sentry.Init(sentry.ClientOptions{Dsn: dsn})` and returns `sentrygin.New(sentrygin.Options{Repanic: true})`. +- `Repanic: true` means the middleware re-panics after capturing, allowing Gin's `Recovery()` to handle the panic response normally. + +## Middleware registration order + +`RegisterRoutes` in `internal/transport/handlers/routes.go` registers middleware in this order: + +1. `SentryMiddleware(sentryDSN)` — first, so it wraps all subsequent handlers +2. `gin.Recovery()` + `gin.Logger()` (debug) or `gin.Recovery()` + `middleware.Logger()` (non-debug) +3. `middleware.RateLimit(rps, burst)` +4. CORS + +`RegisterRoutes` signature: + +```go +func (h *Handler) RegisterRoutes(rps float64, burst int, verifier usecase.FirebaseTokenVerifier, sentryDSN string) http.Handler +``` + +The `sentryDSN` parameter is forwarded directly from `Config.SentryDSN`. + +## Environment variable + +| Variable | Required | Default | +|---|---|---| +| `SENTRY_DSN` | No | `""` (Sentry disabled) | + +Loaded in `loadConfig()` in `internal/bootstrap/bootstrap.go`: + +```go +SentryDSN: os.Getenv("SENTRY_DSN"), +``` + +Stored on `Config.SentryDSN`. Not validated — an empty value disables Sentry without error. + +## Supplying the DSN + +**Local development** — add to `backend/.env`: +``` +SENTRY_DSN=https://@o.ingest.sentry.io/ +``` + +**Production** — set `SENTRY_DSN` as an environment variable in your deployment platform. The app reads it at startup via `godotenv/autoload` (dev) or the process environment (production). + +Leave `SENTRY_DSN` empty (or omit it) to run without Sentry. The app starts and serves normally in both cases. diff --git a/backend/docs/routing.md b/backend/docs/routing.md index 9c7eb92..199b754 100644 --- a/backend/docs/routing.md +++ b/backend/docs/routing.md @@ -6,6 +6,7 @@ sources: - internal/transport/handlers/routes.go - internal/transport/handlers/hello_handler.go - internal/transport/handlers/health_handler.go + - internal/transport/handlers/auth_handler.go - internal/transport/middleware/logger.go - internal/server/server.go - cmd/api/main.go @@ -28,16 +29,16 @@ The `Handler` struct holds use case interfaces — not `*sql.DB` directly. Add n ## Wiring (server.go) `internal/server/server.go` contains `NewServer(app *bootstrap.App) *http.Server` — wiring only, no logic. -It receives the already-validated `*bootstrap.App` (which holds `*sql.DB` and `Config`), constructs the repository, use case, and handler in order, then returns a configured `*http.Server`. It does not read env vars or return an error. +It receives the already-validated `*bootstrap.App` (which holds `*sql.DB`, `Cache`, `Firebase`, and `Config`), constructs the repository, use case, and handler in order, then returns a configured `*http.Server`. It does not read env vars or return an error. ```go healthRepo := postgres.NewHealthRepository(app.DB) healthUC := usecase.NewHealthUseCase(healthRepo) -h := handler.NewHandler(healthUC) +h := handlers.NewHandler(healthUC) return &http.Server{ Addr: fmt.Sprintf(":%d", app.Config.Port), - Handler: h.RegisterRoutes(app.Config.RateLimitRPS, app.Config.RateLimitBurst), + Handler: h.RegisterRoutes(app.Config.RateLimitRPS, app.Config.RateLimitBurst, app.Firebase), IdleTimeout: time.Minute, ReadTimeout: 10 * time.Second, WriteTimeout: 30 * time.Second, @@ -47,9 +48,10 @@ return &http.Server{ ## Route registration All routes registered in `RegisterRoutes()` on `*Handler`, which returns `http.Handler`. `rps` and `burst` come from `bootstrap.Config` (env vars `RATE_LIMIT_RPS` / `RATE_LIMIT_BURST`); pass `rps=0` to disable. +`verifier` is a `usecase.FirebaseTokenVerifier`; pass `nil` to skip Firebase auth (development only — see [auth](auth.md)). ```go -func (h *Handler) RegisterRoutes(rps float64, burst int) http.Handler { +func (h *Handler) RegisterRoutes(rps float64, burst int, verifier usecase.FirebaseTokenVerifier) http.Handler { r := gin.New() // Gin's colorful logger locally; structured slog logger in staging/production. @@ -63,9 +65,16 @@ func (h *Handler) RegisterRoutes(rps float64, burst int) http.Handler { r.Use(cors.New(cors.Config{ ... })) - r.GET("/path", h.myHandler) + r.GET("/", h.HelloWorldHandler) + r.GET("/health", h.HealthHandler) r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) + api := r.Group("/api/v1") + if verifier != nil { + api.Use(middleware.FirebaseAuth(verifier)) + } + api.GET("/me", h.MeHandler) + return r } ``` @@ -91,10 +100,11 @@ Allowed methods: GET, POST, PUT, DELETE, OPTIONS, PATCH. `AllowCredentials: true` — cookies and auth headers pass through. ## Existing routes -| Method | Path | Handler | File | -|---|---|---|---| -| GET | `/` | `HelloWorldHandler` — returns `{"message": "Hello World"}` | `hello_handler.go` | -| GET | `/health` | `healthHandler` — returns `HealthStats`; 503 when DB is down | `health_handler.go` | +| Method | Path | Auth | Handler | File | +|---|---|---|---|---| +| GET | `/` | none | `HelloWorldHandler` — returns `{"message": "Hello World"}` | `hello_handler.go` | +| GET | `/health` | none | `HealthHandler` — returns `HealthStats`; 503 when DB is down | `health_handler.go` | +| GET | `/api/v1/me` | FirebaseAuth | `MeHandler` — returns verified `FirebaseToken` claims | `auth_handler.go` | ## Graceful shutdown Wired in `cmd/api/main.go` via `signal.NotifyContext` for SIGINT/SIGTERM. diff --git a/backend/go.mod b/backend/go.mod index 912a1b1..3522f03 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -79,6 +79,8 @@ require ( github.com/envoyproxy/protoc-gen-validate v1.3.3 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/gabriel-vasile/mimetype v1.4.13 // indirect + github.com/getsentry/sentry-go v0.46.2 // indirect + github.com/getsentry/sentry-go/gin v0.46.2 // indirect github.com/gin-contrib/sse v1.1.1 // indirect github.com/go-jose/go-jose/v4 v4.1.4 // indirect github.com/go-logr/logr v1.4.3 // indirect diff --git a/backend/go.sum b/backend/go.sum index adc5935..87494dd 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -141,6 +141,10 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/getsentry/sentry-go v0.46.2 h1:1jhYwrKGa3sIpo/y5iDNXS5wDoT7I1KNzMHrnK6ojns= +github.com/getsentry/sentry-go v0.46.2/go.mod h1:evVbw2qotNUdYG8KxXbAdjOQWWvWIwKxpjdZZIvcIPw= +github.com/getsentry/sentry-go/gin v0.46.2 h1:+YCJvpEKuKVgna6ZAHMIlJAiBk1D+Nh+K4awpUtJNG0= +github.com/getsentry/sentry-go/gin v0.46.2/go.mod h1:fPPyezNO87IwDi7Q5sCtA6tGHoHD7DutdYbN/l77m2k= github.com/gin-contrib/cors v1.7.7 h1:Oh9joP463x7Mw72vhvJ61YQm8ODh9b04YR7vsOErD0Q= github.com/gin-contrib/cors v1.7.7/go.mod h1:K5tW0RkzJtWSiOdikXloy8VEZlgdVNpHNw8FpjUPNrE= github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= diff --git a/backend/internal/bootstrap/bootstrap.go b/backend/internal/bootstrap/bootstrap.go index b4d05b9..5e55f99 100644 --- a/backend/internal/bootstrap/bootstrap.go +++ b/backend/internal/bootstrap/bootstrap.go @@ -50,6 +50,7 @@ type Config struct { RateLimitBurst int FirebaseProjectID string FirebaseServiceAccountJSON string + SentryDSN string } // ConfigError is returned when required configuration is absent or invalid. @@ -156,6 +157,7 @@ func loadConfig() Config { RateLimitBurst: burst, FirebaseProjectID: os.Getenv("FIREBASE_PROJECT_ID"), FirebaseServiceAccountJSON: os.Getenv("FIREBASE_SERVICE_ACCOUNT_JSON"), + SentryDSN: os.Getenv("SENTRY_DSN"), DB: postgres.DBConfig{ Host: os.Getenv("BLUEPRINT_DB_HOST"), Port: os.Getenv("BLUEPRINT_DB_PORT"), diff --git a/backend/internal/bootstrap/bootstrap_test.go b/backend/internal/bootstrap/bootstrap_test.go index 043e8ae..efd97d4 100644 --- a/backend/internal/bootstrap/bootstrap_test.go +++ b/backend/internal/bootstrap/bootstrap_test.go @@ -69,6 +69,23 @@ func TestLoadConfig_ExplicitSSLMode(t *testing.T) { } } +func TestLoadConfig_SentryDSN_Set(t *testing.T) { + const dsn = "https://key@o0.ingest.sentry.io/0" + t.Setenv("SENTRY_DSN", dsn) + cfg := loadConfig() + if cfg.SentryDSN != dsn { + t.Errorf("expected SentryDSN %q, got %q", dsn, cfg.SentryDSN) + } +} + +func TestLoadConfig_SentryDSN_Unset(t *testing.T) { + t.Setenv("SENTRY_DSN", "") + cfg := loadConfig() + if cfg.SentryDSN != "" { + t.Errorf("expected empty SentryDSN, got %q", cfg.SentryDSN) + } +} + // --------------------------------------------------------------------------- // validateConfig // --------------------------------------------------------------------------- diff --git a/backend/internal/server/server.go b/backend/internal/server/server.go index e151622..40774ba 100644 --- a/backend/internal/server/server.go +++ b/backend/internal/server/server.go @@ -32,7 +32,7 @@ func NewServer(app *bootstrap.App) *http.Server { return &http.Server{ Addr: fmt.Sprintf(":%d", app.Config.Port), - Handler: h.RegisterRoutes(app.Config.RateLimitRPS, app.Config.RateLimitBurst, app.Firebase), + Handler: h.RegisterRoutes(app.Config.RateLimitRPS, app.Config.RateLimitBurst, app.Firebase, app.Config.SentryDSN), IdleTimeout: time.Minute, ReadTimeout: 10 * time.Second, WriteTimeout: 30 * time.Second, diff --git a/backend/internal/transport/handlers/routes.go b/backend/internal/transport/handlers/routes.go index d6e1acb..9d9ef8e 100644 --- a/backend/internal/transport/handlers/routes.go +++ b/backend/internal/transport/handlers/routes.go @@ -16,9 +16,12 @@ import ( // RegisterRoutes creates the Gin engine, applies middleware, and registers all routes. // rps and burst configure IP-based rate limiting; pass rps<=0 to disable. // verifier enables Firebase token auth on protected routes; pass nil to skip auth (dev only). -func (h *Handler) RegisterRoutes(rps float64, burst int, verifier usecase.FirebaseTokenVerifier) http.Handler { +// sentryDSN enables Sentry error tracking; pass empty string to disable. +func (h *Handler) RegisterRoutes(rps float64, burst int, verifier usecase.FirebaseTokenVerifier, sentryDSN string) http.Handler { r := gin.New() + r.Use(middleware.SentryMiddleware(sentryDSN)) + // Use Gin's colorful logger locally; structured slog logger in staging/production. if gin.Mode() == gin.DebugMode { r.Use(gin.Recovery(), gin.Logger()) diff --git a/backend/internal/transport/middleware/sentry.go b/backend/internal/transport/middleware/sentry.go new file mode 100644 index 0000000..2dfacbf --- /dev/null +++ b/backend/internal/transport/middleware/sentry.go @@ -0,0 +1,18 @@ +package middleware + +import ( + sentry "github.com/getsentry/sentry-go" + sentrygin "github.com/getsentry/sentry-go/gin" + + "github.com/gin-gonic/gin" +) + +// SentryMiddleware reports panics and errors to Sentry. +// When dsn is empty it returns a no-op handler so the app works without Sentry configured. +func SentryMiddleware(dsn string) gin.HandlerFunc { + if dsn == "" { + return func(c *gin.Context) { c.Next() } + } + _ = sentry.Init(sentry.ClientOptions{Dsn: dsn}) + return sentrygin.New(sentrygin.Options{Repanic: true}) +} diff --git a/backend/internal/transport/middleware/sentry_test.go b/backend/internal/transport/middleware/sentry_test.go new file mode 100644 index 0000000..2a6e8ee --- /dev/null +++ b/backend/internal/transport/middleware/sentry_test.go @@ -0,0 +1,34 @@ +package middleware_test + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + + "backend/internal/transport/middleware" +) + +func TestSentryMiddleware_NoDSN_IsNoOp(t *testing.T) { + gin.SetMode(gin.TestMode) + r := gin.New() + r.Use(middleware.SentryMiddleware("")) + r.GET("/", func(c *gin.Context) { c.Status(http.StatusOK) }) + + w := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/", nil) + r.ServeHTTP(w, req) + + if w.Code != http.StatusOK { + t.Errorf("expected 200, got %d", w.Code) + } +} + +func TestSentryMiddleware_WithDSN_ReturnsHandler(t *testing.T) { + // Use a syntactically valid but non-connectable DSN + h := middleware.SentryMiddleware("https://key@o0.ingest.sentry.io/0") + if h == nil { + t.Fatal("expected non-nil handler") + } +} diff --git a/mobile/app/build.gradle.kts b/mobile/app/build.gradle.kts index 2c154eb..41d72a4 100644 --- a/mobile/app/build.gradle.kts +++ b/mobile/app/build.gradle.kts @@ -19,6 +19,7 @@ android { versionName = "1.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + buildConfigField("String", "SENTRY_DSN", "\"\"") } buildTypes { @@ -36,6 +37,7 @@ android { } buildFeatures { compose = true + buildConfig = true } } @@ -48,6 +50,7 @@ dependencies { implementation(libs.androidx.compose.ui.tooling.preview) implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.sentry.android) testImplementation(libs.junit) androidTestImplementation(platform(libs.androidx.compose.bom)) androidTestImplementation(libs.androidx.compose.ui.test.junit4) diff --git a/mobile/app/src/main/java/com/company/template/MainActivity.kt b/mobile/app/src/main/java/com/company/template/MainActivity.kt index ae731cc..43c72f3 100644 --- a/mobile/app/src/main/java/com/company/template/MainActivity.kt +++ b/mobile/app/src/main/java/com/company/template/MainActivity.kt @@ -12,10 +12,24 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import com.company.template.ui.theme.TemplateTheme +import io.sentry.android.core.SentryAndroid + +/** + * Returns true when [dsn] is non-blank — i.e. Sentry should be initialised. + * Extracted as a top-level function so it can be unit-tested on the JVM + * without touching any Android framework APIs. + */ +fun shouldInitSentry(dsn: String): Boolean = dsn.isNotBlank() class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + if (shouldInitSentry(BuildConfig.SENTRY_DSN)) { + SentryAndroid.init(this) { options -> + options.dsn = BuildConfig.SENTRY_DSN + options.tracesSampleRate = 1.0 + } + } enableEdgeToEdge() setContent { TemplateTheme { diff --git a/mobile/app/src/test/java/com/company/template/SentryInitTest.kt b/mobile/app/src/test/java/com/company/template/SentryInitTest.kt new file mode 100644 index 0000000..a95c23f --- /dev/null +++ b/mobile/app/src/test/java/com/company/template/SentryInitTest.kt @@ -0,0 +1,30 @@ +package com.company.template + +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test + +/** + * Unit tests for Sentry initialisation logic. + * + * The real SentryAndroid.init() is an Android framework call and cannot run on + * the JVM. The production code therefore guards the call behind + * [shouldInitSentry], which IS pure Kotlin and testable here. + */ +class SentryInitTest { + + @Test + fun `shouldInitSentry returns false when DSN is empty`() { + assertFalse(shouldInitSentry("")) + } + + @Test + fun `shouldInitSentry returns false when DSN is blank`() { + assertFalse(shouldInitSentry(" ")) + } + + @Test + fun `shouldInitSentry returns true when DSN is non-empty`() { + assertTrue(shouldInitSentry("https://example@sentry.io/123")) + } +} diff --git a/mobile/docs/_index.md b/mobile/docs/_index.md index 5a6f23d..9312112 100644 --- a/mobile/docs/_index.md +++ b/mobile/docs/_index.md @@ -12,3 +12,4 @@ Topic-based documentation for the Android app. Each file is kept in sync with th | Jetpack Compose UI conventions | `compose-conventions.md` | `app/src/main/java/com/company/template/MainActivity.kt`, `ui/theme/` | | Activity and Compose architecture | `architecture.md` | `app/src/main/java/com/company/template/MainActivity.kt`, `app/build.gradle.kts` | | Testing patterns | `testing.md` | `app/src/test/java/com/company/template/GreetingFormatTest.kt`, `app/src/androidTest/java/com/company/template/GreetingTest.kt`, `app/build.gradle.kts` | +| Observability (Sentry error tracking) | `observability.md` | `gradle/libs.versions.toml`, `app/build.gradle.kts`, `app/src/main/java/com/company/template/MainActivity.kt` | diff --git a/mobile/docs/observability.md b/mobile/docs/observability.md new file mode 100644 index 0000000..e0a2858 --- /dev/null +++ b/mobile/docs/observability.md @@ -0,0 +1,100 @@ +--- +topic: observability +last_verified: 2026-06-15 +sources: + - gradle/libs.versions.toml + - app/build.gradle.kts + - app/src/main/java/com/company/template/MainActivity.kt +--- + +# Observability + +## Sentry SDK + +| Dependency | Version | +|---|---| +| `io.sentry:sentry-android` | 8.14.0 | + +Declared in `gradle/libs.versions.toml`: + +```toml +[versions] +sentry = "8.14.0" + +[libraries] +sentry-android = { group = "io.sentry", name = "sentry-android", version.ref = "sentry" } +``` + +Added to `app/build.gradle.kts`: + +```kotlin +implementation(libs.sentry.android) +``` + +## BuildConfig.SENTRY_DSN + +`app/build.gradle.kts` sets `buildConfig = true` and defines the field in `defaultConfig`: + +```kotlin +buildFeatures { + buildConfig = true +} + +defaultConfig { + buildConfigField("String", "SENTRY_DSN", "\"\"") +} +``` + +The default value is an empty string, meaning Sentry is off for all build variants unless overridden. + +**To supply a real DSN — per build type:** + +```kotlin +buildTypes { + release { + buildConfigField("String", "SENTRY_DSN", "\"https://@o.ingest.sentry.io/\"") + } +} +``` + +**To supply a real DSN — via command line:** + +```bash +./gradlew assembleRelease -PSENTRY_DSN="https://@o.ingest.sentry.io/" +``` + +Then reference the project property in `build.gradle.kts`: + +```kotlin +buildConfigField("String", "SENTRY_DSN", "\"${project.findProperty("SENTRY_DSN") ?: ""}\"") +``` + +## shouldInitSentry helper + +`MainActivity.kt` defines a top-level function: + +```kotlin +fun shouldInitSentry(dsn: String): Boolean = dsn.isNotBlank() +``` + +It is a pure function with no Android framework dependencies, making it directly unit-testable on the JVM without Robolectric or an emulator. + +## Initialization in MainActivity.onCreate + +`SentryAndroid.init` is called before `setContent`, guarded by `shouldInitSentry`: + +```kotlin +override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + if (shouldInitSentry(BuildConfig.SENTRY_DSN)) { + SentryAndroid.init(this) { options -> + options.dsn = BuildConfig.SENTRY_DSN + options.tracesSampleRate = 1.0 + } + } + enableEdgeToEdge() + setContent { ... } +} +``` + +When `BuildConfig.SENTRY_DSN` is blank (the default), the `if` block is skipped entirely and no Sentry code runs. diff --git a/mobile/gradle/libs.versions.toml b/mobile/gradle/libs.versions.toml index 394ece7..84bb6ef 100644 --- a/mobile/gradle/libs.versions.toml +++ b/mobile/gradle/libs.versions.toml @@ -8,6 +8,7 @@ lifecycleRuntimeKtx = "2.6.1" activityCompose = "1.8.0" kotlin = "2.2.10" composeBom = "2026.02.01" +sentry = "8.14.0" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -24,6 +25,7 @@ androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "u androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" } +sentry-android = { group = "io.sentry", name = "sentry-android", version.ref = "sentry" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } diff --git a/web/.env.example b/web/.env.example new file mode 100644 index 0000000..8aae850 --- /dev/null +++ b/web/.env.example @@ -0,0 +1,2 @@ +# Sentry DSN for error tracking (optional; leave empty to disable) +NEXT_PUBLIC_SENTRY_DSN= diff --git a/web/.gitignore b/web/.gitignore index 5ef6a52..f66f9ff 100644 --- a/web/.gitignore +++ b/web/.gitignore @@ -32,7 +32,7 @@ yarn-error.log* # env files (can opt-in for committing if needed) .env* - +!.env.example # vercel .vercel diff --git a/web/__tests__/sentry.test.ts b/web/__tests__/sentry.test.ts new file mode 100644 index 0000000..7f54917 --- /dev/null +++ b/web/__tests__/sentry.test.ts @@ -0,0 +1,20 @@ +import { vi, describe, it, expect } from "vitest"; + +// Mock @sentry/nextjs so tests don't try to connect +vi.mock("@sentry/nextjs", () => ({ + init: vi.fn(), +})); + +describe("Sentry config", () => { + it("client config imports without error", async () => { + await expect(import("../sentry.client.config")).resolves.toBeDefined(); + }); + + it("server config imports without error", async () => { + await expect(import("../sentry.server.config")).resolves.toBeDefined(); + }); + + it("edge config imports without error", async () => { + await expect(import("../sentry.edge.config")).resolves.toBeDefined(); + }); +}); diff --git a/web/docs/_index.md b/web/docs/_index.md index 3f38c07..5f65cdf 100644 --- a/web/docs/_index.md +++ b/web/docs/_index.md @@ -10,3 +10,4 @@ The `docs` agent reads this index first to locate the right file. | Styling with Tailwind CSS v4 | [styling.md](styling.md) | `app/globals.css`, `postcss.config.mjs` | | Component conventions | [components.md](components.md) | `app/` (all component files) | | Testing patterns | [testing.md](testing.md) | `vitest.config.ts`, `vitest.setup.ts`, `__tests__/page.test.tsx` | +| Observability (Sentry error tracking) | [observability.md](observability.md) | `sentry.client.config.ts`, `sentry.server.config.ts`, `sentry.edge.config.ts`, `next.config.ts`, `.env.local.example` | diff --git a/web/docs/observability.md b/web/docs/observability.md new file mode 100644 index 0000000..0e43fb4 --- /dev/null +++ b/web/docs/observability.md @@ -0,0 +1,83 @@ +--- +topic: observability +last_verified: 2026-06-15 +sources: + - sentry.client.config.ts + - sentry.server.config.ts + - sentry.edge.config.ts + - next.config.ts + - .env.local.example +--- + +# Observability + +## Sentry SDK + +| Package | Version | +|---|---| +| `@sentry/nextjs` | 10.57.0 | + +## Runtime config files + +Three files initialize Sentry, each targeting a different Next.js runtime. All three call `Sentry.init` with identical options: + +```ts +Sentry.init({ + dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, + tracesSampleRate: 1.0, + debug: false, +}); +``` + +| File | Runtime | +|---|---| +| `sentry.client.config.ts` | Browser (client components, client-side navigation) | +| `sentry.server.config.ts` | Node.js (Server Components, Route Handlers, Server Actions) | +| `sentry.edge.config.ts` | Edge runtime (Middleware, Edge Route Handlers) | + +Next.js picks up each file automatically based on the execution context — no manual imports are needed. + +## Environment variable + +| Variable | Required | Default | +|---|---|---| +| `NEXT_PUBLIC_SENTRY_DSN` | No | `undefined` (Sentry disabled) | + +The `NEXT_PUBLIC_` prefix makes the value available in the browser bundle. When the variable is absent or empty, `Sentry.init` is a no-op. + +**Local development** — add to `web/.env.local`: +``` +NEXT_PUBLIC_SENTRY_DSN=https://@o.ingest.sentry.io/ +``` + +`web/.env.local.example` documents this with an empty placeholder. + +**Production** — set `NEXT_PUBLIC_SENTRY_DSN` in your deployment platform's environment configuration. + +## next.config.ts — withSentryConfig + +`next.config.ts` wraps the Next.js config with `withSentryConfig`: + +```ts +export default withSentryConfig(nextConfig, { + silent: true, + org: "", // fill in your Sentry org slug + project: "", // fill in your Sentry project slug + widenClientFileUpload: true, + sourcemaps: { + deleteSourcemapsAfterUpload: true, + }, + webpack: { + treeshake: { + removeDebugLogging: true, + }, + }, +}); +``` + +Option notes: +- `silent: true` — suppresses Sentry CLI output during builds. +- `org` and `project` — must be set to non-empty strings to enable source map uploads to Sentry. Left as `""` in the committed file; fill these in for production builds. +- `widenClientFileUpload: true` — uploads source maps from a broader set of client bundle chunks. +- `sourcemaps.deleteSourcemapsAfterUpload: true` — removes source map files from the build output after uploading, so they are not served publicly. +- `webpack.treeshake.removeDebugLogging: true` — strips Sentry debug log calls from the production bundle. diff --git a/web/next.config.ts b/web/next.config.ts index e9ffa30..75ca89c 100644 --- a/web/next.config.ts +++ b/web/next.config.ts @@ -1,7 +1,21 @@ import type { NextConfig } from "next"; +import { withSentryConfig } from "@sentry/nextjs"; const nextConfig: NextConfig = { /* config options here */ }; -export default nextConfig; +export default withSentryConfig(nextConfig, { + silent: true, + org: "", // fill in your Sentry org slug + project: "", // fill in your Sentry project slug + widenClientFileUpload: true, + sourcemaps: { + deleteSourcemapsAfterUpload: true, + }, + webpack: { + treeshake: { + removeDebugLogging: true, + }, + }, +}); diff --git a/web/package.json b/web/package.json index 7a00251..0603bd8 100644 --- a/web/package.json +++ b/web/package.json @@ -12,6 +12,7 @@ "test:ui": "vitest --ui" }, "dependencies": { + "@sentry/nextjs": "^10.57.0", "next": "16.2.9", "react": "19.2.4", "react-dom": "19.2.4" diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 8abfa27..f34851f 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -8,9 +8,12 @@ importers: .: dependencies: + '@sentry/nextjs': + specifier: ^10.57.0 + version: 10.57.0(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1))(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(webpack@5.107.2) next: specifier: 16.2.9 - version: 16.2.9(@babel/core@7.29.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: specifier: 19.2.4 version: 19.2.4 @@ -38,7 +41,7 @@ importers: version: 19.2.3(@types/react@19.2.17) '@vitejs/plugin-react': specifier: ^6.0.2 - version: 6.0.2(vite@8.0.16(@types/node@20.19.43)(jiti@2.7.0)) + version: 6.0.2(vite@8.0.16(@types/node@20.19.43)(jiti@2.7.0)(terser@5.48.0)) eslint: specifier: ^9 version: 9.39.4(jiti@2.7.0) @@ -56,7 +59,7 @@ importers: version: 5.9.3 vitest: specifier: ^4.1.8 - version: 4.1.8(@types/node@20.19.43)(jsdom@29.1.1)(vite@8.0.16(@types/node@20.19.43)(jiti@2.7.0)) + version: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@20.19.43)(jsdom@29.1.1)(vite@8.0.16(@types/node@20.19.43)(jiti@2.7.0)(terser@5.48.0)) packages: @@ -419,6 +422,9 @@ packages: resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} + '@jridgewell/source-map@0.3.11': + resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} + '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} @@ -501,6 +507,42 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} + '@opentelemetry/api-logs@0.214.0': + resolution: {integrity: sha512-40lSJeqYO8Uz2Yj7u94/SJWE/wONa7rmMKjI1ZcIjgf3MHNHv1OZUCrCETGuaRF62d5pQD1wKIW+L4lmSMTzZA==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/api@1.9.1': + resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/core@2.8.0': + resolution: {integrity: sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/instrumentation@0.214.0': + resolution: {integrity: sha512-MHqEX5Dk59cqVah5LiARMACku7jXSVk9iVDWOea4x3cr7VfdByeDCURK6o1lntT1JS/Tsovw01UJrBhN3/uC5w==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/resources@2.8.0': + resolution: {integrity: sha512-qmXQ27ilDbUK/vGMqwL8D4/rhn76C+sherM4wTbjlfknR8Nvfc/hCxjRJPhkzZzUsPiNg16SA31NxMabwttRjg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-trace-base@2.8.0': + resolution: {integrity: sha512-mhU4jp+vW0mGbFRd+GeXHvmfA4aDqWjBjLC3pE5XMpLs0IE2ryYb019Ts2AQrOq67gaTF25D91+fgvEHDZEnuQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/semantic-conventions@1.41.1': + resolution: {integrity: sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==} + engines: {node: '>=14'} + '@oxc-project/types@0.133.0': resolution: {integrity: sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==} @@ -596,9 +638,299 @@ packages: '@rolldown/pluginutils@1.0.1': resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + '@rollup/plugin-commonjs@28.0.1': + resolution: {integrity: sha512-+tNWdlWKbpB3WgBN7ijjYkq9X5uhjmcvyjEght4NmH5fAU++zfQzAJ6wumLS+dNcvwEZhKx2Z+skY8m7v0wGSA==} + engines: {node: '>=16.0.0 || 14 >= 14.17'} + peerDependencies: + rollup: ^2.68.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/pluginutils@5.4.0': + resolution: {integrity: sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/rollup-android-arm-eabi@4.62.0': + resolution: {integrity: sha512-IPIQ55ythEHkfEd9jMEi32OQ7SxURsGA43JI22lj01OLZNt2NUbJX8YUHxkVWyQ6daHPNn0truF5nSj3DQp6YQ==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.62.0': + resolution: {integrity: sha512-M6s9cr10MibETyo8JsOkq+Lo1+lU6hcvb1MApnUql5qte/5hMEgzlN8/ReIKNfRV8rrqX50W1BX9zoUhC192RA==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.62.0': + resolution: {integrity: sha512-BqCoMoIbn0keKys+dEAdBa70EtOwV1bEsQCUgU9FdiZmmMge/Zk7LlkYGqbrdHR+Frnt0E1FOanly+rlwvvQzw==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.62.0': + resolution: {integrity: sha512-SIMzST3VFNXDAbeIWDWiFCNM5qncUBDWaEV7NfE7oZbDt2mgfW4MvbKdbYiGOLoM32gbTv608UMd0XktEYSD7w==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.62.0': + resolution: {integrity: sha512-ezjfSQMP7ArdUsbBwbQIfwAlhE84I2iVnzQNCFSveqV42q+BmKlzVpf7mxv5EchLcoWU4y6/heFzVg1F+hodUQ==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.62.0': + resolution: {integrity: sha512-9+qTWGW9AZRhnUgwtTwzNwcPlL87ngkeN0LA+q1bADvmY9aNvWaF2TFW8BZgnQPYxpDI7+rMVLivcd4V737TAQ==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.62.0': + resolution: {integrity: sha512-T1dMEQhXA/jkJ/jyMIw9IovK8bSUq7A8kLIlvZTb/6YIVsp2zLavr4F3oyllHWo7eIVJRyE5n3tUjQJEbE1IuQ==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.62.0': + resolution: {integrity: sha512-2as0LgT7qQpyceQq6VUJYnumUMUrgGQCWIiDIN9DE0/tglsk6o66uCB4f3djRawAltvfCNLyZZrsqbPA6inCsA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.62.0': + resolution: {integrity: sha512-bVURMg+6eNN9C/yc0aVjooZcwTTtYF4YW3xta5pP0//r3o1V8gXEHXWCndj47w/HhwsFroZrFhR+6uQP5T0n0g==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.62.0': + resolution: {integrity: sha512-Ful8pM/2yYI83PViWdFdpZhdI8HJ5qsXANe5atypbHDf+KIBBDsZsbyy8hbXnULVvW9NsTh5DHwbcBftyLTfiw==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.62.0': + resolution: {integrity: sha512-9Gp/DgrkzfUBmNPVTyPTvay+4xEP7M/clXpj3efXBcm6uTIVIgDg4rqUpqKXvLEuFRVuEpSAOkhgNeecvaZ4Cg==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-musl@4.62.0': + resolution: {integrity: sha512-m9tsJz54LUXkSYM8+8PG81B9IKK5r+2T0clMq4QrS16xFosufU7firBDAZEsDheDs7wTlP7h3++S7lMsU955HA==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.62.0': + resolution: {integrity: sha512-3UvJ5PNVU16aJf6M3tFI24pWzAl2/ynfbyRN3ICyQajK1lSkrnVYNnLz3v04J32qKa0FczJc22zeToc0lr2A3w==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-ppc64-musl@4.62.0': + resolution: {integrity: sha512-vRWUAbYLGHBZS6Q8Msb2sfnf1fvJf+47t8l/TwOerM2qArzy+IeNMTHrYLHXh95h8MoatPHI5hhSZNs+mGXKPg==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.62.0': + resolution: {integrity: sha512-c00T5SYENHAt86cfW47URaP3Us5vLC/4QO7GYud1G5VNRffCwwCuBspwqYrriuJB+5m0WFzClCn9wed0FBjKvg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.62.0': + resolution: {integrity: sha512-krrCDilhXOwFkSkO3Wm9I/f9H0L92XHHwy2fwxjukxIbh0dem8gZqOW5Y8BsHrpJv5qwlRBV+Wl4ZFyRWhUpwg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.62.0': + resolution: {integrity: sha512-7pfYFSTc4/rUC/FtAI0Qp6QthDBCIi6/AuP1xYqFk5vanI6KnL5dWKP60OM/05LOsbwTmIcvr6eXC4CJuJ75IA==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.62.0': + resolution: {integrity: sha512-7SDIalKeIpG0Ifogbbdn58HmSotYMlf23K3dCJEmiVd9Fg36Vmni82iPQec27N3wY4Bvbxftkxz6vSx9OcouTg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.62.0': + resolution: {integrity: sha512-eRZevouTH2i1HeAVLqJuLnt256krQkGY0TN6WsTmsIhuzbh457HuWDMakKwmi0Cjadux983CoSr8Lim2QhUIFw==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openbsd-x64@4.62.0': + resolution: {integrity: sha512-3oVS7FLGa4U1qcvao9ylGxrjXZyUQqR8UwxEcnUEyPX53O/C/mKDZegNXTdHCP+h3e6ta/f1EN38Yif1mmZHYg==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.62.0': + resolution: {integrity: sha512-yTB9TgfWj5wHe5QgktAgXTLLot1gvEjl1NiPPAUiCs4oPrIWFl5V4nC3GrkNdj9LaAU4s94nVrGbGOCqUpyWsg==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.62.0': + resolution: {integrity: sha512-5LOhoaesY3doG1c+ac/2JtgREpKoJr5bUHH8tKY0V8di7+uSV6BwLs2PlR0/yzefGOkR+wE7ZolZphHCsyG5Rw==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.62.0': + resolution: {integrity: sha512-yYkWHhmbhRTWTnWos5HC4GcPQfjlzzCNbM9e/+GXrLuaBXYA3qSDR9f0Vgufd5S8yX81U8jPKp7ZnAjZFMtRnw==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.62.0': + resolution: {integrity: sha512-SoTb6lPg25xZlA2ibwQ++ahCCnH+FP0qmEuafMJ4gznZKOlXioKEAeJLgCrqjM98ACziXM9V1amFjICVL4IFoA==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.62.0': + resolution: {integrity: sha512-5L+T1fMX4RIEBoZzT0+sQ0PhTS36NULFmMXtl1TZo44TMAROIMHbZufSOjVWt/Y622BtxgxtaNOokbTDvfsrZA==} + cpu: [x64] + os: [win32] + '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} + '@sentry-internal/browser-utils@10.57.0': + resolution: {integrity: sha512-tXObp954rMTSYKlbftjVXHtNl4t/6ssks3jkqyzmKb+PDPWzabGQO7sWwqVuTjT8Kx/8A3FmriS1bGmqxiJy3A==} + engines: {node: '>=18'} + + '@sentry-internal/feedback@10.57.0': + resolution: {integrity: sha512-ZcF4QhkqGX3iiQSXB2N0N3Awp+j5iqnDRu6PA/qyLFrWqH5ZiiAAgu59OLD9E6XAdg6iFtLYw19MAMZVK8qNOQ==} + engines: {node: '>=18'} + + '@sentry-internal/replay-canvas@10.57.0': + resolution: {integrity: sha512-zsfa4JcfV0AEc9YhNxNabd5lSZL2Av84saAyexGAqcHs+67m9Gd0cGStOzMb/nCl7UAtmdP0aI+G7a3rcxxN/A==} + engines: {node: '>=18'} + + '@sentry-internal/replay@10.57.0': + resolution: {integrity: sha512-Wmnx/6ABynVH1iwuoNUqJNyjIUqsqoGML7qsyivBRKb5Wo2YQtPOQlQYfxfZSvWzGpcoSVdInkRjDssUQxQEQg==} + engines: {node: '>=18'} + + '@sentry-internal/server-utils@10.57.0': + resolution: {integrity: sha512-Qu8ETmX/ITzteG7Im46b9HOxKKzeaIeqNvftaIlFURu1RUQdHbtGerS7QOmXzwnhuqNGNeiCQYkduB798IfRqA==} + engines: {node: '>=18'} + + '@sentry/babel-plugin-component-annotate@5.3.0': + resolution: {integrity: sha512-p4q8gn8wcFqZGP/s2MnJCAAd8fTikaU6A0mM97RDHQgStcrYiaS0Sc5zUNfb1V+UOLPuvdEdL6MwyxfzjYJQTA==} + engines: {node: '>= 18'} + + '@sentry/browser@10.57.0': + resolution: {integrity: sha512-s36AQy/CKXTfyY9Z+qUhzNomntZXgfs0rbaK7q9ffnFkqcPwzE8qQtVs58y3Suut56u+AhwSztgQtERcuZ5VIA==} + engines: {node: '>=18'} + + '@sentry/bundler-plugin-core@5.3.0': + resolution: {integrity: sha512-L5T60sWdAI3qWwdg3Ptwek/0TY59PERrxyqp4XMUkroayQvGd9r5dIW9Q1kSeXX9iJ442nXbFZKAOyCKV4Z13Q==} + engines: {node: '>= 18'} + + '@sentry/cli-darwin@2.58.6': + resolution: {integrity: sha512-udAVvcyfNa0R+95GvPz/+43/N3TC0TYKdkQ7D7jhPSzbcMc7l2fxRNN5yB3UpCA5fWFnW4toeaqwDBhb/Wh3LA==} + engines: {node: '>=10'} + os: [darwin] + + '@sentry/cli-linux-arm64@2.58.6': + resolution: {integrity: sha512-q8mEcNNmeXMy5i+jWT30TVpH7LcP4HD21CD5XRSPAd/a912HF6EpK0ybf/1USO14WOhoXbAGi9txwaWabSe33g==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux, freebsd, android] + + '@sentry/cli-linux-arm@2.58.6': + resolution: {integrity: sha512-pD0LAt5PcUzAinBwvDqc66x9+2CabHEv486yP0gRjWO7SakbaxmfVq/EXd8VLq/Tzi39LAu422UYK1lpW3MILw==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux, freebsd, android] + + '@sentry/cli-linux-i686@2.58.6': + resolution: {integrity: sha512-q8vNJi1eOV/4vxAFWBsEwLHoSYapaZHIf4j76KJGJXFKTkEbsjCOOsKbwUIBTQQhRgV4DFWh3ryfsPS/que4Kg==} + engines: {node: '>=10'} + cpu: [x86, ia32] + os: [linux, freebsd, android] + + '@sentry/cli-linux-x64@2.58.6': + resolution: {integrity: sha512-DZu956Mhi3ZRjTBe1WdbGV46ldVbA8d2rgp/fh51GsI25zjBHah4wZnPTSzpc+YqxU6pJpg579B/r3jrIK530Q==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux, freebsd, android] + + '@sentry/cli-win32-arm64@2.58.6': + resolution: {integrity: sha512-nj0Ff/kmAB73EPDhR8B4O9r+NUHK5GkPCkGWC+kXVemqAJWL5jcJ5KdxG0l/S0z6RoEoltID8/43/B+TaMlT7A==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + + '@sentry/cli-win32-i686@2.58.6': + resolution: {integrity: sha512-WNZiDzPbgsEMQWq4avsQ391v/xWKJDIWWWo9GYl+N/w5qcYKkoDW7wQG7T9FasI6ENn68phChTOAPXXxbfAdOg==} + engines: {node: '>=10'} + cpu: [x86, ia32] + os: [win32] + + '@sentry/cli-win32-x64@2.58.6': + resolution: {integrity: sha512-R35WJ17oF4D2eqI1DR2sQQqr0fjRTt5xoP16WrTu91XM2lndRMFsnjh+/GttbxapLCBNlrjzia99MJ0PZHZpgA==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + + '@sentry/cli@2.58.6': + resolution: {integrity: sha512-baBcNPLLfUi9WuL+Tpri9BFaAdvugZIKelC5X0tt0Zdy+K0K+PCVSrnNmwMWU/HyaF/SEv6b6UHnXIdqanBlcg==} + engines: {node: '>= 10'} + hasBin: true + + '@sentry/core@10.57.0': + resolution: {integrity: sha512-kntItTA2kiT0YpL7encXaF6mkdZMB+y48lwj8w1wkfBpfJAC7sifdgrzLQZqmsqVNE3crg9VfufaAGA+78uFMg==} + engines: {node: '>=18'} + + '@sentry/nextjs@10.57.0': + resolution: {integrity: sha512-jRsyc387+YoOpYoxtJaL8VLzCq4I0KrIsVcZW4j3gA4LHG2nolKV4IDGrYWxEygc41Sgrtm1ZYbZAvjHMd9phQ==} + engines: {node: '>=18'} + peerDependencies: + next: ^13.2.0 || ^14.0 || ^15.0.0-rc.0 || ^16.0.0-0 + + '@sentry/node-core@10.57.0': + resolution: {integrity: sha512-2v2IF6MfTiu7pimWEq2rYhZsmlwyNbs3bHUsrYFPeP/Rpa6ObDuUWPdVEzJjfyK+AqqYZYxZdV0l3+B13kTEmQ==} + engines: {node: '>=18'} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + '@opentelemetry/core': ^1.30.1 || ^2.1.0 + '@opentelemetry/exporter-trace-otlp-http': '>=0.57.0 <1' + '@opentelemetry/instrumentation': '>=0.57.1 <1' + '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.1.0 + '@opentelemetry/semantic-conventions': ^1.39.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@opentelemetry/core': + optional: true + '@opentelemetry/exporter-trace-otlp-http': + optional: true + '@opentelemetry/instrumentation': + optional: true + '@opentelemetry/sdk-trace-base': + optional: true + '@opentelemetry/semantic-conventions': + optional: true + + '@sentry/node@10.57.0': + resolution: {integrity: sha512-7KEStrJ97wPf1fA5nU5ONeTTcIIlh7oT8OMffEVA1PXmlhFoXhcQZVzr4rM+zj9tfMWT01og5Ng/Grgh3dN+FA==} + engines: {node: '>=18'} + + '@sentry/opentelemetry@10.57.0': + resolution: {integrity: sha512-iwRz8cEK0GOISG34aJRO8GdYOk3nfpuT6dT2GDQrxw8f7JjkJKx9LPU8MaenOFa4MhY+Z02hI6NNcrbsoI3cXg==} + engines: {node: '>=18'} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + '@opentelemetry/core': ^1.30.1 || ^2.1.0 + '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.1.0 + '@opentelemetry/semantic-conventions': ^1.39.0 + + '@sentry/react@10.57.0': + resolution: {integrity: sha512-6QThwQ4XWQ2rwKZEVQ9P9WKl7JlowC7S5LpAvmMdrwlfJBpLDFOsM7tycnIvbXTXf0ZOOuLFPa4L4YYbdyNGmA==} + engines: {node: '>=18'} + peerDependencies: + react: ^16.14.0 || 17.x || 18.x || 19.x + + '@sentry/vercel-edge@10.57.0': + resolution: {integrity: sha512-8liagiXIWfyG0xGMmZQPCNOvqfzJcL7djB2jjNCaF5y7C+X/NTUHr4sqUHfAHqpQFUUXqmBT/mZv9HB5FDLhyQ==} + engines: {node: '>=18'} + + '@sentry/webpack-plugin@5.3.0': + resolution: {integrity: sha512-i3OQUrS0FZlXLgq57RIKDp+vHHzuvYKPCKewAPXULWKMsBXFGhP6veGRQ+6To/pmZkkXjEX5ofVNDy9C3jEPKQ==} + engines: {node: '>= 18'} + peerDependencies: + webpack: '>=5.0.0' + '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} @@ -959,6 +1291,68 @@ packages: '@vitest/utils@4.1.8': resolution: {integrity: sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==} + '@webassemblyjs/ast@1.14.1': + resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} + + '@webassemblyjs/floating-point-hex-parser@1.13.2': + resolution: {integrity: sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==} + + '@webassemblyjs/helper-api-error@1.13.2': + resolution: {integrity: sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==} + + '@webassemblyjs/helper-buffer@1.14.1': + resolution: {integrity: sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==} + + '@webassemblyjs/helper-numbers@1.13.2': + resolution: {integrity: sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==} + + '@webassemblyjs/helper-wasm-bytecode@1.13.2': + resolution: {integrity: sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==} + + '@webassemblyjs/helper-wasm-section@1.14.1': + resolution: {integrity: sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==} + + '@webassemblyjs/ieee754@1.13.2': + resolution: {integrity: sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==} + + '@webassemblyjs/leb128@1.13.2': + resolution: {integrity: sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==} + + '@webassemblyjs/utf8@1.13.2': + resolution: {integrity: sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==} + + '@webassemblyjs/wasm-edit@1.14.1': + resolution: {integrity: sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==} + + '@webassemblyjs/wasm-gen@1.14.1': + resolution: {integrity: sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==} + + '@webassemblyjs/wasm-opt@1.14.1': + resolution: {integrity: sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==} + + '@webassemblyjs/wasm-parser@1.14.1': + resolution: {integrity: sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==} + + '@webassemblyjs/wast-printer@1.14.1': + resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} + + '@xtuc/ieee754@1.2.0': + resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} + + '@xtuc/long@4.2.2': + resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + + acorn-import-attributes@1.9.5: + resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} + peerDependencies: + acorn: ^8 + + acorn-import-phases@1.0.4: + resolution: {integrity: sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==} + engines: {node: '>=10.13.0'} + peerDependencies: + acorn: ^8.14.0 + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -969,9 +1363,29 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + + ajv-formats@2.1.1: + resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv-keywords@5.1.0: + resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==} + peerDependencies: + ajv: ^8.8.2 + ajv@6.15.0: resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -1080,6 +1494,9 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -1107,6 +1524,13 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + chrome-trace-event@1.0.4: + resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} + engines: {node: '>=6.0'} + + cjs-module-lexer@2.2.0: + resolution: {integrity: sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==} + client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} @@ -1117,6 +1541,12 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + commondir@1.0.1: + resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -1205,6 +1635,10 @@ packages: dom-accessibility-api@0.6.3: resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -1219,6 +1653,10 @@ packages: resolution: {integrity: sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==} engines: {node: '>=10.13.0'} + enhanced-resolve@5.24.0: + resolution: {integrity: sha512-SkE2t82KlkkxQRVMVLAGKxLfORGQfrkx5dkj+vlgXRVNEdPc4eZcR+J/Fvj8C+yKSFH5L0q3NFlyufOVQnCcYQ==} + engines: {node: '>=10.13.0'} + entities@8.0.0: resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} engines: {node: '>=20.19.0'} @@ -1340,6 +1778,10 @@ packages: peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + eslint-scope@5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} + eslint-scope@8.4.0: resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1378,10 +1820,17 @@ packages: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} engines: {node: '>=4.0'} + estraverse@4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + estraverse@5.3.0: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} @@ -1389,6 +1838,10 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + expect-type@1.3.0: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} @@ -1406,6 +1859,9 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-uri@3.1.2: + resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} + fastq@1.20.1: resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} @@ -1487,6 +1943,13 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} + glob-to-regexp@0.4.1: + resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} + + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} + globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} @@ -1543,6 +2006,10 @@ packages: resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -1555,6 +2022,10 @@ packages: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} + import-in-the-middle@3.0.2: + resolution: {integrity: sha512-LGLYRl0A2gtyUJb2WDliBHmk6TtlHwdDjxonacZ8QrEs/ZW+YDgNv2QAfjRQWpS8HqvNcq6GGnN6jrOa5FysDQ==} + engines: {node: '>=18'} + imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -1641,6 +2112,9 @@ packages: is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-reference@1.2.1: + resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} + is-regex@1.2.1: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} @@ -1687,6 +2161,10 @@ packages: resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} engines: {node: '>= 0.4'} + jest-worker@27.5.1: + resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} + engines: {node: '>= 10.13.0'} + jiti@2.7.0: resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true @@ -1718,6 +2196,9 @@ packages: json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} @@ -1818,6 +2299,10 @@ packages: resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} engines: {node: '>= 12.0.0'} + loader-runner@4.3.2: + resolution: {integrity: sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w==} + engines: {node: '>=6.11.5'} + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -1850,6 +2335,9 @@ packages: mdn-data@2.27.1: resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -1858,6 +2346,10 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} @@ -1872,6 +2364,13 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + module-details-from-path@1.0.4: + resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -1888,6 +2387,9 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + next@16.2.9: resolution: {integrity: sha512-MEOJiq/UvuezAdqVSceHbqDgZt1kDw2tpGVOlsdIoJsQdbN2JY2hpVG4xnXGkbdJUOEWhnRfiu/O4Hpc9Juwww==} engines: {node: '>=20.9.0'} @@ -1913,6 +2415,15 @@ packages: resolution: {integrity: sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==} engines: {node: '>= 0.4'} + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + node-releases@2.0.47: resolution: {integrity: sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==} engines: {node: '>=18'} @@ -1987,6 +2498,10 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -2021,9 +2536,16 @@ packages: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + progress@2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -2062,6 +2584,10 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + require-in-the-middle@8.0.1: + resolution: {integrity: sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==} + engines: {node: '>=9.3.0 || >=8.10.0 <9.0.0'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -2083,6 +2609,11 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + rollup@4.62.0: + resolution: {integrity: sha512-nc72Wgq62I7rtDV4izT5/aaS0zxy3kttkinf9586ApknY3jZO9NYsmtc24fUckA0X7Q2v+ML4a15pdUlV5V/jA==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -2105,6 +2636,10 @@ packages: scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + schema-utils@4.3.3: + resolution: {integrity: sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==} + engines: {node: '>= 10.13.0'} + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -2161,12 +2696,23 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + stable-hash@0.0.5: resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + stacktrace-parser@0.1.11: + resolution: {integrity: sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==} + engines: {node: '>=6'} + std-env@4.1.0: resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} @@ -2226,6 +2772,10 @@ packages: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} @@ -2240,6 +2790,54 @@ packages: resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} engines: {node: '>=6'} + terser-webpack-plugin@5.6.1: + resolution: {integrity: sha512-201R5j+sJpK8nFWwKVyNfZot8FaJbLZDq5evriVzbV1wDtSXDjRUDRfJzHpAaxFDMEhsZL1QkeqM61wgsS3KaQ==} + engines: {node: '>= 10.13.0'} + peerDependencies: + '@minify-html/node': '*' + '@swc/core': '*' + '@swc/css': '*' + '@swc/html': '*' + clean-css: '*' + cssnano: '*' + csso: '*' + esbuild: '*' + html-minifier-terser: '*' + lightningcss: '*' + postcss: '*' + uglify-js: '*' + webpack: ^5.1.0 + peerDependenciesMeta: + '@minify-html/node': + optional: true + '@swc/core': + optional: true + '@swc/css': + optional: true + '@swc/html': + optional: true + clean-css: + optional: true + cssnano: + optional: true + csso: + optional: true + esbuild: + optional: true + html-minifier-terser: + optional: true + lightningcss: + optional: true + postcss: + optional: true + uglify-js: + optional: true + + terser@5.48.0: + resolution: {integrity: sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==} + engines: {node: '>=10'} + hasBin: true + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -2270,6 +2868,9 @@ packages: resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} engines: {node: '>=16'} + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + tr46@6.0.0: resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} engines: {node: '>=20'} @@ -2290,6 +2891,10 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} + type-fest@0.7.1: + resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==} + engines: {node: '>=8'} + typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -2429,10 +3034,31 @@ packages: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} + watchpack@2.5.2: + resolution: {integrity: sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg==} + engines: {node: '>=10.13.0'} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + webidl-conversions@8.0.1: resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} engines: {node: '>=20'} + webpack-sources@3.5.0: + resolution: {integrity: sha512-HPuy+uuoTCaaoEoI1LQ3JN9+vrPBvEesnnX1jADHy728cHSMlq4wUc4afYqahq2B1mhQVZxCXOkNTnXltr+2vQ==} + engines: {node: '>=10.13.0'} + + webpack@5.107.2: + resolution: {integrity: sha512-v7RhXaJbpMlV0D7hC7lb2EbnxkoeUqf9qhKr6lozx3Q48pmFrqqNRmZFUEGmi7pSwm6fCQ2H1IjvCkHqdpVdjQ==} + engines: {node: '>=10.13.0'} + hasBin: true + peerDependencies: + webpack-cli: '*' + peerDependenciesMeta: + webpack-cli: + optional: true + whatwg-mimetype@5.0.0: resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} engines: {node: '>=20'} @@ -2441,6 +3067,9 @@ packages: resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -2813,149 +3442,456 @@ snapshots: '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 optional: true - '@img/sharp-linuxmusl-x64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.11.1 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/source-map@0.3.11': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@napi-rs/wasm-runtime@1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.2 + optional: true + + '@next/env@16.2.9': {} + + '@next/eslint-plugin-next@16.2.9': + dependencies: + fast-glob: 3.3.1 + + '@next/swc-darwin-arm64@16.2.9': + optional: true + + '@next/swc-darwin-x64@16.2.9': + optional: true + + '@next/swc-linux-arm64-gnu@16.2.9': + optional: true + + '@next/swc-linux-arm64-musl@16.2.9': + optional: true + + '@next/swc-linux-x64-gnu@16.2.9': + optional: true + + '@next/swc-linux-x64-musl@16.2.9': + optional: true + + '@next/swc-win32-arm64-msvc@16.2.9': + optional: true + + '@next/swc-win32-x64-msvc@16.2.9': + optional: true + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@nolyfill/is-core-module@1.0.39': {} + + '@opentelemetry/api-logs@0.214.0': + dependencies: + '@opentelemetry/api': 1.9.1 + + '@opentelemetry/api@1.9.1': {} + + '@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/semantic-conventions': 1.41.1 + + '@opentelemetry/instrumentation@0.214.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/api-logs': 0.214.0 + import-in-the-middle: 3.0.2 + require-in-the-middle: 8.0.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/resources@2.8.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 + + '@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 + + '@opentelemetry/semantic-conventions@1.41.1': {} + + '@oxc-project/types@0.133.0': {} + + '@rolldown/binding-android-arm64@1.0.3': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.3': + optional: true + + '@rolldown/binding-darwin-x64@1.0.3': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.3': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.3': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.3': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.3': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.3': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.3': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.3': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.3': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@rollup/plugin-commonjs@28.0.1(rollup@4.62.0)': + dependencies: + '@rollup/pluginutils': 5.4.0(rollup@4.62.0) + commondir: 1.0.1 + estree-walker: 2.0.2 + fdir: 6.5.0(picomatch@4.0.4) + is-reference: 1.2.1 + magic-string: 0.30.21 + picomatch: 4.0.4 + optionalDependencies: + rollup: 4.62.0 + + '@rollup/pluginutils@5.4.0(rollup@4.62.0)': + dependencies: + '@types/estree': 1.0.9 + estree-walker: 2.0.2 + picomatch: 4.0.4 + optionalDependencies: + rollup: 4.62.0 + + '@rollup/rollup-android-arm-eabi@4.62.0': + optional: true + + '@rollup/rollup-android-arm64@4.62.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.62.0': + optional: true + + '@rollup/rollup-darwin-x64@4.62.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.62.0': optional: true - '@img/sharp-wasm32@0.34.5': - dependencies: - '@emnapi/runtime': 1.11.1 + '@rollup/rollup-freebsd-x64@4.62.0': optional: true - '@img/sharp-win32-arm64@0.34.5': + '@rollup/rollup-linux-arm-gnueabihf@4.62.0': optional: true - '@img/sharp-win32-ia32@0.34.5': + '@rollup/rollup-linux-arm-musleabihf@4.62.0': optional: true - '@img/sharp-win32-x64@0.34.5': + '@rollup/rollup-linux-arm64-gnu@4.62.0': optional: true - '@jridgewell/gen-mapping@0.3.13': - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 + '@rollup/rollup-linux-arm64-musl@4.62.0': + optional: true - '@jridgewell/remapping@2.3.5': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 + '@rollup/rollup-linux-loong64-gnu@4.62.0': + optional: true - '@jridgewell/resolve-uri@3.1.2': {} + '@rollup/rollup-linux-loong64-musl@4.62.0': + optional: true - '@jridgewell/sourcemap-codec@1.5.5': {} + '@rollup/rollup-linux-ppc64-gnu@4.62.0': + optional: true - '@jridgewell/trace-mapping@0.3.31': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 + '@rollup/rollup-linux-ppc64-musl@4.62.0': + optional: true - '@napi-rs/wasm-runtime@1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': - dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@tybys/wasm-util': 0.10.2 + '@rollup/rollup-linux-riscv64-gnu@4.62.0': optional: true - '@next/env@16.2.9': {} + '@rollup/rollup-linux-riscv64-musl@4.62.0': + optional: true - '@next/eslint-plugin-next@16.2.9': - dependencies: - fast-glob: 3.3.1 + '@rollup/rollup-linux-s390x-gnu@4.62.0': + optional: true - '@next/swc-darwin-arm64@16.2.9': + '@rollup/rollup-linux-x64-gnu@4.62.0': optional: true - '@next/swc-darwin-x64@16.2.9': + '@rollup/rollup-linux-x64-musl@4.62.0': optional: true - '@next/swc-linux-arm64-gnu@16.2.9': + '@rollup/rollup-openbsd-x64@4.62.0': optional: true - '@next/swc-linux-arm64-musl@16.2.9': + '@rollup/rollup-openharmony-arm64@4.62.0': optional: true - '@next/swc-linux-x64-gnu@16.2.9': + '@rollup/rollup-win32-arm64-msvc@4.62.0': optional: true - '@next/swc-linux-x64-musl@16.2.9': + '@rollup/rollup-win32-ia32-msvc@4.62.0': optional: true - '@next/swc-win32-arm64-msvc@16.2.9': + '@rollup/rollup-win32-x64-gnu@4.62.0': optional: true - '@next/swc-win32-x64-msvc@16.2.9': + '@rollup/rollup-win32-x64-msvc@4.62.0': optional: true - '@nodelib/fs.scandir@2.1.5': + '@rtsao/scc@1.1.0': {} + + '@sentry-internal/browser-utils@10.57.0': dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 + '@sentry/core': 10.57.0 - '@nodelib/fs.stat@2.0.5': {} + '@sentry-internal/feedback@10.57.0': + dependencies: + '@sentry/core': 10.57.0 - '@nodelib/fs.walk@1.2.8': + '@sentry-internal/replay-canvas@10.57.0': dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.20.1 + '@sentry-internal/replay': 10.57.0 + '@sentry/core': 10.57.0 - '@nolyfill/is-core-module@1.0.39': {} + '@sentry-internal/replay@10.57.0': + dependencies: + '@sentry-internal/browser-utils': 10.57.0 + '@sentry/core': 10.57.0 - '@oxc-project/types@0.133.0': {} + '@sentry-internal/server-utils@10.57.0': + dependencies: + '@sentry/core': 10.57.0 - '@rolldown/binding-android-arm64@1.0.3': - optional: true + '@sentry/babel-plugin-component-annotate@5.3.0': {} - '@rolldown/binding-darwin-arm64@1.0.3': - optional: true + '@sentry/browser@10.57.0': + dependencies: + '@sentry-internal/browser-utils': 10.57.0 + '@sentry-internal/feedback': 10.57.0 + '@sentry-internal/replay': 10.57.0 + '@sentry-internal/replay-canvas': 10.57.0 + '@sentry/core': 10.57.0 - '@rolldown/binding-darwin-x64@1.0.3': - optional: true + '@sentry/bundler-plugin-core@5.3.0': + dependencies: + '@babel/core': 7.29.7 + '@sentry/babel-plugin-component-annotate': 5.3.0 + '@sentry/cli': 2.58.6 + dotenv: 16.6.1 + find-up: 5.0.0 + glob: 13.0.6 + magic-string: 0.30.21 + transitivePeerDependencies: + - encoding + - supports-color - '@rolldown/binding-freebsd-x64@1.0.3': + '@sentry/cli-darwin@2.58.6': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.3': + '@sentry/cli-linux-arm64@2.58.6': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.3': + '@sentry/cli-linux-arm@2.58.6': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.3': + '@sentry/cli-linux-i686@2.58.6': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.0.3': + '@sentry/cli-linux-x64@2.58.6': optional: true - '@rolldown/binding-linux-s390x-gnu@1.0.3': + '@sentry/cli-win32-arm64@2.58.6': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.3': + '@sentry/cli-win32-i686@2.58.6': optional: true - '@rolldown/binding-linux-x64-musl@1.0.3': + '@sentry/cli-win32-x64@2.58.6': optional: true - '@rolldown/binding-openharmony-arm64@1.0.3': - optional: true + '@sentry/cli@2.58.6': + dependencies: + https-proxy-agent: 5.0.1 + node-fetch: 2.7.0 + progress: 2.0.3 + proxy-from-env: 1.1.0 + which: 2.0.2 + optionalDependencies: + '@sentry/cli-darwin': 2.58.6 + '@sentry/cli-linux-arm': 2.58.6 + '@sentry/cli-linux-arm64': 2.58.6 + '@sentry/cli-linux-i686': 2.58.6 + '@sentry/cli-linux-x64': 2.58.6 + '@sentry/cli-win32-arm64': 2.58.6 + '@sentry/cli-win32-i686': 2.58.6 + '@sentry/cli-win32-x64': 2.58.6 + transitivePeerDependencies: + - encoding + - supports-color - '@rolldown/binding-wasm32-wasi@1.0.3': + '@sentry/core@10.57.0': {} + + '@sentry/nextjs@10.57.0(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1))(next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(webpack@5.107.2)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/semantic-conventions': 1.41.1 + '@rollup/plugin-commonjs': 28.0.1(rollup@4.62.0) + '@sentry-internal/browser-utils': 10.57.0 + '@sentry/bundler-plugin-core': 5.3.0 + '@sentry/core': 10.57.0 + '@sentry/node': 10.57.0 + '@sentry/opentelemetry': 10.57.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.41.1) + '@sentry/react': 10.57.0(react@19.2.4) + '@sentry/vercel-edge': 10.57.0 + '@sentry/webpack-plugin': 5.3.0(webpack@5.107.2) + next: 16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + rollup: 4.62.0 + stacktrace-parser: 0.1.11 + transitivePeerDependencies: + - '@opentelemetry/core' + - '@opentelemetry/exporter-trace-otlp-http' + - '@opentelemetry/sdk-trace-base' + - encoding + - react + - supports-color + - webpack + + '@sentry/node-core@10.57.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.214.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.41.1)': dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) - optional: true + '@sentry/core': 10.57.0 + '@sentry/opentelemetry': 10.57.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.41.1) + import-in-the-middle: 3.0.2 + optionalDependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 + + '@sentry/node@10.57.0': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 + '@sentry-internal/server-utils': 10.57.0 + '@sentry/core': 10.57.0 + '@sentry/node-core': 10.57.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.214.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.41.1) + '@sentry/opentelemetry': 10.57.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.41.1) + import-in-the-middle: 3.0.2 + transitivePeerDependencies: + - '@opentelemetry/exporter-trace-otlp-http' + - supports-color - '@rolldown/binding-win32-arm64-msvc@1.0.3': - optional: true + '@sentry/opentelemetry@10.57.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.41.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.41.1 + '@sentry/core': 10.57.0 - '@rolldown/binding-win32-x64-msvc@1.0.3': - optional: true + '@sentry/react@10.57.0(react@19.2.4)': + dependencies: + '@sentry/browser': 10.57.0 + '@sentry/core': 10.57.0 + react: 19.2.4 - '@rolldown/pluginutils@1.0.1': {} + '@sentry/vercel-edge@10.57.0': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/resources': 2.8.0(@opentelemetry/api@1.9.1) + '@sentry/core': 10.57.0 - '@rtsao/scc@1.1.0': {} + '@sentry/webpack-plugin@5.3.0(webpack@5.107.2)': + dependencies: + '@sentry/bundler-plugin-core': 5.3.0 + webpack: 5.107.2 + transitivePeerDependencies: + - encoding + - supports-color '@standard-schema/spec@1.1.0': {} @@ -3255,10 +4191,10 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.12.2': optional: true - '@vitejs/plugin-react@6.0.2(vite@8.0.16(@types/node@20.19.43)(jiti@2.7.0))': + '@vitejs/plugin-react@6.0.2(vite@8.0.16(@types/node@20.19.43)(jiti@2.7.0)(terser@5.48.0))': dependencies: '@rolldown/pluginutils': 1.0.1 - vite: 8.0.16(@types/node@20.19.43)(jiti@2.7.0) + vite: 8.0.16(@types/node@20.19.43)(jiti@2.7.0)(terser@5.48.0) '@vitest/expect@4.1.8': dependencies: @@ -3269,13 +4205,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.8(vite@8.0.16(@types/node@20.19.43)(jiti@2.7.0))': + '@vitest/mocker@4.1.8(vite@8.0.16(@types/node@20.19.43)(jiti@2.7.0)(terser@5.48.0))': dependencies: '@vitest/spy': 4.1.8 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.16(@types/node@20.19.43)(jiti@2.7.0) + vite: 8.0.16(@types/node@20.19.43)(jiti@2.7.0)(terser@5.48.0) '@vitest/pretty-format@4.1.8': dependencies: @@ -3301,12 +4237,115 @@ snapshots: convert-source-map: 2.0.0 tinyrainbow: 3.1.0 + '@webassemblyjs/ast@1.14.1': + dependencies: + '@webassemblyjs/helper-numbers': 1.13.2 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + + '@webassemblyjs/floating-point-hex-parser@1.13.2': {} + + '@webassemblyjs/helper-api-error@1.13.2': {} + + '@webassemblyjs/helper-buffer@1.14.1': {} + + '@webassemblyjs/helper-numbers@1.13.2': + dependencies: + '@webassemblyjs/floating-point-hex-parser': 1.13.2 + '@webassemblyjs/helper-api-error': 1.13.2 + '@xtuc/long': 4.2.2 + + '@webassemblyjs/helper-wasm-bytecode@1.13.2': {} + + '@webassemblyjs/helper-wasm-section@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/wasm-gen': 1.14.1 + + '@webassemblyjs/ieee754@1.13.2': + dependencies: + '@xtuc/ieee754': 1.2.0 + + '@webassemblyjs/leb128@1.13.2': + dependencies: + '@xtuc/long': 4.2.2 + + '@webassemblyjs/utf8@1.13.2': {} + + '@webassemblyjs/wasm-edit@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/helper-wasm-section': 1.14.1 + '@webassemblyjs/wasm-gen': 1.14.1 + '@webassemblyjs/wasm-opt': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + '@webassemblyjs/wast-printer': 1.14.1 + + '@webassemblyjs/wasm-gen@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/ieee754': 1.13.2 + '@webassemblyjs/leb128': 1.13.2 + '@webassemblyjs/utf8': 1.13.2 + + '@webassemblyjs/wasm-opt@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/wasm-gen': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + + '@webassemblyjs/wasm-parser@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-api-error': 1.13.2 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/ieee754': 1.13.2 + '@webassemblyjs/leb128': 1.13.2 + '@webassemblyjs/utf8': 1.13.2 + + '@webassemblyjs/wast-printer@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@xtuc/long': 4.2.2 + + '@xtuc/ieee754@1.2.0': {} + + '@xtuc/long@4.2.2': {} + + acorn-import-attributes@1.9.5(acorn@8.17.0): + dependencies: + acorn: 8.17.0 + + acorn-import-phases@1.0.4(acorn@8.17.0): + dependencies: + acorn: 8.17.0 + acorn-jsx@5.3.2(acorn@8.17.0): dependencies: acorn: 8.17.0 acorn@8.17.0: {} + agent-base@6.0.2: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + ajv-formats@2.1.1(ajv@8.20.0): + optionalDependencies: + ajv: 8.20.0 + + ajv-keywords@5.1.0(ajv@8.20.0): + dependencies: + ajv: 8.20.0 + fast-deep-equal: 3.1.3 + ajv@6.15.0: dependencies: fast-deep-equal: 3.1.3 @@ -3314,6 +4353,13 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.2 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + ansi-regex@5.0.1: {} ansi-styles@4.3.0: @@ -3442,6 +4488,8 @@ snapshots: node-releases: 2.0.47 update-browserslist-db: 1.2.3(browserslist@4.28.2) + buffer-from@1.1.2: {} + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -3470,6 +4518,10 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + chrome-trace-event@1.0.4: {} + + cjs-module-lexer@2.2.0: {} + client-only@0.0.1: {} color-convert@2.0.1: @@ -3478,6 +4530,10 @@ snapshots: color-name@1.1.4: {} + commander@2.20.3: {} + + commondir@1.0.1: {} + concat-map@0.0.1: {} convert-source-map@2.0.0: {} @@ -3560,6 +4616,8 @@ snapshots: dom-accessibility-api@0.6.3: {} + dotenv@16.6.1: {} + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -3575,6 +4633,11 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.3.3 + enhanced-resolve@5.24.0: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.3 + entities@8.0.0: {} es-abstract@1.24.2: @@ -3819,6 +4882,11 @@ snapshots: string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 + eslint-scope@5.1.1: + dependencies: + esrecurse: 4.3.0 + estraverse: 4.3.0 + eslint-scope@8.4.0: dependencies: esrecurse: 4.3.0 @@ -3885,14 +4953,20 @@ snapshots: dependencies: estraverse: 5.3.0 + estraverse@4.3.0: {} + estraverse@5.3.0: {} + estree-walker@2.0.2: {} + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.9 esutils@2.0.3: {} + events@3.3.0: {} + expect-type@1.3.0: {} fast-deep-equal@3.1.3: {} @@ -3909,6 +4983,8 @@ snapshots: fast-levenshtein@2.0.6: {} + fast-uri@3.1.2: {} + fastq@1.20.1: dependencies: reusify: 1.1.0 @@ -4000,6 +5076,14 @@ snapshots: dependencies: is-glob: 4.0.3 + glob-to-regexp@0.4.1: {} + + glob@13.0.6: + dependencies: + minimatch: 10.2.5 + minipass: 7.1.3 + path-scurry: 2.0.2 + globals@14.0.0: {} globals@16.4.0: {} @@ -4047,6 +5131,13 @@ snapshots: transitivePeerDependencies: - '@noble/hashes' + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + ignore@5.3.2: {} ignore@7.0.5: {} @@ -4056,6 +5147,13 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 + import-in-the-middle@3.0.2: + dependencies: + acorn: 8.17.0 + acorn-import-attributes: 1.9.5(acorn@8.17.0) + cjs-module-lexer: 2.2.0 + module-details-from-path: 1.0.4 + imurmurhash@0.1.4: {} indent-string@4.0.0: {} @@ -4145,6 +5243,10 @@ snapshots: is-potential-custom-element-name@1.0.1: {} + is-reference@1.2.1: + dependencies: + '@types/estree': 1.0.9 + is-regex@1.2.1: dependencies: call-bound: 1.0.4 @@ -4197,6 +5299,12 @@ snapshots: has-symbols: 1.1.0 set-function-name: 2.0.2 + jest-worker@27.5.1: + dependencies: + '@types/node': 20.19.43 + merge-stream: 2.0.0 + supports-color: 8.1.1 + jiti@2.7.0: {} js-tokens@4.0.0: {} @@ -4237,6 +5345,8 @@ snapshots: json-schema-traverse@0.4.1: {} + json-schema-traverse@1.0.0: {} + json-stable-stringify-without-jsonify@1.0.1: {} json5@1.0.2: @@ -4316,6 +5426,8 @@ snapshots: lightningcss-win32-arm64-msvc: 1.32.0 lightningcss-win32-x64-msvc: 1.32.0 + loader-runner@4.3.2: {} + locate-path@6.0.0: dependencies: p-locate: 5.0.0 @@ -4342,6 +5454,8 @@ snapshots: mdn-data@2.27.1: {} + merge-stream@2.0.0: {} + merge2@1.4.1: {} micromatch@4.0.8: @@ -4349,6 +5463,8 @@ snapshots: braces: 3.0.3 picomatch: 2.3.2 + mime-db@1.54.0: {} + min-indent@1.0.1: {} minimatch@10.2.5: @@ -4361,6 +5477,10 @@ snapshots: minimist@1.2.8: {} + minipass@7.1.3: {} + + module-details-from-path@1.0.4: {} + ms@2.1.3: {} nanoid@3.3.12: {} @@ -4369,7 +5489,9 @@ snapshots: natural-compare@1.4.0: {} - next@16.2.9(@babel/core@7.29.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + neo-async@2.6.2: {} + + next@16.2.9(@babel/core@7.29.7)(@opentelemetry/api@1.9.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): dependencies: '@next/env': 16.2.9 '@swc/helpers': 0.5.15 @@ -4388,6 +5510,7 @@ snapshots: '@next/swc-linux-x64-musl': 16.2.9 '@next/swc-win32-arm64-msvc': 16.2.9 '@next/swc-win32-x64-msvc': 16.2.9 + '@opentelemetry/api': 1.9.1 sharp: 0.34.5 transitivePeerDependencies: - '@babel/core' @@ -4400,6 +5523,10 @@ snapshots: object.entries: 1.1.9 semver: 6.3.1 + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + node-releases@2.0.47: {} object-assign@4.1.1: {} @@ -4483,6 +5610,11 @@ snapshots: path-parse@1.0.7: {} + path-scurry@2.0.2: + dependencies: + lru-cache: 11.5.1 + minipass: 7.1.3 + pathe@2.0.3: {} picocolors@1.1.1: {} @@ -4513,12 +5645,16 @@ snapshots: ansi-styles: 5.2.0 react-is: 17.0.2 + progress@2.0.3: {} + prop-types@15.8.1: dependencies: loose-envify: 1.4.0 object-assign: 4.1.1 react-is: 16.13.1 + proxy-from-env@1.1.0: {} + punycode@2.3.1: {} queue-microtask@1.2.3: {} @@ -4561,6 +5697,13 @@ snapshots: require-from-string@2.0.2: {} + require-in-the-middle@8.0.1: + dependencies: + debug: 4.4.3 + module-details-from-path: 1.0.4 + transitivePeerDependencies: + - supports-color + resolve-from@4.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -4597,6 +5740,37 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.0.3 '@rolldown/binding-win32-x64-msvc': 1.0.3 + rollup@4.62.0: + dependencies: + '@types/estree': 1.0.9 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.62.0 + '@rollup/rollup-android-arm64': 4.62.0 + '@rollup/rollup-darwin-arm64': 4.62.0 + '@rollup/rollup-darwin-x64': 4.62.0 + '@rollup/rollup-freebsd-arm64': 4.62.0 + '@rollup/rollup-freebsd-x64': 4.62.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.62.0 + '@rollup/rollup-linux-arm-musleabihf': 4.62.0 + '@rollup/rollup-linux-arm64-gnu': 4.62.0 + '@rollup/rollup-linux-arm64-musl': 4.62.0 + '@rollup/rollup-linux-loong64-gnu': 4.62.0 + '@rollup/rollup-linux-loong64-musl': 4.62.0 + '@rollup/rollup-linux-ppc64-gnu': 4.62.0 + '@rollup/rollup-linux-ppc64-musl': 4.62.0 + '@rollup/rollup-linux-riscv64-gnu': 4.62.0 + '@rollup/rollup-linux-riscv64-musl': 4.62.0 + '@rollup/rollup-linux-s390x-gnu': 4.62.0 + '@rollup/rollup-linux-x64-gnu': 4.62.0 + '@rollup/rollup-linux-x64-musl': 4.62.0 + '@rollup/rollup-openbsd-x64': 4.62.0 + '@rollup/rollup-openharmony-arm64': 4.62.0 + '@rollup/rollup-win32-arm64-msvc': 4.62.0 + '@rollup/rollup-win32-ia32-msvc': 4.62.0 + '@rollup/rollup-win32-x64-gnu': 4.62.0 + '@rollup/rollup-win32-x64-msvc': 4.62.0 + fsevents: 2.3.3 + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -4626,6 +5800,13 @@ snapshots: scheduler@0.27.0: {} + schema-utils@4.3.3: + dependencies: + '@types/json-schema': 7.0.15 + ajv: 8.20.0 + ajv-formats: 2.1.1(ajv@8.20.0) + ajv-keywords: 5.1.0(ajv@8.20.0) + semver@6.3.1: {} semver@7.8.4: {} @@ -4722,10 +5903,21 @@ snapshots: source-map-js@1.2.1: {} + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + stable-hash@0.0.5: {} stackback@0.0.2: {} + stacktrace-parser@0.1.11: + dependencies: + type-fest: 0.7.1 + std-env@4.1.0: {} stop-iteration-iterator@1.1.0: @@ -4803,6 +5995,10 @@ snapshots: dependencies: has-flag: 4.0.0 + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + supports-preserve-symlinks-flag@1.0.0: {} symbol-tree@3.2.4: {} @@ -4811,6 +6007,21 @@ snapshots: tapable@2.3.3: {} + terser-webpack-plugin@5.6.1(webpack@5.107.2): + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + jest-worker: 27.5.1 + schema-utils: 4.3.3 + terser: 5.48.0 + webpack: 5.107.2 + + terser@5.48.0: + dependencies: + '@jridgewell/source-map': 0.3.11 + acorn: 8.17.0 + commander: 2.20.3 + source-map-support: 0.5.21 + tinybench@2.9.0: {} tinyexec@1.2.4: {} @@ -4836,6 +6047,8 @@ snapshots: dependencies: tldts: 7.4.2 + tr46@0.0.3: {} + tr46@6.0.0: dependencies: punycode: 2.3.1 @@ -4857,6 +6070,8 @@ snapshots: dependencies: prelude-ls: 1.2.1 + type-fest@0.7.1: {} + typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 @@ -4951,7 +6166,7 @@ snapshots: dependencies: punycode: 2.3.1 - vite@8.0.16(@types/node@20.19.43)(jiti@2.7.0): + vite@8.0.16(@types/node@20.19.43)(jiti@2.7.0)(terser@5.48.0): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 @@ -4962,11 +6177,12 @@ snapshots: '@types/node': 20.19.43 fsevents: 2.3.3 jiti: 2.7.0 + terser: 5.48.0 - vitest@4.1.8(@types/node@20.19.43)(jsdom@29.1.1)(vite@8.0.16(@types/node@20.19.43)(jiti@2.7.0)): + vitest@4.1.8(@opentelemetry/api@1.9.1)(@types/node@20.19.43)(jsdom@29.1.1)(vite@8.0.16(@types/node@20.19.43)(jiti@2.7.0)(terser@5.48.0)): dependencies: '@vitest/expect': 4.1.8 - '@vitest/mocker': 4.1.8(vite@8.0.16(@types/node@20.19.43)(jiti@2.7.0)) + '@vitest/mocker': 4.1.8(vite@8.0.16(@types/node@20.19.43)(jiti@2.7.0)(terser@5.48.0)) '@vitest/pretty-format': 4.1.8 '@vitest/runner': 4.1.8 '@vitest/snapshot': 4.1.8 @@ -4983,9 +6199,10 @@ snapshots: tinyexec: 1.2.4 tinyglobby: 0.2.17 tinyrainbow: 3.1.0 - vite: 8.0.16(@types/node@20.19.43)(jiti@2.7.0) + vite: 8.0.16(@types/node@20.19.43)(jiti@2.7.0)(terser@5.48.0) why-is-node-running: 2.3.0 optionalDependencies: + '@opentelemetry/api': 1.9.1 '@types/node': 20.19.43 jsdom: 29.1.1 transitivePeerDependencies: @@ -4995,8 +6212,55 @@ snapshots: dependencies: xml-name-validator: 5.0.0 + watchpack@2.5.2: + dependencies: + graceful-fs: 4.2.11 + + webidl-conversions@3.0.1: {} + webidl-conversions@8.0.1: {} + webpack-sources@3.5.0: {} + + webpack@5.107.2: + dependencies: + '@types/estree': 1.0.9 + '@types/json-schema': 7.0.15 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/wasm-edit': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + acorn: 8.17.0 + acorn-import-phases: 1.0.4(acorn@8.17.0) + browserslist: 4.28.2 + chrome-trace-event: 1.0.4 + enhanced-resolve: 5.24.0 + es-module-lexer: 2.1.0 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + loader-runner: 4.3.2 + mime-db: 1.54.0 + neo-async: 2.6.2 + schema-utils: 4.3.3 + tapable: 2.3.3 + terser-webpack-plugin: 5.6.1(webpack@5.107.2) + watchpack: 2.5.2 + webpack-sources: 3.5.0 + transitivePeerDependencies: + - '@minify-html/node' + - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso + - esbuild + - html-minifier-terser + - lightningcss + - postcss + - uglify-js + whatwg-mimetype@5.0.0: {} whatwg-url@16.0.1: @@ -5007,6 +6271,11 @@ snapshots: transitivePeerDependencies: - '@noble/hashes' + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 diff --git a/web/sentry.client.config.ts b/web/sentry.client.config.ts new file mode 100644 index 0000000..1976ce2 --- /dev/null +++ b/web/sentry.client.config.ts @@ -0,0 +1,7 @@ +import * as Sentry from "@sentry/nextjs"; + +Sentry.init({ + dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, + tracesSampleRate: 1.0, + debug: false, +}); diff --git a/web/sentry.edge.config.ts b/web/sentry.edge.config.ts new file mode 100644 index 0000000..1976ce2 --- /dev/null +++ b/web/sentry.edge.config.ts @@ -0,0 +1,7 @@ +import * as Sentry from "@sentry/nextjs"; + +Sentry.init({ + dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, + tracesSampleRate: 1.0, + debug: false, +}); diff --git a/web/sentry.server.config.ts b/web/sentry.server.config.ts new file mode 100644 index 0000000..1976ce2 --- /dev/null +++ b/web/sentry.server.config.ts @@ -0,0 +1,7 @@ +import * as Sentry from "@sentry/nextjs"; + +Sentry.init({ + dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, + tracesSampleRate: 1.0, + debug: false, +}); From 05dc1b8b099546d2fd5cfb127daddc2577c48835 Mon Sep 17 00:00:00 2001 From: GRACENOBLE Date: Mon, 15 Jun 2026 15:26:04 +0300 Subject: [PATCH 2/2] fix(observability): address CodeRabbit review findings - backend/.env.example: separate inline comments from FIREBASE var values so dotenv parsers don't treat # as a literal value - backend/docs/bootstrap.md: add missing SentryDSN field to Config struct docs - backend/docs/routing.md: update RegisterRoutes signature and NewServer wiring snippet to include sentryDSN param - backend/docs/observability.md: add dotenv language tag to code fence - middleware/sentry.go: log sentry.Init errors instead of discarding with _; degrade to no-op on failure - mobile/build.gradle.kts: read SENTRY_DSN from local.properties (gitignored) instead of hardcoding empty string - web/docs: fix stale .env.local.example references to .env.example; add language tag to code fence Co-Authored-By: Claude Sonnet 4.6 --- backend/.env.example | 6 ++++-- backend/docs/bootstrap.md | 1 + backend/docs/observability.md | 2 +- backend/docs/routing.md | 4 ++-- backend/internal/transport/middleware/sentry.go | 10 ++++++++-- mobile/app/build.gradle.kts | 8 +++++++- web/docs/_index.md | 2 +- web/docs/observability.md | 8 +++----- 8 files changed, 27 insertions(+), 14 deletions(-) diff --git a/backend/.env.example b/backend/.env.example index b1be62c..c19c32f 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -10,7 +10,9 @@ BLUEPRINT_DB_SSLMODE=disable REDIS_URL= RATE_LIMIT_RPS=100 # requests/sec per IP (omit or 0 = disabled) RATE_LIMIT_BURST=500 # burst capacity (defaults to RPS×5) -FIREBASE_PROJECT_ID=# Firebase project ID — e.g. my-app-12345 (omit to disable auth) -FIREBASE_SERVICE_ACCOUNT_JSON=# Service account key as a single-line JSON string. Get from: Firebase Console → Project Settings → Service Accounts → Generate new private key +# Firebase project ID — e.g. my-app-12345 (omit to disable auth) +FIREBASE_PROJECT_ID= +# Service account key as a single-line JSON string. Get from: Firebase Console → Project Settings → Service Accounts → Generate new private key +FIREBASE_SERVICE_ACCOUNT_JSON= # Sentry error tracking DSN (optional; leave empty to disable) SENTRY_DSN= \ No newline at end of file diff --git a/backend/docs/bootstrap.md b/backend/docs/bootstrap.md index 6d59bdb..dfd2709 100644 --- a/backend/docs/bootstrap.md +++ b/backend/docs/bootstrap.md @@ -35,6 +35,7 @@ type Config struct { RateLimitBurst int FirebaseProjectID string FirebaseServiceAccountJSON string + SentryDSN string } ``` `loadConfig()` reads all values from environment variables. `PORT` defaults to `8080`; `BLUEPRINT_DB_SCHEMA` defaults to `public`; `BLUEPRINT_DB_SSLMODE` defaults to `disable`. `RateLimitBurst` is derived as `int(RPS)*5` when omitted and RPS is set. Optional fields (`RedisURL`, `FirebaseProjectID`, `FirebaseServiceAccountJSON`) default to empty string — their respective services are skipped when empty. diff --git a/backend/docs/observability.md b/backend/docs/observability.md index d073bb9..d904e83 100644 --- a/backend/docs/observability.md +++ b/backend/docs/observability.md @@ -63,7 +63,7 @@ Stored on `Config.SentryDSN`. Not validated — an empty value disables Sentry w ## Supplying the DSN **Local development** — add to `backend/.env`: -``` +```dotenv SENTRY_DSN=https://@o.ingest.sentry.io/ ``` diff --git a/backend/docs/routing.md b/backend/docs/routing.md index 199b754..dc4221d 100644 --- a/backend/docs/routing.md +++ b/backend/docs/routing.md @@ -38,7 +38,7 @@ h := handlers.NewHandler(healthUC) return &http.Server{ Addr: fmt.Sprintf(":%d", app.Config.Port), - Handler: h.RegisterRoutes(app.Config.RateLimitRPS, app.Config.RateLimitBurst, app.Firebase), + Handler: h.RegisterRoutes(app.Config.RateLimitRPS, app.Config.RateLimitBurst, app.Firebase, app.Config.SentryDSN), IdleTimeout: time.Minute, ReadTimeout: 10 * time.Second, WriteTimeout: 30 * time.Second, @@ -51,7 +51,7 @@ All routes registered in `RegisterRoutes()` on `*Handler`, which returns `http.H `verifier` is a `usecase.FirebaseTokenVerifier`; pass `nil` to skip Firebase auth (development only — see [auth](auth.md)). ```go -func (h *Handler) RegisterRoutes(rps float64, burst int, verifier usecase.FirebaseTokenVerifier) http.Handler { +func (h *Handler) RegisterRoutes(rps float64, burst int, verifier usecase.FirebaseTokenVerifier, sentryDSN string) http.Handler { r := gin.New() // Gin's colorful logger locally; structured slog logger in staging/production. diff --git a/backend/internal/transport/middleware/sentry.go b/backend/internal/transport/middleware/sentry.go index 2dfacbf..a0d17b9 100644 --- a/backend/internal/transport/middleware/sentry.go +++ b/backend/internal/transport/middleware/sentry.go @@ -1,18 +1,24 @@ package middleware import ( + "log/slog" + sentry "github.com/getsentry/sentry-go" sentrygin "github.com/getsentry/sentry-go/gin" - "github.com/gin-gonic/gin" ) // SentryMiddleware reports panics and errors to Sentry. // When dsn is empty it returns a no-op handler so the app works without Sentry configured. +// When dsn is set but sentry.Init fails (e.g. malformed DSN), the error is logged and +// the middleware degrades to a no-op rather than preventing startup. func SentryMiddleware(dsn string) gin.HandlerFunc { if dsn == "" { return func(c *gin.Context) { c.Next() } } - _ = sentry.Init(sentry.ClientOptions{Dsn: dsn}) + if err := sentry.Init(sentry.ClientOptions{Dsn: dsn}); err != nil { + slog.Default().Warn("sentry: init failed, continuing without error tracking", "error", err) + return func(c *gin.Context) { c.Next() } + } return sentrygin.New(sentrygin.Options{Repanic: true}) } diff --git a/mobile/app/build.gradle.kts b/mobile/app/build.gradle.kts index 41d72a4..18ff9b9 100644 --- a/mobile/app/build.gradle.kts +++ b/mobile/app/build.gradle.kts @@ -1,8 +1,14 @@ +import java.util.Properties + plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.compose) } +val localProps = Properties().apply { + rootProject.file("local.properties").takeIf { it.exists() }?.inputStream()?.use { load(it) } +} + android { namespace = "com.company.template" compileSdk { @@ -19,7 +25,7 @@ android { versionName = "1.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - buildConfigField("String", "SENTRY_DSN", "\"\"") + buildConfigField("String", "SENTRY_DSN", "\"${localProps.getProperty("SENTRY_DSN", "")}\"") } buildTypes { diff --git a/web/docs/_index.md b/web/docs/_index.md index 5f65cdf..eb6109a 100644 --- a/web/docs/_index.md +++ b/web/docs/_index.md @@ -10,4 +10,4 @@ The `docs` agent reads this index first to locate the right file. | Styling with Tailwind CSS v4 | [styling.md](styling.md) | `app/globals.css`, `postcss.config.mjs` | | Component conventions | [components.md](components.md) | `app/` (all component files) | | Testing patterns | [testing.md](testing.md) | `vitest.config.ts`, `vitest.setup.ts`, `__tests__/page.test.tsx` | -| Observability (Sentry error tracking) | [observability.md](observability.md) | `sentry.client.config.ts`, `sentry.server.config.ts`, `sentry.edge.config.ts`, `next.config.ts`, `.env.local.example` | +| Observability (Sentry error tracking) | [observability.md](observability.md) | `sentry.client.config.ts`, `sentry.server.config.ts`, `sentry.edge.config.ts`, `next.config.ts`, `.env.example` | diff --git a/web/docs/observability.md b/web/docs/observability.md index 0e43fb4..d98731c 100644 --- a/web/docs/observability.md +++ b/web/docs/observability.md @@ -6,7 +6,7 @@ sources: - sentry.server.config.ts - sentry.edge.config.ts - next.config.ts - - .env.local.example + - .env.example --- # Observability @@ -45,13 +45,11 @@ Next.js picks up each file automatically based on the execution context — no m The `NEXT_PUBLIC_` prefix makes the value available in the browser bundle. When the variable is absent or empty, `Sentry.init` is a no-op. -**Local development** — add to `web/.env.local`: -``` +**Local development** — copy `web/.env.example` to `web/.env.local` and fill in your DSN: +```dotenv NEXT_PUBLIC_SENTRY_DSN=https://@o.ingest.sentry.io/ ``` -`web/.env.local.example` documents this with an empty placeholder. - **Production** — set `NEXT_PUBLIC_SENTRY_DSN` in your deployment platform's environment configuration. ## next.config.ts — withSentryConfig