diff --git a/.cursor/rules/dyolink-overview.mdc b/.cursor/rules/dyolink-overview.mdc index 9f32ca5..9b21daf 100644 --- a/.cursor/rules/dyolink-overview.mdc +++ b/.cursor/rules/dyolink-overview.mdc @@ -40,7 +40,7 @@ QR + URL for **sent** cases; focus page `/lab-case/[token]`. Auth redirect via ` ## API errors -Logical failures: `AppException(ErrorCode.X)` → `errors.X` in en/fa/nl. UI: `getUserFacingError`. Skill: `.cursor/skills/api-errors/SKILL.md`. Appointments/working hours: client IANA `timeZone` (never Node local `getHours()`). +Logical failures: `AppException(ErrorCode.X)` → `errors.X` in en/fa/nl. UI: `getUserFacingError`. Unexpected 500s / client crashes: GlitchTip via Sentry SDKs (`SENTRY_DSN` / `NEXT_PUBLIC_SENTRY_DSN`). Skill: `.cursor/skills/api-errors/SKILL.md`. Appointments/working hours: client IANA `timeZone` (never Node local `getHours()`). ## Notifications (inbox + live tabs) diff --git a/.cursor/rules/error-tracking.mdc b/.cursor/rules/error-tracking.mdc new file mode 100644 index 0000000..569c29f --- /dev/null +++ b/.cursor/rules/error-tracking.mdc @@ -0,0 +1,20 @@ +--- +description: GlitchTip error tracking via Sentry SDKs — 500s only, no PHI +alwaysApply: true +--- + +# Error tracking (GlitchTip) + +Self-hosted GlitchTip at `https://errors.wixur.ir`. Apps use official Sentry SDKs (`@sentry/nestjs`, `@sentry/nextjs`) pointed at that DSN — not sentry.io. + +## What to report + +- Backend: HTTP **500+** in `HttpExceptionFilter` only. Do **not** send `AppException` 4xx. +- Frontend: render crashes (`error.tsx` / `global-error.tsx`) and axios **5xx / network** failures only. +- No session replay. Scrub cookies, JWT, request bodies, emails. + +## Config + +- Backend DSN: `SENTRY_DSN` in `backend.env` / `backend.staging.env` (runtime). +- Frontend DSN: `NEXT_PUBLIC_SENTRY_DSN` baked at Docker build (Gitea var `NEXT_PUBLIC_SENTRY_DSN`). +- Empty DSN = tracking off (local default). diff --git a/.gitea/workflows/prod-tag-deploy.yml b/.gitea/workflows/prod-tag-deploy.yml new file mode 100644 index 0000000..7828559 --- /dev/null +++ b/.gitea/workflows/prod-tag-deploy.yml @@ -0,0 +1,221 @@ +# Production: build images for https://nudentic.ir, push :v* to Gitea (not :latest), +# SCP compose to Linux, docker login wixur.ir:3000, compose pull + up. +# +# Triggers: git tag v1.0.1 (and v*.*.*), or Actions → this workflow → Run (input tag). +# Staging stays on master: .gitea/workflows/registry-build-deploy.yml +# +# Do NOT push :latest — staging already uses :latest with the wixur.ir frontend bake. +# +# Variables: +# REGISTRY_HOST Windows Docker push host, e.g. host.docker.internal:3000 +# REGISTRY_OWNER Gitea user/org for packages +# CLONE_HOST git clone, e.g. 127.0.0.1:3000 +# PROD_PUBLIC_BASE_URL https://nudentic.ir (no trailing slash) +# NEXT_PUBLIC_SENTRY_DSN GlitchTip frontend project DSN +# PROD_REGISTRY_HOST Linux pull host, e.g. wixur.ir:3000 (HTTP; insecure-registries on the VPS) +# PROD_INFRA_DIR /opt/dyolink/infrastructure +# +# Secrets (in addition to REGISTRY_USERNAME / REGISTRY_PASSWORD): +# PROD_SSH_HOST Linux public IP or hostname +# PROD_SSH_USER e.g. dyolink +# PROD_SSH_KEY OpenSSH private key (full PEM) +# PROD_SSH_PORT optional, default 22 +# +# One-time on Linux: insecure-registries ["wixur.ir:3000"], .env REGISTRY_PREFIX + DOMAIN=nudentic.ir +# See infrastructure/DEPLOY.md + +name: Production — tag build, push, deploy + +on: + push: + tags: + - 'v*.*.*' + workflow_dispatch: + inputs: + tag: + description: 'Release tag to build and deploy (e.g. v1.0.1)' + required: true + +defaults: + run: + shell: powershell + +jobs: + build-and-push: + runs-on: windows + outputs: + image_tag: ${{ steps.meta.outputs.image_tag }} + steps: + - name: Checkout (this Gitea) + run: | + $ErrorActionPreference = 'Stop' + $cloneHost = '${{ vars.CLONE_HOST }}'.Trim() + if ([string]::IsNullOrWhiteSpace($cloneHost)) { + $Server = "${{ github.server_url }}".TrimEnd('/') + } elseif ($cloneHost -match '^https?://') { + $Server = $cloneHost.TrimEnd('/') + } else { + $Server = 'http://' + $cloneHost + } + $Repo = "${{ github.repository }}" + $dispatchTag = '${{ github.event.inputs.tag }}'.Trim() + if (-not [string]::IsNullOrWhiteSpace($dispatchTag)) { + $Branch = $dispatchTag + } else { + $Branch = "${{ github.ref_name }}" + } + $Token = "${{ github.token }}" + $Actor = "${{ github.actor }}" + $hp = $Server -replace '^https?://', '' + if ($Server.StartsWith('https')) { + $cloneUrl = 'https://' + $Actor + ':' + $Token + '@' + $hp + '/' + $Repo + '.git' + } else { + $cloneUrl = 'http://' + $Actor + ':' + $Token + '@' + $hp + '/' + $Repo + '.git' + } + $env:GIT_TERMINAL_PROMPT = '0' + git clone --depth 1 --branch $Branch $cloneUrl . + + - name: Resolve image tag (v*.*.* only) + id: meta + run: | + $ErrorActionPreference = 'Stop' + $dispatchTag = '${{ github.event.inputs.tag }}'.Trim() + if (-not [string]::IsNullOrWhiteSpace($dispatchTag)) { + $tag = $dispatchTag + } else { + $tag = "${{ github.ref_name }}" + } + if ($tag -notmatch '^v\d+\.\d+\.\d+') { + Write-Host "Production images must be tagged vMAJOR.MINOR.PATCH (got: $tag)" + exit 1 + } + $utf8 = New-Object System.Text.UTF8Encoding $false + [System.IO.File]::AppendAllText($env:GITHUB_OUTPUT, "image_tag=$tag`n", $utf8) + $prefix = "${{ vars.REGISTRY_HOST }}/${{ vars.REGISTRY_OWNER }}" + [System.IO.File]::AppendAllText($env:GITHUB_ENV, "REGISTRY_PREFIX=$prefix`n", $utf8) + Write-Host "image_tag=$tag REGISTRY_PREFIX=$prefix" + + - name: Log in to container registry + run: | + $ErrorActionPreference = 'Stop' + $pass = @' + ${{ secrets.REGISTRY_PASSWORD }} + '@ + $pass.Trim() | docker login "${{ vars.REGISTRY_HOST }}" -u "${{ secrets.REGISTRY_USERNAME }}" --password-stdin + + - name: Build and push backend (tag only, not :latest) + run: | + $ErrorActionPreference = 'Stop' + $tag = "${{ steps.meta.outputs.image_tag }}" + docker build -t "$env:REGISTRY_PREFIX/dyolink-backend:$tag" ./backend + docker push "$env:REGISTRY_PREFIX/dyolink-backend:$tag" + + - name: Build and push frontend (nudentic.ir baked in) + env: + PROD_PUBLIC_BASE_URL: ${{ vars.PROD_PUBLIC_BASE_URL }} + NEXT_PUBLIC_SENTRY_DSN: ${{ vars.NEXT_PUBLIC_SENTRY_DSN }} + run: | + $ErrorActionPreference = 'Stop' + $tag = "${{ steps.meta.outputs.image_tag }}" + $base = $env:PROD_PUBLIC_BASE_URL.Trim() + if ([string]::IsNullOrWhiteSpace($base)) { $base = 'https://nudentic.ir' } + $base = $base.TrimEnd('/') + docker build ` + --build-arg "NEXT_PUBLIC_API_URL=$base/api" ` + --build-arg "NEXT_PUBLIC_APP_URL=$base" ` + --build-arg "NEXT_PUBLIC_APP_NAME=Dyolink" ` + --build-arg "NEXT_PUBLIC_SENTRY_DSN=$env:NEXT_PUBLIC_SENTRY_DSN" ` + --build-arg "NEXT_PUBLIC_SENTRY_ENVIRONMENT=production" ` + -t "$env:REGISTRY_PREFIX/dyolink-frontend:$tag" ` + ./frontend + docker push "$env:REGISTRY_PREFIX/dyolink-frontend:$tag" + + deploy: + needs: build-and-push + runs-on: windows + steps: + - name: Checkout (tagged tree for compose file) + run: | + $ErrorActionPreference = 'Stop' + $cloneHost = '${{ vars.CLONE_HOST }}'.Trim() + if ([string]::IsNullOrWhiteSpace($cloneHost)) { + $Server = "${{ github.server_url }}".TrimEnd('/') + } elseif ($cloneHost -match '^https?://') { + $Server = $cloneHost.TrimEnd('/') + } else { + $Server = 'http://' + $cloneHost + } + $Repo = "${{ github.repository }}" + $dispatchTag = '${{ github.event.inputs.tag }}'.Trim() + if (-not [string]::IsNullOrWhiteSpace($dispatchTag)) { + $Branch = $dispatchTag + } else { + $Branch = "${{ github.ref_name }}" + } + $Token = "${{ github.token }}" + $Actor = "${{ github.actor }}" + $hp = $Server -replace '^https?://', '' + if ($Server.StartsWith('https')) { + $cloneUrl = 'https://' + $Actor + ':' + $Token + '@' + $hp + '/' + $Repo + '.git' + } else { + $cloneUrl = 'http://' + $Actor + ':' + $Token + '@' + $hp + '/' + $Repo + '.git' + } + $env:GIT_TERMINAL_PROMPT = '0' + git clone --depth 1 --branch $Branch $cloneUrl . + + - name: Write SSH key + run: | + $ErrorActionPreference = 'Stop' + $raw = @' + ${{ secrets.PROD_SSH_KEY }} + '@ + $key = $raw.Trim() + if ($key -notmatch 'BEGIN') { + Write-Host "Set secret PROD_SSH_KEY to an OpenSSH private key" + exit 1 + } + $keyPath = Join-Path $env:RUNNER_TEMP 'prod_ssh' + $normalized = $key.Replace("`r`n", "`n").TrimEnd() + "`n" + [System.IO.File]::WriteAllText($keyPath, $normalized) + icacls $keyPath /inheritance:r | Out-Null + icacls $keyPath /grant:r "$($env:USERNAME):(R)" | Out-Null + [System.IO.File]::AppendAllText($env:GITHUB_ENV, "PROD_SSH_KEY_PATH=$keyPath`n") + + - name: Copy compose + deploy script to Linux + run: | + $ErrorActionPreference = 'Stop' + $hostName = '${{ secrets.PROD_SSH_HOST }}'.Trim() + $user = '${{ secrets.PROD_SSH_USER }}'.Trim() + $port = '${{ secrets.PROD_SSH_PORT }}'.Trim() + if ([string]::IsNullOrWhiteSpace($port)) { $port = '22' } + $infra = '${{ vars.PROD_INFRA_DIR }}'.Trim() + if ([string]::IsNullOrWhiteSpace($infra)) { $infra = '/opt/dyolink/infrastructure' } + $ssh = @('-i', $env:PROD_SSH_KEY_PATH, '-o', 'StrictHostKeyChecking=accept-new') + ssh.exe @ssh -p $port "${user}@${hostName}" "mkdir -p $infra/scripts" + scp.exe @ssh -P $port ` + infrastructure/docker-compose.prod.yml ` + "${user}@${hostName}:${infra}/docker-compose.prod.yml" + scp.exe @ssh -P $port ` + infrastructure/scripts/prod-remote-deploy.sh ` + "${user}@${hostName}:${infra}/scripts/prod-remote-deploy.sh" + ssh.exe @ssh -p $port "${user}@${hostName}" "chmod +x $infra/scripts/prod-remote-deploy.sh" + + - name: Login on Linux and deploy tag + run: | + $ErrorActionPreference = 'Stop' + $hostName = '${{ secrets.PROD_SSH_HOST }}'.Trim() + $user = '${{ secrets.PROD_SSH_USER }}'.Trim() + $port = '${{ secrets.PROD_SSH_PORT }}'.Trim() + if ([string]::IsNullOrWhiteSpace($port)) { $port = '22' } + $infra = '${{ vars.PROD_INFRA_DIR }}'.Trim() + if ([string]::IsNullOrWhiteSpace($infra)) { $infra = '/opt/dyolink/infrastructure' } + $regHost = '${{ vars.PROD_REGISTRY_HOST }}'.Trim() + if ([string]::IsNullOrWhiteSpace($regHost)) { $regHost = 'wixur.ir:3000' } + $tag = "${{ needs.build-and-push.outputs.image_tag }}" + $pass = @' + ${{ secrets.REGISTRY_PASSWORD }} + '@ + $regUser = '${{ secrets.REGISTRY_USERNAME }}' + $ssh = @('-i', $env:PROD_SSH_KEY_PATH, '-o', 'StrictHostKeyChecking=accept-new') + $remote = "docker login $regHost -u $regUser --password-stdin && PROD_INFRA_DIR=$infra $infra/scripts/prod-remote-deploy.sh $tag" + $pass.Trim() | ssh.exe @ssh -p $port "${user}@${hostName}" $remote diff --git a/.gitea/workflows/registry-build-deploy.yml b/.gitea/workflows/registry-build-deploy.yml index 84e81a1..a14c67f 100644 --- a/.gitea/workflows/registry-build-deploy.yml +++ b/.gitea/workflows/registry-build-deploy.yml @@ -1,31 +1,33 @@ # Staging: build backend/frontend images, push to Gitea Container Registry, deploy on self-hosted runner. # # Triggers: push to master/main, or manual workflow_dispatch. +# Production (nudentic.ir / git tags): .gitea/workflows/prod-tag-deploy.yml — this file is Windows staging only. # # Repository Variables (Settings → Actions → Variables): -# REGISTRY_HOST Docker registry host:port (no http/https). Same PC as Gitea → 127.0.0.1:3000 +# REGISTRY_HOST Docker registry host:port (no http/https). Windows Docker Desktop → host.docker.internal:3000 # REGISTRY_OWNER Gitea user or org that owns the packages -# PUBLIC_BASE_URL URL users sopen in browser, e.g. http://178.131.50.201:8088 (no trailing slash) +# PUBLIC_BASE_URL URL users open in the browser, e.g. http://wixur.ir (no trailing slash, no :8088) +# NEXT_PUBLIC_SENTRY_DSN GlitchTip frontend project DSN (https://…@errors.wixur.ir/…) # DEPLOY_SECRETS_DIR absolute path on runner, e.g. C:/dyolink/secrets # # Optional: -# STAGING_HTTP_PORT public port (default 8088) — Windows portproxy listens here -# STAGING_LOCAL_PORT Docker bind on 127.0.0.1 (default 18088) — must not equal 8088 if portproxy owns 8088 +# STAGING_HTTP_PORT public HTTP port (default 80) — Windows portproxy listens here → 18088 +# STAGING_LOCAL_PORT Docker bind on 127.0.0.1 (default 18088) — must not equal the public port if portproxy owns it # CLONE_HOST git clone host when runner = Gitea host → 127.0.0.1:3000 # # Same Windows PC runs Gitea + runner + deploy: # CLONE_HOST → 127.0.0.1:3000 (git runs on Windows host) # REGISTRY_HOST → host.docker.internal:3000 (docker commands run inside Docker Desktop VM) -# Gitea app.ini ROOT_URL → http://host.docker.internal:3000/ -# PUBLIC_BASE_URL → public IP:8088 (browser URL for staging app) +# Gitea app.ini ROOT_URL → http://host.docker.internal:3000/ (Docker registry auth; browsers use http://wixur.ir:3000) +# PUBLIC_BASE_URL → http://wixur.ir (staging app; Gitea stays on :3000) # -# Add host.docker.internal:3000 to Docker Desktop insecure-registries. +# Add host.docker.internal:3000 (and wixur.ir:3000 if you pull by public hostname) to Docker Desktop insecure-registries. # # Repository Secrets (Settings → Actions → Secrets): # REGISTRY_USERNAME Gitea username for docker login # REGISTRY_PASSWORD Gitea access token (packages:read/write) or account password # -# Docker on runner: add registry hosts to insecure-registries, e.g. ["127.0.0.1:3000","178.131.50.201:3000"] +# Docker on runner: insecure-registries e.g. ["host.docker.internal:3000","wixur.ir:3000"] # # Runner: self-hosted with Docker + git. Default shell is powershell (Windows act_runner). @@ -102,6 +104,7 @@ jobs: - name: Build and push frontend env: PUBLIC_BASE_URL: ${{ vars.PUBLIC_BASE_URL }} + NEXT_PUBLIC_SENTRY_DSN: ${{ vars.NEXT_PUBLIC_SENTRY_DSN }} run: | $ErrorActionPreference = 'Stop' $tag = "${{ steps.meta.outputs.image_tag }}" @@ -110,6 +113,8 @@ jobs: --build-arg "NEXT_PUBLIC_API_URL=$base/api" ` --build-arg "NEXT_PUBLIC_APP_URL=$base" ` --build-arg "NEXT_PUBLIC_APP_NAME=Dyolink" ` + --build-arg "NEXT_PUBLIC_SENTRY_DSN=$env:NEXT_PUBLIC_SENTRY_DSN" ` + --build-arg "NEXT_PUBLIC_SENTRY_ENVIRONMENT=staging" ` -t "$env:REGISTRY_PREFIX/dyolink-frontend:$tag" ` -t "$env:REGISTRY_PREFIX/dyolink-frontend:latest" ` ./frontend @@ -162,7 +167,7 @@ jobs: exit 1 } $stagingPort = '${{ vars.STAGING_HTTP_PORT }}'.Trim() - if ([string]::IsNullOrWhiteSpace($stagingPort)) { $stagingPort = '8088' } + if ([string]::IsNullOrWhiteSpace($stagingPort)) { $stagingPort = '80' } $localPort = '${{ vars.STAGING_LOCAL_PORT }}'.Trim() if ([string]::IsNullOrWhiteSpace($localPort)) { $localPort = '18088' } $imageTag = "${{ needs.build-and-push.outputs.image_tag }}" diff --git a/AGENTS.md b/AGENTS.md index 8236b0f..048e3f7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,7 +10,7 @@ Dental clinic ↔ lab platform (monorepo): |------|--------| | `backend/` | NestJS, Prisma, PostgreSQL | | `frontend/` | Next.js 16, React 19, next-intl, Tailwind | -| `infrastructure/` | Docker, nginx, deploy scripts — prod: `DEPLOY.md`, staging: `STAGING-DEPLOY.md` | +| `infrastructure/` | Docker — staging `https://wixur.ir` (Windows, `STAGING-DEPLOY.md`); prod `https://nudentic.ir` (Linux, `DEPLOY.md`) | **Organization types:** `CLINIC` (patients, appointments, treatment) and `LAB` (cases, tasks). Many features are org-type-specific. Permissions use `TAB_*_READ` / `TAB_*_EDIT` codes — see `backend/src/common/permissions.ts`. @@ -85,7 +85,7 @@ backend/src/ prisma/ → schema, migrations, seed ``` -Errors: `AppException` + `ErrorCode` → frontend `getUserFacingError()`. Never throw raw strings for user-facing failures. +Errors: `AppException` + `ErrorCode` → frontend `getUserFacingError()`. Unexpected 500s: GlitchTip (`SENTRY_DSN`). Never throw raw strings for user-facing failures. ## Git & commits diff --git a/README.md b/README.md index 2302c2f..a5c49a7 100644 --- a/README.md +++ b/README.md @@ -8,45 +8,21 @@ Local development: see **`backend/README.md`** and **`frontend/README.md`**. --- -## Production deploy (Docker Hub + HTTPS + Let's Encrypt) +## Production deploy (`https://nudentic.ir`) -**Full step-by-step guide:** [`infrastructure/DEPLOY.md`](infrastructure/DEPLOY.md) +**Full guide:** [`infrastructure/DEPLOY.md`](infrastructure/DEPLOY.md) -Minimal server setup: install Docker, create `.env` + `secrets/`, `docker login`, run one script. +Git tag **`v1.0.1`** → Gitea workflow [`.gitea/workflows/prod-tag-deploy.yml`](.gitea/workflows/prod-tag-deploy.yml) builds images (URLs baked for nudentic.ir), pushes to Gitea registry, SSH-deploys the Linux stack. -| On server (once) | In repo / Docker | -|------------------|------------------| -| DNS A record → server IP | `docker-compose.prod.yml`, nginx, certbot | -| `docker login` (private Hub) | Build & push images from dev machine | -| `secrets/database.env`, `secrets/backend.env` | Examples: `database.prod.env.example`, `backend.prod.env.example` | -| `infrastructure/.env` (`DOMAIN`, `LETSENCRYPT_EMAIL`) | `deploy.prod.env.example` | - -**Dev machine** — build frontend with the public domain baked in, push to Docker Hub: - -```bash -./infrastructure/scripts/build-and-push-prod.sh wixur.ir latest -``` - -**Server** — from `infrastructure/`: - -```bash -cp deploy.prod.env.example .env # edit DOMAIN, paths -mkdir -p ../secrets && cp database.prod.env.example ../secrets/database.env -cp backend.prod.env.example ../secrets/backend.env # set passwords + FRONTEND_URL -docker login -chmod +x scripts/*.sh -./scripts/deploy-prod.sh -``` - -SSL is issued automatically via **Certbot** (`scripts/init-letsencrypt.sh`). Nginx config is generated from `DOMAIN` in `.env`. When you move to another domain (e.g. `dyolink.com`), update `.env` + `backend.env`, re-run `init-letsencrypt.sh`, and **rebuild the frontend image** with the new URL. +First-time SSL / Hub fallback: same `DEPLOY.md`. --- -## Staging deploy (Gitea Actions + Windows) +## Staging deploy (`https://wixur.ir`) **Full guide:** [`infrastructure/STAGING-DEPLOY.md`](infrastructure/STAGING-DEPLOY.md) -Merge or push to **`master`** → Gitea Actions builds images → deploys to `http://:8088`. +Merge or push to **`master`** → Gitea Actions builds images → deploys to `https://wixur.ir` (Gitea: `http://wixur.ir:3000`). Workflow: [`.gitea/workflows/registry-build-deploy.yml`](.gitea/workflows/registry-build-deploy.yml) diff --git a/backend/.env.example b/backend/.env.example index d387d78..c59f8f7 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -72,3 +72,7 @@ OPENROUTER_API_KEY= # plus processing takes ten seconds at minimum — so it is purely an abuse guard. # VOICE_THROTTLE_TTL=60 # VOICE_THROTTLE_LIMIT=6 + +# GlitchTip (optional). Leave empty for local unless you want to send events. +# SENTRY_DSN=https://PUBLIC_KEY@errors.wixur.ir/1 +# SENTRY_ENVIRONMENT=development diff --git a/backend/package-lock.json b/backend/package-lock.json index e4c3c85..e90ab23 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -25,6 +25,7 @@ "@nestjs/throttler": "^6.5.0", "@nestjs/websockets": "^11.1.28", "@prisma/client": "^6.19.2", + "@sentry/nestjs": "^10.72.0", "adminjs": "^7.8.17", "axios": "^1.13.5", "bcrypt": "^6.0.0", @@ -4649,6 +4650,119 @@ "npm": ">=5.10.0" } }, + "node_modules/@opentelemetry/api": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", + "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/api-logs": { + "version": "0.220.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.220.0.tgz", + "integrity": "sha512-CmVa4ImJ+ynfrPMNaAXHET6Bhb44SwzmfyVJFq9ni2jgXJR/l7C6gfVFddNmHP+ZOkP9cf4f9DBe68qVLTHc9w==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/core": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.10.0.tgz", + "integrity": "sha512-/wNZ8twnEQQA4HoHu22+vcsdru6pWPWxW+7w+FlxT6Id7PE/WIbZmVKkte+PF72e0F2dnImFeHD2syyE1Mw6MQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/instrumentation": { + "version": "0.220.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.220.0.tgz", + "integrity": "sha512-xQx3E2WxP1mDvKzxLxX+CTCtNLa560YJZ3087qYHerl2YmiKpv7AH+dAy7vmx+eVrZ5BwhfWUAVoKOoxCNHcpw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.220.0", + "import-in-the-middle": "^3.0.0", + "require-in-the-middle": "^8.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/resources": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.10.0.tgz", + "integrity": "sha512-q6MMm2zhggzsHVNbabYwut+a6nbuQQe3URUoxaojM/8K1IBfwwPzvxIjNi2/lI1TFe+fMHMW9MWhrtDLEXEnkA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.10.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace/-/sdk-trace-2.10.0.tgz", + "integrity": "sha512-MfQGq3GRmTh5fM/y+OjaO0vj6+luCB1XO2gfXCalKCfgKw0eHL++sm75DNweC6ohlp+aFvACqeE0fYayqdRaoQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.10.0", + "@opentelemetry/resources": "2.10.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.10.0.tgz", + "integrity": "sha512-GuYQQT7QD2EeO8lcZLRQzcbOyhqAzL+6WWTKTU9mSUBYBazkEDl+VrQcXQhbB08OWM9anD1aHleVadzulpOaUQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.10.0", + "@opentelemetry/resources": "2.10.0", + "@opentelemetry/sdk-trace": "2.10.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.43.0.tgz", + "integrity": "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, "node_modules/@paralleldrive/cuid2": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", @@ -5333,6 +5447,138 @@ "hasInstallScript": true, "license": "Apache-2.0" }, + "node_modules/@sentry/conventions": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@sentry/conventions/-/conventions-0.16.0.tgz", + "integrity": "sha512-fO9PLmHdVURcSPUpWCItWAtgKiMwGdJHbovoSEyLplX5sxs2ugvI4CBPTrkkgqhObnZOD0CnWBKDzSVQYBKEyQ==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@sentry/core": { + "version": "10.72.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.72.0.tgz", + "integrity": "sha512-UJMHZfbjP4qk+g4AQhZmzosMdICC2D9p0/hLrm1LofPsp+WBfcSnv9jsY1a9TfmpS4WCAmTx8nANYTIXifcBjA==", + "license": "MIT", + "dependencies": { + "@sentry/conventions": "^0.16.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/nestjs": { + "version": "10.72.0", + "resolved": "https://registry.npmjs.org/@sentry/nestjs/-/nestjs-10.72.0.tgz", + "integrity": "sha512-+1wdX+fkv8zIZ+lI66Se1DKwPXoa6edxoHuAgWDwcqv3bKF8R75NqzXj+4U7FcgBI05i6HFiZJuyvx9BCECy8g==", + "license": "MIT", + "dependencies": { + "@opentelemetry/api": "^1.9.1", + "@opentelemetry/instrumentation": "^0.220.0", + "@sentry/conventions": "^0.16.0", + "@sentry/core": "10.72.0", + "@sentry/node": "10.72.0", + "@sentry/server-utils": "10.72.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", + "@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0" + } + }, + "node_modules/@sentry/node": { + "version": "10.72.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-10.72.0.tgz", + "integrity": "sha512-eQHQFxSX26MhG/nB+tAY4QRslnPvJse7pww/hd3zXAqNULRa5lFtjSLALcHx/KUVDCZdTPdUVZEj4nGidcHrow==", + "license": "MIT", + "dependencies": { + "@opentelemetry/api": "^1.9.1", + "@opentelemetry/instrumentation": "^0.220.0", + "@opentelemetry/sdk-trace-base": "^2.9.0", + "@sentry/conventions": "^0.16.0", + "@sentry/core": "10.72.0", + "@sentry/node-core": "10.72.0", + "@sentry/opentelemetry": "10.72.0", + "@sentry/server-utils": "10.72.0", + "import-in-the-middle": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/node-core": { + "version": "10.72.0", + "resolved": "https://registry.npmjs.org/@sentry/node-core/-/node-core-10.72.0.tgz", + "integrity": "sha512-xYuYWmWEWnN8h3YHa7mds212ANFgrxfrbmLvVg0p0wf9m6MFjLowOTmjaiK0XW20sM/veSelicre650bx8UFtQ==", + "license": "MIT", + "dependencies": { + "@sentry/conventions": "^0.16.0", + "@sentry/core": "10.72.0", + "@sentry/opentelemetry": "10.72.0", + "import-in-the-middle": "^3.0.0" + }, + "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" + }, + "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 + } + } + }, + "node_modules/@sentry/opentelemetry": { + "version": "10.72.0", + "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-10.72.0.tgz", + "integrity": "sha512-ZVbAM1rGU/awN7cH/jvC87WpQw0NOJx5id6dKnnefStXpP/kUnZXYhtX9gfBnofYvJWa5I5VUSeuKCLUcKL75w==", + "license": "MIT", + "dependencies": { + "@sentry/conventions": "^0.16.0", + "@sentry/core": "10.72.0" + }, + "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" + } + }, + "node_modules/@sentry/server-utils": { + "version": "10.72.0", + "resolved": "https://registry.npmjs.org/@sentry/server-utils/-/server-utils-10.72.0.tgz", + "integrity": "sha512-CWpHMYW81RDBSVBdnxulGUvvBhkBb/OpSr72ubSe1UkKTcgT0NDMCWxE3dLFXqSxzT4DaF5UlUVv9ii5Sse53w==", + "license": "MIT", + "dependencies": { + "@sentry/conventions": "^0.16.0", + "@sentry/core": "10.72.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@sinclair/typebox": { "version": "0.34.49", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", @@ -8352,7 +8598,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", - "dev": true, "license": "MIT" }, "node_modules/class-transformer": { @@ -9222,10 +9467,9 @@ } }, "node_modules/es-module-lexer": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", - "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", - "dev": true, + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", "license": "MIT" }, "node_modules/es-object-atoms": { @@ -10649,6 +10893,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/import-in-the-middle": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-3.3.3.tgz", + "integrity": "sha512-AiohS3H80sXO6owEltjGX+glb7qXaDhBoJb9XcQVH4UI207xu/bDLUcadVKp7Qe576reg9yr/PXZjV5qx8gfbA==", + "license": "Apache-2.0", + "dependencies": { + "cjs-module-lexer": "^2.2.0", + "es-module-lexer": "^2.2.0", + "module-details-from-path": "^1.0.4" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/import-local": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", @@ -12517,6 +12775,12 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/module-details-from-path": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", + "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -14205,6 +14469,19 @@ "node": ">=0.10.0" } }, + "node_modules/require-in-the-middle": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-8.0.1.tgz", + "integrity": "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "module-details-from-path": "^1.0.3" + }, + "engines": { + "node": ">=9.3.0 || >=8.10.0 <9.0.0" + } + }, "node_modules/resolve": { "version": "1.22.12", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", diff --git a/backend/package.json b/backend/package.json index 0346056..d58c282 100644 --- a/backend/package.json +++ b/backend/package.json @@ -46,6 +46,7 @@ "@nestjs/throttler": "^6.5.0", "@nestjs/websockets": "^11.1.28", "@prisma/client": "^6.19.2", + "@sentry/nestjs": "^10.72.0", "adminjs": "^7.8.17", "axios": "^1.13.5", "bcrypt": "^6.0.0", diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts index 66e6583..e075d23 100644 --- a/backend/src/app.module.ts +++ b/backend/src/app.module.ts @@ -1,6 +1,7 @@ import { Module } from '@nestjs/common'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { ThrottlerModule } from '@nestjs/throttler'; +import { SentryModule } from '@sentry/nestjs/setup'; import configurations from './configs/configurations'; import { AuthModule } from './modules/auth/auth.module'; import { AppController } from './app.controller'; @@ -25,6 +26,7 @@ import { VoiceModule } from './modules/voice/voice.module'; @Module({ imports: [ + SentryModule.forRoot(), ConfigModule.forRoot({ isGlobal: true, load: [configurations], diff --git a/backend/src/common/errors/http-exception.filter.ts b/backend/src/common/errors/http-exception.filter.ts index a522e73..68c3a80 100644 --- a/backend/src/common/errors/http-exception.filter.ts +++ b/backend/src/common/errors/http-exception.filter.ts @@ -6,6 +6,7 @@ import { HttpStatus, Logger, } from '@nestjs/common'; +import * as Sentry from '@sentry/nestjs'; import type { Response } from 'express'; import { AppException, type AppErrorResponse } from './app.exception'; import { ErrorCode, type ErrorCodeValue } from './error-codes'; @@ -42,6 +43,9 @@ export class HttpExceptionFilter implements ExceptionFilter { this.logger.error( exception instanceof Error ? exception.stack : String(exception), ); + if (Sentry.getClient()) { + Sentry.captureException(exception); + } } response.status(statusCode).json(body); diff --git a/backend/src/instrument.ts b/backend/src/instrument.ts new file mode 100644 index 0000000..4a8c6c2 --- /dev/null +++ b/backend/src/instrument.ts @@ -0,0 +1,36 @@ +import { config } from 'dotenv'; +import * as Sentry from '@sentry/nestjs'; + +config(); + +const dsn = process.env.SENTRY_DSN?.trim(); + +if (dsn) { + Sentry.init({ + dsn, + environment: + process.env.SENTRY_ENVIRONMENT?.trim() || + process.env.NODE_ENV || + 'development', + release: process.env.SENTRY_RELEASE?.trim() || undefined, + sendDefaultPii: false, + tracesSampleRate: 0, + beforeSend(event) { + if (event.request) { + delete event.request.cookies; + delete event.request.data; + if (event.request.headers) { + delete event.request.headers.cookie; + delete event.request.headers.authorization; + delete event.request.headers.Authorization; + } + } + if (event.user) { + delete event.user.email; + delete event.user.ip_address; + delete event.user.username; + } + return event; + }, + }); +} diff --git a/backend/src/main.ts b/backend/src/main.ts index b2ec46b..0ca6b5a 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -1,4 +1,5 @@ // backend/src/main.ts +import './instrument'; import { NestFactory } from '@nestjs/core'; import { urlencoded } from 'express'; import { AppModule } from './app.module'; diff --git a/frontend/.env.example b/frontend/.env.example index 8b36792..110736e 100644 --- a/frontend/.env.example +++ b/frontend/.env.example @@ -5,3 +5,7 @@ NEXT_PUBLIC_API_URL=http://localhost:3000/api NEXT_PUBLIC_APP_NAME=DyoLink # URL where users open the frontend (used for metadata, images, etc.) NEXT_PUBLIC_APP_URL=http://localhost:3001 + +# GlitchTip frontend project DSN (optional locally). Baked into the Docker image in CI. +# NEXT_PUBLIC_SENTRY_DSN=https://PUBLIC_KEY@errors.wixur.ir/2 +# NEXT_PUBLIC_SENTRY_ENVIRONMENT=development diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 62f4fb9..bb5ddab 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -18,12 +18,16 @@ COPY . . ARG NEXT_PUBLIC_API_URL ARG NEXT_PUBLIC_APP_URL ARG NEXT_PUBLIC_APP_NAME +ARG NEXT_PUBLIC_SENTRY_DSN +ARG NEXT_PUBLIC_SENTRY_ENVIRONMENT ENV NEXT_TELEMETRY_DISABLED=1 ENV NODE_ENV=production ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL} ENV NEXT_PUBLIC_APP_URL=${NEXT_PUBLIC_APP_URL} ENV NEXT_PUBLIC_APP_NAME=${NEXT_PUBLIC_APP_NAME} +ENV NEXT_PUBLIC_SENTRY_DSN=${NEXT_PUBLIC_SENTRY_DSN} +ENV NEXT_PUBLIC_SENTRY_ENVIRONMENT=${NEXT_PUBLIC_SENTRY_ENVIRONMENT} RUN npm run build diff --git a/frontend/messages/en.json b/frontend/messages/en.json index f2681c3..c325c48 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -18,6 +18,9 @@ "redirecting": "Redirecting…", "readOnlyAccess": "Read-only access for this organization.", "errorGeneric": "Something went wrong", + "pageErrorTitle": "Something went wrong", + "pageErrorBody": "This page failed to load. You can try again.", + "tryAgain": "Try again", "loadingEllipsis": "Loading...", "search": "Search", "action": "Action", diff --git a/frontend/messages/fa.json b/frontend/messages/fa.json index eb3357a..c5977f4 100644 --- a/frontend/messages/fa.json +++ b/frontend/messages/fa.json @@ -18,6 +18,9 @@ "redirecting": "در حال انتقال...", "readOnlyAccess": "دسترسی فقط خواندنی برای این سازمان.", "errorGeneric": "خطایی رخ داده است", + "pageErrorTitle": "خطایی رخ داده است", + "pageErrorBody": "این صفحه بارگذاری نشد. می‌توانید دوباره تلاش کنید.", + "tryAgain": "تلاش دوباره", "loadingEllipsis": "در حال بارگذاری...", "search": "جستجو", "action": "عملیات", diff --git a/frontend/messages/nl.json b/frontend/messages/nl.json index f5f81b8..291c209 100644 --- a/frontend/messages/nl.json +++ b/frontend/messages/nl.json @@ -18,6 +18,9 @@ "redirecting": "Bezig met doorsturen...", "readOnlyAccess": "Alleen-lezen toegang voor deze organisatie.", "errorGeneric": "Er is iets misgegaan", + "pageErrorTitle": "Er is iets misgegaan", + "pageErrorBody": "Deze pagina kon niet worden geladen. U kunt het opnieuw proberen.", + "tryAgain": "Opnieuw proberen", "loadingEllipsis": "Laden...", "search": "Zoeken", "action": "Actie", diff --git a/frontend/next.config.ts b/frontend/next.config.ts index dc7e89f..c4ebdf5 100644 --- a/frontend/next.config.ts +++ b/frontend/next.config.ts @@ -1,4 +1,5 @@ import type { NextConfig } from 'next'; +import { withSentryConfig } from '@sentry/nextjs'; import createNextIntlPlugin from 'next-intl/plugin'; const withNextIntl = createNextIntlPlugin('./src/i18n/request.ts'); @@ -35,9 +36,15 @@ const nextConfig: NextConfig = { NEXT_PUBLIC_APP_NAME: process.env.NEXT_PUBLIC_APP_NAME, NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL, NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL, + NEXT_PUBLIC_SENTRY_DSN: process.env.NEXT_PUBLIC_SENTRY_DSN, + NEXT_PUBLIC_SENTRY_ENVIRONMENT: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT, }, output: 'standalone', compress: true, }; -export default withNextIntl(nextConfig); +export default withSentryConfig(withNextIntl(nextConfig), { + silent: true, + sourcemaps: { disable: true }, + disableLogger: true, +}); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 3428e26..7b8ab34 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "@hookform/resolvers": "^5.2.2", + "@sentry/nextjs": "^10.72.0", "@tanstack/react-query": "^5.90.21", "axios": "^1.13.6", "js-cookie": "^3.0.5", @@ -54,7 +55,6 @@ "version": "7.29.0", "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.29.0.tgz", "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", @@ -69,7 +69,6 @@ "version": "7.29.0", "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.29.0.tgz", "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -79,7 +78,6 @@ "version": "7.29.0", "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.29.0.tgz", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.29.0", @@ -110,7 +108,6 @@ "version": "7.29.1", "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.29.1.tgz", "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.29.0", @@ -127,7 +124,6 @@ "version": "7.28.6", "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/compat-data": "^7.28.6", @@ -144,7 +140,6 @@ "version": "7.28.0", "resolved": "https://registry.npmmirror.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz", "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -154,7 +149,6 @@ "version": "7.28.6", "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.28.6", @@ -168,7 +162,6 @@ "version": "7.28.6", "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.28.6", @@ -186,7 +179,6 @@ "version": "7.27.1", "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "devOptional": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -196,7 +188,6 @@ "version": "7.28.5", "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "devOptional": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -206,7 +197,6 @@ "version": "7.27.1", "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -216,7 +206,6 @@ "version": "7.28.6", "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.28.6.tgz", "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.28.6", @@ -230,7 +219,6 @@ "version": "7.29.0", "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.0.tgz", "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.29.0" @@ -255,7 +243,6 @@ "version": "7.28.6", "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.28.6.tgz", "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.28.6", @@ -270,7 +257,6 @@ "version": "7.29.0", "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.29.0.tgz", "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.29.0", @@ -289,7 +275,6 @@ "version": "7.29.0", "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.0.tgz", "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -1040,7 +1025,6 @@ "version": "0.3.13", "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -1051,7 +1035,6 @@ "version": "2.3.5", "resolved": "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz", "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -1062,30 +1045,54 @@ "version": "3.1.2", "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" } }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, "node_modules/@napi-rs/wasm-runtime": { "version": "0.2.12", "resolved": "https://registry.npmmirror.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", @@ -1291,6 +1298,119 @@ "node": ">=12.4.0" } }, + "node_modules/@opentelemetry/api": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", + "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/api-logs": { + "version": "0.220.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.220.0.tgz", + "integrity": "sha512-CmVa4ImJ+ynfrPMNaAXHET6Bhb44SwzmfyVJFq9ni2jgXJR/l7C6gfVFddNmHP+ZOkP9cf4f9DBe68qVLTHc9w==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/core": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.10.0.tgz", + "integrity": "sha512-/wNZ8twnEQQA4HoHu22+vcsdru6pWPWxW+7w+FlxT6Id7PE/WIbZmVKkte+PF72e0F2dnImFeHD2syyE1Mw6MQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/instrumentation": { + "version": "0.220.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.220.0.tgz", + "integrity": "sha512-xQx3E2WxP1mDvKzxLxX+CTCtNLa560YJZ3087qYHerl2YmiKpv7AH+dAy7vmx+eVrZ5BwhfWUAVoKOoxCNHcpw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.220.0", + "import-in-the-middle": "^3.0.0", + "require-in-the-middle": "^8.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/resources": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.10.0.tgz", + "integrity": "sha512-q6MMm2zhggzsHVNbabYwut+a6nbuQQe3URUoxaojM/8K1IBfwwPzvxIjNi2/lI1TFe+fMHMW9MWhrtDLEXEnkA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.10.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace/-/sdk-trace-2.10.0.tgz", + "integrity": "sha512-MfQGq3GRmTh5fM/y+OjaO0vj6+luCB1XO2gfXCalKCfgKw0eHL++sm75DNweC6ohlp+aFvACqeE0fYayqdRaoQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.10.0", + "@opentelemetry/resources": "2.10.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.10.0.tgz", + "integrity": "sha512-GuYQQT7QD2EeO8lcZLRQzcbOyhqAzL+6WWTKTU9mSUBYBazkEDl+VrQcXQhbB08OWM9anD1aHleVadzulpOaUQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.10.0", + "@opentelemetry/resources": "2.10.0", + "@opentelemetry/sdk-trace": "2.10.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.43.0.tgz", + "integrity": "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, "node_modules/@parcel/watcher": { "version": "2.5.6", "resolved": "https://registry.npmmirror.com/@parcel/watcher/-/watcher-2.5.6.tgz", @@ -1624,6 +1744,420 @@ } } }, + "node_modules/@rollup/plugin-commonjs": { + "version": "28.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.1.tgz", + "integrity": "sha512-+tNWdlWKbpB3WgBN7ijjYkq9X5uhjmcvyjEght4NmH5fAU++zfQzAJ6wumLS+dNcvwEZhKx2Z+skY8m7v0wGSA==", + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "commondir": "^1.0.1", + "estree-walker": "^2.0.2", + "fdir": "^6.2.0", + "is-reference": "1.2.1", + "magic-string": "^0.30.3", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=16.0.0 || 14 >= 14.17" + }, + "peerDependencies": { + "rollup": "^2.68.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-commonjs/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-commonjs/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.4.0.tgz", + "integrity": "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.1.tgz", + "integrity": "sha512-UZ8sUxPTiHWYX9QNdJedb1kDZSpS1t/VPWBWGSgqHNi9w3Cu6IXvu2mzbhiTiPvtrqgTQJ+zqiAq2iPIPilpaQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.1.tgz", + "integrity": "sha512-cQ4nFQABN5cDvDpbvJ7bMStCpnaVxynZrRMfUJYgxcIk9Sh54FIO1vtfkg0B69REjER77ioZ/ov+eAApx/KmLQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.1.tgz", + "integrity": "sha512-FQNqd1lRy/0QhDk3xeRIkSBiCpXCiDnZO3YLVdcDKN1UBiKToNftCzcXYNLshmPDUMlu2TdeS8tGcsU6f3YF1Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.1.tgz", + "integrity": "sha512-pvD16V939D3CloK0+qikpGaxiPrDUXTe7Y5cWOMkMSy7m1cawa8EGy/kXYi/G/cKAC4HDAbSnzCIk1WmsoOKXg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.1.tgz", + "integrity": "sha512-pcFGeL2345VwdTnJhA6zLbew+YgWB0qBG2+dMtXjCicf6+rm6kO6cOoh5VnTe0ZMrMRgRyuHmCJxZWrIdzYuOw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.1.tgz", + "integrity": "sha512-mRJlqSRulVzcKq/LKA6ICSIc3K/l4fzlVn/gePn2nXIHy8seRi5z/eeRE0d/XMBxcMldiXtQTSpRj0tkkC3g8Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.1.tgz", + "integrity": "sha512-YDUNvVM85TI3g/1OpnqKP1h4NeW/j64DfWMf+G3M809xNk1bJSnpFp4sh83NpmVE5DXnkh8ULor4LTVZKoYLHw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.1.tgz", + "integrity": "sha512-7Mcn71p9ZuQFAj+h+dhQXy/yeLePRS2yKRnmW1DijA9thKO5qap0GNOIQK4yQ6iP3SU0Mrb/yWo8h8vgRba8lw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.1.tgz", + "integrity": "sha512-4YiLQTX6U4CSl0L9cluep9A9W6UmTfqBDc2/CH6wlu54pl4E7Jn3cOD8oxzvBDEGk/JMKgJ47C8g+radF7mwvg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.1.tgz", + "integrity": "sha512-2ra8F7w8OquwZN9z2/fKFnli69wa8PLwaVzRMIPGb13ByMJwC28Fbp8YcVGoUhlYMTt7j5j9bNgpysrN2UM+vw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.1.tgz", + "integrity": "sha512-Sy20ncyhjmBP0Ml+UvQbimjlk6VFgjW5uNP+qqwHB00mTE8Bl2C1TuHTlRwK2YoXeZbee5lP2XevBWVkAQAtSQ==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.1.tgz", + "integrity": "sha512-noITLp8oNjYliPnGWmLyelIHwULGqbHloQHGw1rtxbWhTuWooRpnZarZQJ1y9EUC4szuCusCc+HEpUtxpIwYvA==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.1.tgz", + "integrity": "sha512-hlxxXd+F1mWiAcaFR7Sv9ZQT6m6UfI8+Vy/kFJzztq2pDMU/0wZ9sish0iszNZvsQDo8Gc0i5yuFEOz5dDf6fA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.1.tgz", + "integrity": "sha512-EF7OpqQTQ/BvGqLzUi4rEHuagCV9MugAUXSHemwPW5vxZ75RR+jxO/2j95Ph2dalMpFHSVECjRoioHZgA9zOYA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.1.tgz", + "integrity": "sha512-wQO3JesW9PRkwlabQ27y7sPfVOOTLRG73I4F2UYHG5PXun3J9U3y+b7ezVKSYbsvSKGQ1k1cq8Qlun4C9kLt3w==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.1.tgz", + "integrity": "sha512-ouAGwhO6wHRXdnOVCOsB0tRFkA7nhNB2Nwax6oECXN0YiN8EYUTBAOudADOB1PI+yDL61TeNx/u7MVCzksNbkQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.1.tgz", + "integrity": "sha512-q2R38Sn+1J8RxhfJ+T54wSWmyKXWec+9jgDfqO2AtArEqHO5R2aeayp5H5OYLr5UYDVGsVaZPEFUooMhYCdz5A==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.1.tgz", + "integrity": "sha512-gfI5T24WLLuFfSKw7Go/zDXjAAV0fny0swTaDv+WjK7vqcw4cRhFfdsyKL1n+ukI+ooBxn3bVQnyrn06WpI50w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.1.tgz", + "integrity": "sha512-4h6XqthmB4Hspji84wvgk+ElodTsGj+dbZqHJHHtKxj4mYq0ANSEEPX9ys3moJueqsRjwpaJYH7874Itwnj2ow==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.1.tgz", + "integrity": "sha512-dlfCOa87o1VAYegLQ9EKilx2JCeRofiyPGhTCmqnuXZ6bMPiycO1rq1+sKoulAp7pGLIsTIw+1x5R+zgh5LhhA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.1.tgz", + "integrity": "sha512-cjkLbOlfcm3QGhMM1J5zaZjsw1GggbN6rw9UTSSRrPrR1KkcXnN7Uq9rPw34xImQ9VOY9GN+6u2Zj80B9ptkcw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.1.tgz", + "integrity": "sha512-Li1KdUnWGE4N3e1F/B4RTB1ms+nG4WBgjByO46pkeBVX/2UBsY53xf5vK9WygVmnH3RwncIST7lkSdLSY6P9lg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.1.tgz", + "integrity": "sha512-t4ZYOSoLTgwhuFMrmTMLx/+i1DQVK7HYqMc6kY46EApwi8X0nIVphzdNoThU3xt6n+N5urG1/gxBdCaKDLavfg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.1.tgz", + "integrity": "sha512-RgroPfMmKlD1RzSDxvwgcPiy2HNQKoYV7OmwIXDsk73uKW5t6B/V8KIy27SMv/FNXFo/oSBtWc9J0X7t91ezZg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.1.tgz", + "integrity": "sha512-at8QVep6S3h5Y6gSbdGU06bRY5WJkf6WUduM9YtvYMbYhB1MOFfUgc6kehitQXzOtMSaT70q7f9ydPhpqu821w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/@rtsao/scc/-/scc-1.1.0.tgz", @@ -1637,6 +2171,492 @@ "integrity": "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==", "license": "MIT" }, + "node_modules/@sentry/babel-plugin-component-annotate": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-5.3.0.tgz", + "integrity": "sha512-p4q8gn8wcFqZGP/s2MnJCAAd8fTikaU6A0mM97RDHQgStcrYiaS0Sc5zUNfb1V+UOLPuvdEdL6MwyxfzjYJQTA==", + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@sentry/browser": { + "version": "10.72.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-10.72.0.tgz", + "integrity": "sha512-pbXHaovq/rRO5wsHz6Yey6SBPJLrbi7kCaYlSr6+v4bB4UoIL5dFB65BcQ7XD3BzZDNNvl6jTe68a1/sUMb5oA==", + "license": "MIT", + "dependencies": { + "@sentry/browser-utils": "10.72.0", + "@sentry/conventions": "^0.16.0", + "@sentry/core": "10.72.0", + "@sentry/feedback": "10.72.0", + "@sentry/replay": "10.72.0", + "@sentry/replay-canvas": "10.72.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/browser-utils": { + "version": "10.72.0", + "resolved": "https://registry.npmjs.org/@sentry/browser-utils/-/browser-utils-10.72.0.tgz", + "integrity": "sha512-aZyogxXnNgDRupNscB8VEZdJbhbIk0+LiXrg5Fl82foGlcc5I22GdxORSb/JHwkeMFttZmvh1/OMwSPzAmE94Q==", + "license": "MIT", + "dependencies": { + "@sentry/conventions": "^0.16.0", + "@sentry/core": "10.72.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/bundler-plugin-core": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@sentry/bundler-plugin-core/-/bundler-plugin-core-5.3.0.tgz", + "integrity": "sha512-L5T60sWdAI3qWwdg3Ptwek/0TY59PERrxyqp4XMUkroayQvGd9r5dIW9Q1kSeXX9iJ442nXbFZKAOyCKV4Z13Q==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.18.5", + "@sentry/babel-plugin-component-annotate": "5.3.0", + "@sentry/cli": "^2.58.5", + "dotenv": "^16.3.1", + "find-up": "^5.0.0", + "glob": "^13.0.6", + "magic-string": "~0.30.8" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@sentry/bundler-plugins": { + "version": "10.72.0", + "resolved": "https://registry.npmjs.org/@sentry/bundler-plugins/-/bundler-plugins-10.72.0.tgz", + "integrity": "sha512-r9A2xZ/JlXuN/B6mfov+jn5RPrpz/35WqMtbns/WzwBDjMKQ5ZbsBUNFC/zXZ4VGEdhZCn0/gFFpl0yvt5NYiQ==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.18.5", + "@sentry/cli": "^2.58.6", + "@sentry/core": "10.72.0", + "dotenv": "^17.4.2", + "find-up": "^5.0.0", + "glob": "^13.0.6", + "magic-string": "~0.30.8" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "rollup": ">=3.2.0", + "webpack": ">=5.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/@sentry/bundler-plugins/node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/@sentry/cli": { + "version": "2.58.6", + "resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.58.6.tgz", + "integrity": "sha512-baBcNPLLfUi9WuL+Tpri9BFaAdvugZIKelC5X0tt0Zdy+K0K+PCVSrnNmwMWU/HyaF/SEv6b6UHnXIdqanBlcg==", + "hasInstallScript": true, + "license": "FSL-1.1-MIT", + "dependencies": { + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.7", + "progress": "^2.0.3", + "proxy-from-env": "^1.1.0", + "which": "^2.0.2" + }, + "bin": { + "sentry-cli": "bin/sentry-cli" + }, + "engines": { + "node": ">= 10" + }, + "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" + } + }, + "node_modules/@sentry/cli-darwin": { + "version": "2.58.6", + "resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.58.6.tgz", + "integrity": "sha512-udAVvcyfNa0R+95GvPz/+43/N3TC0TYKdkQ7D7jhPSzbcMc7l2fxRNN5yB3UpCA5fWFnW4toeaqwDBhb/Wh3LA==", + "license": "FSL-1.1-MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-linux-arm": { + "version": "2.58.6", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.58.6.tgz", + "integrity": "sha512-pD0LAt5PcUzAinBwvDqc66x9+2CabHEv486yP0gRjWO7SakbaxmfVq/EXd8VLq/Tzi39LAu422UYK1lpW3MILw==", + "cpu": [ + "arm" + ], + "license": "FSL-1.1-MIT", + "optional": true, + "os": [ + "linux", + "freebsd", + "android" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-linux-arm64": { + "version": "2.58.6", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.58.6.tgz", + "integrity": "sha512-q8mEcNNmeXMy5i+jWT30TVpH7LcP4HD21CD5XRSPAd/a912HF6EpK0ybf/1USO14WOhoXbAGi9txwaWabSe33g==", + "cpu": [ + "arm64" + ], + "license": "FSL-1.1-MIT", + "optional": true, + "os": [ + "linux", + "freebsd", + "android" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-linux-i686": { + "version": "2.58.6", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.58.6.tgz", + "integrity": "sha512-q8vNJi1eOV/4vxAFWBsEwLHoSYapaZHIf4j76KJGJXFKTkEbsjCOOsKbwUIBTQQhRgV4DFWh3ryfsPS/que4Kg==", + "cpu": [ + "x86", + "ia32" + ], + "license": "FSL-1.1-MIT", + "optional": true, + "os": [ + "linux", + "freebsd", + "android" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-linux-x64": { + "version": "2.58.6", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.58.6.tgz", + "integrity": "sha512-DZu956Mhi3ZRjTBe1WdbGV46ldVbA8d2rgp/fh51GsI25zjBHah4wZnPTSzpc+YqxU6pJpg579B/r3jrIK530Q==", + "cpu": [ + "x64" + ], + "license": "FSL-1.1-MIT", + "optional": true, + "os": [ + "linux", + "freebsd", + "android" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-win32-arm64": { + "version": "2.58.6", + "resolved": "https://registry.npmjs.org/@sentry/cli-win32-arm64/-/cli-win32-arm64-2.58.6.tgz", + "integrity": "sha512-nj0Ff/kmAB73EPDhR8B4O9r+NUHK5GkPCkGWC+kXVemqAJWL5jcJ5KdxG0l/S0z6RoEoltID8/43/B+TaMlT7A==", + "cpu": [ + "arm64" + ], + "license": "FSL-1.1-MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-win32-i686": { + "version": "2.58.6", + "resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.58.6.tgz", + "integrity": "sha512-WNZiDzPbgsEMQWq4avsQ391v/xWKJDIWWWo9GYl+N/w5qcYKkoDW7wQG7T9FasI6ENn68phChTOAPXXxbfAdOg==", + "cpu": [ + "x86", + "ia32" + ], + "license": "FSL-1.1-MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/cli-win32-x64": { + "version": "2.58.6", + "resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.58.6.tgz", + "integrity": "sha512-R35WJ17oF4D2eqI1DR2sQQqr0fjRTt5xoP16WrTu91XM2lndRMFsnjh+/GttbxapLCBNlrjzia99MJ0PZHZpgA==", + "cpu": [ + "x64" + ], + "license": "FSL-1.1-MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@sentry/conventions": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@sentry/conventions/-/conventions-0.16.0.tgz", + "integrity": "sha512-fO9PLmHdVURcSPUpWCItWAtgKiMwGdJHbovoSEyLplX5sxs2ugvI4CBPTrkkgqhObnZOD0CnWBKDzSVQYBKEyQ==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@sentry/core": { + "version": "10.72.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.72.0.tgz", + "integrity": "sha512-UJMHZfbjP4qk+g4AQhZmzosMdICC2D9p0/hLrm1LofPsp+WBfcSnv9jsY1a9TfmpS4WCAmTx8nANYTIXifcBjA==", + "license": "MIT", + "dependencies": { + "@sentry/conventions": "^0.16.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/feedback": { + "version": "10.72.0", + "resolved": "https://registry.npmjs.org/@sentry/feedback/-/feedback-10.72.0.tgz", + "integrity": "sha512-UPCABAD5WkOIg8XfyH58B5hsP19RGBbMXmiu7k7yQ0kFr/QIsn1tO2ts5w8ek5tIuTdAaeK3L+tinnCRh2STbg==", + "license": "MIT", + "dependencies": { + "@sentry/core": "10.72.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/nextjs": { + "version": "10.72.0", + "resolved": "https://registry.npmjs.org/@sentry/nextjs/-/nextjs-10.72.0.tgz", + "integrity": "sha512-pOO0Ir32pWQkhXhSwu5T6lREm8uMEeb1tXD90TVNxuCitQAA+FkqoTMyUNmOD4KaoSH/J2sDB+aBEmQgvwr5Mw==", + "license": "MIT", + "dependencies": { + "@opentelemetry/api": "^1.9.1", + "@rollup/plugin-commonjs": "28.0.1", + "@sentry/browser-utils": "10.72.0", + "@sentry/bundler-plugin-core": "^5.3.0", + "@sentry/conventions": "^0.16.0", + "@sentry/core": "10.72.0", + "@sentry/node": "10.72.0", + "@sentry/opentelemetry": "10.72.0", + "@sentry/react": "10.72.0", + "@sentry/server-utils": "10.72.0", + "@sentry/vercel-edge": "10.72.0", + "@sentry/webpack-plugin": "^5.3.0", + "rollup": "^4.60.3", + "stacktrace-parser": "^0.1.11" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "next": "^13.2.0 || ^14.0 || ^15.0.0-rc.0 || ^16.0.0-0" + } + }, + "node_modules/@sentry/node": { + "version": "10.72.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-10.72.0.tgz", + "integrity": "sha512-eQHQFxSX26MhG/nB+tAY4QRslnPvJse7pww/hd3zXAqNULRa5lFtjSLALcHx/KUVDCZdTPdUVZEj4nGidcHrow==", + "license": "MIT", + "dependencies": { + "@opentelemetry/api": "^1.9.1", + "@opentelemetry/instrumentation": "^0.220.0", + "@opentelemetry/sdk-trace-base": "^2.9.0", + "@sentry/conventions": "^0.16.0", + "@sentry/core": "10.72.0", + "@sentry/node-core": "10.72.0", + "@sentry/opentelemetry": "10.72.0", + "@sentry/server-utils": "10.72.0", + "import-in-the-middle": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/node-core": { + "version": "10.72.0", + "resolved": "https://registry.npmjs.org/@sentry/node-core/-/node-core-10.72.0.tgz", + "integrity": "sha512-xYuYWmWEWnN8h3YHa7mds212ANFgrxfrbmLvVg0p0wf9m6MFjLowOTmjaiK0XW20sM/veSelicre650bx8UFtQ==", + "license": "MIT", + "dependencies": { + "@sentry/conventions": "^0.16.0", + "@sentry/core": "10.72.0", + "@sentry/opentelemetry": "10.72.0", + "import-in-the-middle": "^3.0.0" + }, + "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" + }, + "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 + } + } + }, + "node_modules/@sentry/opentelemetry": { + "version": "10.72.0", + "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-10.72.0.tgz", + "integrity": "sha512-ZVbAM1rGU/awN7cH/jvC87WpQw0NOJx5id6dKnnefStXpP/kUnZXYhtX9gfBnofYvJWa5I5VUSeuKCLUcKL75w==", + "license": "MIT", + "dependencies": { + "@sentry/conventions": "^0.16.0", + "@sentry/core": "10.72.0" + }, + "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" + } + }, + "node_modules/@sentry/react": { + "version": "10.72.0", + "resolved": "https://registry.npmjs.org/@sentry/react/-/react-10.72.0.tgz", + "integrity": "sha512-I+/Wq2duluHIGoCVbtm2FsC4kp160hNmKVFlCVa/UsutsuEbZAcFC1GI8Zzg/uUWbR7x/LhH7OqP9jdfwJsm4Q==", + "license": "MIT", + "dependencies": { + "@sentry/browser": "10.72.0", + "@sentry/conventions": "^0.16.0", + "@sentry/core": "10.72.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^16.14.0 || 17.x || 18.x || 19.x" + } + }, + "node_modules/@sentry/replay": { + "version": "10.72.0", + "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-10.72.0.tgz", + "integrity": "sha512-DiUnhALGvEzaF7bSuRnr/Xftfh4eYZHQ9kwb+CRIDbJ0R2beetHH7vzE8vDe3pFMd8TWAuLkdNLMTC2ic9xfnA==", + "license": "MIT", + "dependencies": { + "@sentry/browser-utils": "10.72.0", + "@sentry/core": "10.72.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/replay-canvas": { + "version": "10.72.0", + "resolved": "https://registry.npmjs.org/@sentry/replay-canvas/-/replay-canvas-10.72.0.tgz", + "integrity": "sha512-RdNCbuQ1TBsyCrkHDBPTukQ3gtm8chXi0ldiJSiNUduPGCoKet9L1JocTqJ2gMguV+bOK+QLMvFnBCDbdcSg6A==", + "license": "MIT", + "dependencies": { + "@sentry/core": "10.72.0", + "@sentry/replay": "10.72.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/server-utils": { + "version": "10.72.0", + "resolved": "https://registry.npmjs.org/@sentry/server-utils/-/server-utils-10.72.0.tgz", + "integrity": "sha512-CWpHMYW81RDBSVBdnxulGUvvBhkBb/OpSr72ubSe1UkKTcgT0NDMCWxE3dLFXqSxzT4DaF5UlUVv9ii5Sse53w==", + "license": "MIT", + "dependencies": { + "@sentry/conventions": "^0.16.0", + "@sentry/core": "10.72.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/vercel-edge": { + "version": "10.72.0", + "resolved": "https://registry.npmjs.org/@sentry/vercel-edge/-/vercel-edge-10.72.0.tgz", + "integrity": "sha512-rQ8m5nr0ZNWjTwi/bd/n0ZL1BmiCoj1zuI0yngezfRgPc3/gv6h/jRiHlTiVnisA48t4S9ri4kB7OCOb6/A9uw==", + "license": "MIT", + "dependencies": { + "@opentelemetry/api": "^1.9.1", + "@sentry/core": "10.72.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/webpack-plugin": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@sentry/webpack-plugin/-/webpack-plugin-5.4.0.tgz", + "integrity": "sha512-J3a0BvUZ75Qxy+v/Ap3Hx4ZEcSjlPHZ/jDtxdRhXQCyNeEb8xq0uUBTI9VLtGk2eNeNucOxOEJ5ngqdNjnEH/A==", + "license": "MIT", + "dependencies": { + "@sentry/bundler-plugins": "^10.64.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "webpack": ">=5.0.0" + } + }, "node_modules/@socket.io/component-emitter": { "version": "3.1.2", "resolved": "https://registry.npmmirror.com/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", @@ -2243,10 +3263,9 @@ "license": "MIT" }, "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "license": "MIT" }, "node_modules/@types/js-cookie": { @@ -2260,7 +3279,6 @@ "version": "7.0.15", "resolved": "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, "license": "MIT" }, "node_modules/@types/json5": { @@ -2274,7 +3292,6 @@ "version": "20.19.37", "resolved": "https://registry.npmmirror.com/@types/node/-/node-20.19.37.tgz", "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -2890,11 +3907,185 @@ "win32" ] }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "license": "MIT", + "peer": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "license": "MIT", + "peer": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "license": "MIT", + "peer": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "license": "MIT", + "peer": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "license": "MIT", + "peer": true, + "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" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "license": "MIT", + "peer": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "license": "MIT", + "peer": true, + "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" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "license": "MIT", + "peer": true, + "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" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "license": "MIT", + "peer": true, + "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" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "license": "Apache-2.0", + "peer": true + }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -2913,6 +4104,18 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ajv": { "version": "6.14.0", "resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.14.0.tgz", @@ -2930,6 +4133,48 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT", + "peer": true + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -3260,7 +4505,6 @@ "version": "4.28.1", "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.28.1.tgz", "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", - "dev": true, "funding": [ { "type": "opencollective", @@ -3290,6 +4534,13 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT", + "peer": true + }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.8.tgz", @@ -3406,6 +4657,22 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/cjs-module-lexer": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.1.tgz", + "integrity": "sha512-Ca8swihM+/4yKecYHY52kgJd300hi2lADU/a1RxNTRe+RJ9jvqQlESpbz9DnG9mowez8qwXHB8qYdIUw9e+F5Q==", + "license": "MIT" + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmmirror.com/client-only/-/client-only-0.0.1.tgz", @@ -3453,6 +4720,19 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT", + "peer": true + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "license": "MIT" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", @@ -3464,7 +4744,6 @@ "version": "2.0.0", "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, "license": "MIT" }, "node_modules/core-js": { @@ -3800,6 +5079,18 @@ "@types/trusted-types": "^2.0.7" } }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -3818,7 +5109,6 @@ "version": "1.5.313", "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.313.tgz", "integrity": "sha512-QBMrTWEf00GXZmJyx2lbYD45jpI3TUFnNIzJ5BBc8piGUDwMPa1GV6HJWTZVvY/eiN3fSopl7NRbgGp9sZ9LTA==", - "dev": true, "license": "ISC" }, "node_modules/emoji-regex": { @@ -3851,14 +5141,13 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.20.0", - "resolved": "https://registry.npmmirror.com/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz", - "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==", - "dev": true, + "version": "5.24.5", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz", + "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", - "tapable": "^2.3.0" + "tapable": "^2.3.3" }, "engines": { "node": ">=10.13.0" @@ -3980,6 +5269,12 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -4052,7 +5347,6 @@ "version": "3.2.0", "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -4491,6 +5785,12 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz", @@ -4507,11 +5807,20 @@ "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", "license": "MIT" }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -4569,6 +5878,23 @@ "pako": "^2.1.0" } }, + "node_modules/fast-uri": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.6.tgz", + "integrity": "sha512-7Ical1vFEMr0onbVzEDIreM22I4khW+fzyQPwvAFWBp1iwdshSZRsL4jjRvPG9JP1uiqMHRto+YU6R2/CzDz5Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause", + "peer": true + }, "node_modules/fastq": { "version": "1.20.1", "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.20.1.tgz", @@ -4615,7 +5941,6 @@ "version": "5.0.0", "resolved": "https://registry.npmmirror.com/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, "license": "MIT", "dependencies": { "locate-path": "^6.0.0", @@ -4701,6 +6026,20 @@ "node": ">= 6" } }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", @@ -4755,7 +6094,6 @@ "version": "1.0.0-beta.2", "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -4829,6 +6167,23 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz", @@ -4842,6 +6197,42 @@ "node": ">=10.13.0" } }, + "node_modules/glob/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/globals": { "version": "14.0.0", "resolved": "https://registry.npmmirror.com/globals/-/globals-14.0.0.tgz", @@ -4888,7 +6279,6 @@ "version": "4.2.11", "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, "license": "ISC" }, "node_modules/has-bigints": { @@ -4908,7 +6298,6 @@ "version": "4.0.0", "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5013,6 +6402,19 @@ "node": ">=8.0.0" } }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/icu-minify": { "version": "4.13.0", "resolved": "https://registry.npmmirror.com/icu-minify/-/icu-minify-4.13.0.tgz", @@ -5065,6 +6467,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/import-in-the-middle": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-3.3.3.tgz", + "integrity": "sha512-AiohS3H80sXO6owEltjGX+glb7qXaDhBoJb9XcQVH4UI207xu/bDLUcadVKp7Qe576reg9yr/PXZjV5qx8gfbA==", + "license": "Apache-2.0", + "dependencies": { + "cjs-module-lexer": "^2.2.0", + "es-module-lexer": "^2.2.0", + "module-details-from-path": "^1.0.4" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -5383,6 +6799,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmmirror.com/is-regex/-/is-regex-1.2.1.tgz", @@ -5539,7 +6964,6 @@ "version": "2.0.0", "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, "license": "ISC" }, "node_modules/iterator.prototype": { @@ -5560,6 +6984,37 @@ "node": ">= 0.4" } }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/jiti": { "version": "2.6.1", "resolved": "https://registry.npmmirror.com/jiti/-/jiti-2.6.1.tgz", @@ -5602,7 +7057,6 @@ "version": "3.1.0", "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz", "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -5636,7 +7090,6 @@ "version": "2.2.3", "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, "license": "MIT", "bin": { "json5": "lib/cli.js" @@ -5987,7 +7440,6 @@ "version": "6.0.0", "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, "license": "MIT", "dependencies": { "p-locate": "^5.0.0" @@ -6022,7 +7474,6 @@ "version": "5.1.1", "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, "license": "ISC", "dependencies": { "yallist": "^3.0.2" @@ -6041,7 +7492,6 @@ "version": "0.30.21", "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" @@ -6056,6 +7506,13 @@ "node": ">= 0.4" } }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT", + "peer": true + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz", @@ -6124,6 +7581,82 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minimizer-webpack-plugin": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/minimizer-webpack-plugin/-/minimizer-webpack-plugin-5.8.0.tgz", + "integrity": "sha512-2cT9+goJfBhtMz+gJqejSf09ClgmYhPhccRb/fb0ztVbixt0BkO8mRuI26FoPPuUNkRk6iEDryWAIouc5W8eJA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.3", + "terser": "^5.51.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "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 + } + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/module-details-from-path": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", + "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", @@ -6180,6 +7713,13 @@ "node": ">= 0.6" } }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "license": "MIT", + "peer": true + }, "node_modules/next": { "version": "16.1.6", "resolved": "https://registry.npmmirror.com/next/-/next-16.1.6.tgz", @@ -6374,11 +7914,30 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-releases": { "version": "2.0.36", "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.36.tgz", "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", - "dev": true, "license": "MIT" }, "node_modules/object-assign": { @@ -6543,7 +8102,6 @@ "version": "3.1.0", "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" @@ -6559,7 +8117,6 @@ "version": "5.0.0", "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, "license": "MIT", "dependencies": { "p-limit": "^3.0.2" @@ -6604,7 +8161,6 @@ "version": "4.0.0", "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -6627,6 +8183,31 @@ "dev": true, "license": "MIT" }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/performance-now": { "version": "2.1.0", "resolved": "https://registry.npmmirror.com/performance-now/-/performance-now-2.1.0.tgz", @@ -6708,6 +8289,15 @@ "node": ">= 0.8.0" } }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz", @@ -6947,6 +8537,29 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-in-the-middle": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-8.0.1.tgz", + "integrity": "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "module-details-from-path": "^1.0.3" + }, + "engines": { + "node": ">=9.3.0 || >=8.10.0 <9.0.0" + } + }, "node_modules/reselect": { "version": "5.2.0", "resolved": "https://registry.npmmirror.com/reselect/-/reselect-5.2.0.tgz", @@ -7015,6 +8628,51 @@ "node": ">= 0.8.15" } }, + "node_modules/rollup": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.1.tgz", + "integrity": "sha512-3Df9jsstwhccuEfmAMi9l8XUh/GOkVObmFTU7CCVBysEbcOZLl84jCtaAZMcPiMz2EGKsATzQcU+Xr3n/wU6cg==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.63.1", + "@rollup/rollup-android-arm64": "4.63.1", + "@rollup/rollup-darwin-arm64": "4.63.1", + "@rollup/rollup-darwin-x64": "4.63.1", + "@rollup/rollup-freebsd-arm64": "4.63.1", + "@rollup/rollup-freebsd-x64": "4.63.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.63.1", + "@rollup/rollup-linux-arm-musleabihf": "4.63.1", + "@rollup/rollup-linux-arm64-gnu": "4.63.1", + "@rollup/rollup-linux-arm64-musl": "4.63.1", + "@rollup/rollup-linux-loong64-gnu": "4.63.1", + "@rollup/rollup-linux-loong64-musl": "4.63.1", + "@rollup/rollup-linux-ppc64-gnu": "4.63.1", + "@rollup/rollup-linux-ppc64-musl": "4.63.1", + "@rollup/rollup-linux-riscv64-gnu": "4.63.1", + "@rollup/rollup-linux-riscv64-musl": "4.63.1", + "@rollup/rollup-linux-s390x-gnu": "4.63.1", + "@rollup/rollup-linux-x64-gnu": "4.63.1", + "@rollup/rollup-linux-x64-musl": "4.63.1", + "@rollup/rollup-openbsd-x64": "4.63.1", + "@rollup/rollup-openharmony-arm64": "4.63.1", + "@rollup/rollup-win32-arm64-msvc": "4.63.1", + "@rollup/rollup-win32-ia32-msvc": "4.63.1", + "@rollup/rollup-win32-x64-gnu": "4.63.1", + "@rollup/rollup-win32-x64-msvc": "4.63.1", + "fsevents": "~2.3.2" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", @@ -7100,11 +8758,67 @@ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "license": "MIT" }, + "node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT", + "peer": true + }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -7344,6 +9058,16 @@ "node": ">=10.0.0" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", @@ -7353,6 +9077,17 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "peer": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/stable-hash": { "version": "0.0.5", "resolved": "https://registry.npmmirror.com/stable-hash/-/stable-hash-0.0.5.tgz", @@ -7370,6 +9105,18 @@ "node": ">=0.1.14" } }, + "node_modules/stacktrace-parser": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.11.tgz", + "integrity": "sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==", + "license": "MIT", + "dependencies": { + "type-fest": "^0.7.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", @@ -7587,10 +9334,9 @@ "license": "MIT" }, "node_modules/tapable": { - "version": "2.3.0", - "resolved": "https://registry.npmmirror.com/tapable/-/tapable-2.3.0.tgz", - "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", - "dev": true, + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", "license": "MIT", "engines": { "node": ">=6" @@ -7600,6 +9346,25 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/terser": { + "version": "5.51.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.51.2.tgz", + "integrity": "sha512-bWnjSNscmuI+GJze6ZupnHP8G/cTcsJF+bXCeQknk2SHQsgbNJnLrqiH9jZ2W4STPVXH2mDKKRX3iwPhc9Cn/Q==", + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/text-segmentation": { "version": "1.0.3", "resolved": "https://registry.npmmirror.com/text-segmentation/-/text-segmentation-1.0.3.tgz", @@ -7677,6 +9442,12 @@ "node": ">=8.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/ts-api-utils": { "version": "2.4.0", "resolved": "https://registry.npmmirror.com/ts-api-utils/-/ts-api-utils-2.4.0.tgz", @@ -7735,6 +9506,15 @@ "node": ">= 0.8.0" } }, + "node_modules/type-fest": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", + "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=8" + } + }, "node_modules/typed-array-buffer": { "version": "1.0.3", "resolved": "https://registry.npmmirror.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", @@ -7874,7 +9654,6 @@ "version": "6.21.0", "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, "license": "MIT" }, "node_modules/unrs-resolver": { @@ -7916,7 +9695,6 @@ "version": "1.2.3", "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, "funding": [ { "type": "opencollective", @@ -8015,11 +9793,102 @@ "d3-timer": "^3.0.1" } }, + "node_modules/watchpack": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.2.tgz", + "integrity": "sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg==", + "license": "MIT", + "peer": true, + "dependencies": { + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/webpack": { + "version": "5.110.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.110.2.tgz", + "integrity": "sha512-TciLrfM7zgEjqGdY851HkirDsSPQgTFsWQpl9oHqMAMYsHhEC0bKjscvjpnz+pzx10hLC8qISApGrsnrCP4UtQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree": "^1.0.8", + "@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.16.0", + "browserslist": "^4.28.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.24.4", + "es-module-lexer": "^2.1.0", + "events": "^3.2.0", + "graceful-fs": "^4.2.11", + "mime-db": "^1.54.0", + "minimizer-webpack-plugin": "^5.7.0", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "watchpack": "^2.5.2", + "webpack-sources": "^3.5.1" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-sources": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.5.1.tgz", + "integrity": "sha512-jyuiGJdtvY434z5bUZrjz67v76/ePNvFZTp9Mdz29IlH4+GPsgyGjiv0fKI+M7BdkU6ADjulUcKAd3tUK3WlEw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -8163,14 +10032,12 @@ "version": "3.1.1", "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, "license": "ISC" }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" diff --git a/frontend/package.json b/frontend/package.json index ba8e631..f6618b5 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "@hookform/resolvers": "^5.2.2", + "@sentry/nextjs": "^10.72.0", "@tanstack/react-query": "^5.90.21", "axios": "^1.13.6", "js-cookie": "^3.0.5", diff --git a/frontend/src/app/[locale]/error.tsx b/frontend/src/app/[locale]/error.tsx new file mode 100644 index 0000000..30923e6 --- /dev/null +++ b/frontend/src/app/[locale]/error.tsx @@ -0,0 +1,30 @@ +'use client'; + +import { useEffect } from 'react'; +import { useTranslations } from 'next-intl'; +import * as Sentry from '@sentry/nextjs'; +import { Button } from '@/components/ui/shared/Button'; + +export default function LocaleError({ + error, + reset, +}: { + error: Error & { digest?: string }; + reset: () => void; +}) { + const t = useTranslations('common'); + + useEffect(() => { + Sentry.captureException(error); + }, [error]); + + return ( +
+

{t('pageErrorTitle')}

+

{t('pageErrorBody')}

+ +
+ ); +} diff --git a/frontend/src/app/global-error.tsx b/frontend/src/app/global-error.tsx new file mode 100644 index 0000000..36deb8b --- /dev/null +++ b/frontend/src/app/global-error.tsx @@ -0,0 +1,44 @@ +'use client'; + +import { useEffect } from 'react'; +import * as Sentry from '@sentry/nextjs'; + +export default function GlobalError({ + error, + reset, +}: { + error: Error & { digest?: string }; + reset: () => void; +}) { + useEffect(() => { + Sentry.captureException(error); + }, [error]); + + return ( + + +
+

Something went wrong

+

+ The page failed to load. You can try again. +

+ +
+ + + ); +} diff --git a/frontend/src/components/ui/today/TodayWidgetErrorBoundary.tsx b/frontend/src/components/ui/today/TodayWidgetErrorBoundary.tsx index 5bc0ec0..6913273 100644 --- a/frontend/src/components/ui/today/TodayWidgetErrorBoundary.tsx +++ b/frontend/src/components/ui/today/TodayWidgetErrorBoundary.tsx @@ -1,5 +1,6 @@ 'use client'; +import * as Sentry from '@sentry/nextjs'; import { Component, type ErrorInfo, type ReactNode } from 'react'; interface TodayWidgetErrorBoundaryProps { @@ -23,6 +24,7 @@ export class TodayWidgetErrorBoundary extends Component< componentDidCatch(error: Error, info: ErrorInfo) { console.error('Today widget render error:', error, info); + Sentry.captureException(error, { extra: { componentStack: info.componentStack } }); } render() { diff --git a/frontend/src/instrumentation-client.ts b/frontend/src/instrumentation-client.ts new file mode 100644 index 0000000..53910bd --- /dev/null +++ b/frontend/src/instrumentation-client.ts @@ -0,0 +1,15 @@ +import * as Sentry from '@sentry/nextjs'; +import { sentrySharedOptions } from '@/lib/error-tracking/sentrySharedOptions'; + +const dsn = process.env.NEXT_PUBLIC_SENTRY_DSN?.trim(); + +if (dsn) { + Sentry.init({ + dsn, + environment: + process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT?.trim() || + process.env.NODE_ENV || + 'development', + ...sentrySharedOptions(), + }); +} diff --git a/frontend/src/instrumentation.ts b/frontend/src/instrumentation.ts new file mode 100644 index 0000000..db4db02 --- /dev/null +++ b/frontend/src/instrumentation.ts @@ -0,0 +1,9 @@ +import * as Sentry from '@sentry/nextjs'; + +export async function register(): Promise { + if (process.env.NEXT_RUNTIME === 'nodejs') { + await import('./lib/error-tracking/sentry.server.config'); + } +} + +export const onRequestError = Sentry.captureRequestError; diff --git a/frontend/src/lib/api/client.ts b/frontend/src/lib/api/client.ts index 099398a..6222f37 100644 --- a/frontend/src/lib/api/client.ts +++ b/frontend/src/lib/api/client.ts @@ -2,6 +2,7 @@ import axios, { AxiosError, InternalAxiosRequestConfig } from 'axios'; import type { ApiError } from '@/types/api'; import { notifyAccessTokenRefreshed } from '@/lib/auth/accessTokenEvents'; +import { reportUnexpectedApiFailure } from '@/lib/error-tracking/reportUnexpectedApiFailure'; interface CustomAxiosRequestConfig extends InternalAxiosRequestConfig { _retry?: boolean; @@ -46,6 +47,7 @@ function shouldSkipRefreshRetry(url: string | undefined): boolean { apiClient.interceptors.response.use( (response) => response, async (error: AxiosError) => { + reportUnexpectedApiFailure(error); const originalRequest = error.config as CustomAxiosRequestConfig; if ( diff --git a/frontend/src/lib/error-tracking/reportUnexpectedApiFailure.ts b/frontend/src/lib/error-tracking/reportUnexpectedApiFailure.ts new file mode 100644 index 0000000..f0c9719 --- /dev/null +++ b/frontend/src/lib/error-tracking/reportUnexpectedApiFailure.ts @@ -0,0 +1,20 @@ +import * as Sentry from '@sentry/nextjs'; +import type { AxiosError } from 'axios'; + +/** Report network failures and HTTP 5xx only — not coded 4xx AppExceptions. */ +export function reportUnexpectedApiFailure(error: AxiosError): void { + const status = error.response?.status; + if (status !== undefined && status < 500) { + return; + } + + Sentry.captureException(error, { + tags: { + api_status: status ? String(status) : 'network', + }, + extra: { + url: error.config?.url, + method: error.config?.method, + }, + }); +} diff --git a/frontend/src/lib/error-tracking/sentry.server.config.ts b/frontend/src/lib/error-tracking/sentry.server.config.ts new file mode 100644 index 0000000..53910bd --- /dev/null +++ b/frontend/src/lib/error-tracking/sentry.server.config.ts @@ -0,0 +1,15 @@ +import * as Sentry from '@sentry/nextjs'; +import { sentrySharedOptions } from '@/lib/error-tracking/sentrySharedOptions'; + +const dsn = process.env.NEXT_PUBLIC_SENTRY_DSN?.trim(); + +if (dsn) { + Sentry.init({ + dsn, + environment: + process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT?.trim() || + process.env.NODE_ENV || + 'development', + ...sentrySharedOptions(), + }); +} diff --git a/frontend/src/lib/error-tracking/sentrySharedOptions.ts b/frontend/src/lib/error-tracking/sentrySharedOptions.ts new file mode 100644 index 0000000..f650120 --- /dev/null +++ b/frontend/src/lib/error-tracking/sentrySharedOptions.ts @@ -0,0 +1,28 @@ +import type { ErrorEvent } from '@sentry/core'; + +/** Shared Sentry/GlitchTip options — no session replay, no PII in payloads. */ +export function sentrySharedOptions() { + return { + sendDefaultPii: false as const, + tracesSampleRate: 0, + replaysSessionSampleRate: 0, + replaysOnErrorSampleRate: 0, + beforeSend(event: ErrorEvent): ErrorEvent { + if (event.request) { + delete event.request.cookies; + delete event.request.data; + if (event.request.headers) { + delete event.request.headers.cookie; + delete event.request.headers.authorization; + delete event.request.headers.Authorization; + } + } + if (event.user) { + delete event.user.email; + delete event.user.ip_address; + delete event.user.username; + } + return event; + }, + }; +} diff --git a/infrastructure/DEPLOY.md b/infrastructure/DEPLOY.md index 79828e4..40978f7 100644 --- a/infrastructure/DEPLOY.md +++ b/infrastructure/DEPLOY.md @@ -1,13 +1,84 @@ # Dyolink — Production Server Deploy Guide -Deploy the full stack (Postgres, NestJS API, Next.js, Nginx, Let's Encrypt) on a fresh Linux server using **Docker Hub** images. +Linux VPS serves **`https://nudentic.ir` only**. Images come from the **Gitea registry** (`wixur.ir:3000`) on git tags. Staging is Windows (`https://wixur.ir`) — see [`STAGING-DEPLOY.md`](STAGING-DEPLOY.md). -**Example used in production:** `https://wixur.ir` on server `185.243.48.140`. +**Automated path:** tag `v1.0.1` → [`.gitea/workflows/prod-tag-deploy.yml`](../.gitea/workflows/prod-tag-deploy.yml) (Windows runner builds, SSH to this server). + +**Manual / first SSL:** Docker Hub + [`scripts/build-and-push-prod.sh`](scripts/build-and-push-prod.sh) is still documented below as a fallback. + +**Example host:** `https://nudentic.ir` (do not point `wixur.ir` at this VPS). + +--- + +## Automated deploy (Gitea tags) + +``` +git tag v1.0.1 && git push origin v1.0.1 + ↓ +Windows act_runner builds frontend with https://nudentic.ir + ↓ +Push wixur.ir:3000//dyolink-*:v1.0.1 (never :latest) + ↓ +SSH → Linux docker login wixur.ir:3000 → compose pull + up + ↓ +https://nudentic.ir +``` + +`:latest` is **staging only** (wixur.ir baked in). Production compose must pin `TAG=v1.0.1`. + +### One-time on the Linux server + +1. **HTTP registry** — Gitea is `http://wixur.ir:3000`. In `/etc/docker/daemon.json`: + +```json +{ + "insecure-registries": ["wixur.ir:3000"] +} +``` + +Then `sudo systemctl restart docker` (containers restart). + +2. **`.env`** in `/opt/dyolink/infrastructure/` — from [`deploy.prod.env.example`](deploy.prod.env.example): + +- `DOMAIN=nudentic.ir` +- `REGISTRY_PREFIX=wixur.ir:3000/` (same owner as Gitea `REGISTRY_OWNER`) +- `TAG=v1.0.1` (CI overrides per release) + +3. **`secrets/backend.env`:** `FRONTEND_URL=https://nudentic.ir`, `COOKIE_SECURE=true` + +4. **SSH** — user that can run `docker` (e.g. `dyolink` in the `docker` group). Put the matching **public** key in `~/.ssh/authorized_keys`. + +5. **Test pull** (after a staging or prod image exists): + +```bash +docker login wixur.ir:3000 -u +docker pull wixur.ir:3000//dyolink-backend: +``` + +### Gitea (same repo as staging) + +**Variables:** `PROD_PUBLIC_BASE_URL=https://nudentic.ir`, `PROD_REGISTRY_HOST=wixur.ir:3000`, `PROD_INFRA_DIR=/opt/dyolink/infrastructure`, plus existing `REGISTRY_HOST` / `REGISTRY_OWNER` / `CLONE_HOST`. + +**Secrets:** `PROD_SSH_HOST`, `PROD_SSH_USER`, `PROD_SSH_KEY` (private key). Reuse `REGISTRY_USERNAME` / `REGISTRY_PASSWORD`. Optional `PROD_SSH_PORT` (default 22). + +OpenSSH must be on the Windows runner (`ssh.exe` / `scp.exe`). + +### Release + +```bash +git tag v1.0.1 +git push origin v1.0.1 +``` + +Or Gitea → Actions → **Production — tag build, push, deploy** → Run → tag `v1.0.1`. + +Check `https://nudentic.ir/api/health`. --- ## Architecture + ``` Internet → Nginx (:80 / :443) ├── / → frontend:3000 (Next.js) @@ -18,8 +89,8 @@ Internet → Nginx (:80 / :443) | Service | Image | Notes | |-----------|------------------------------------|--------------------------------| | postgres | `postgres:15-alpine` | Data in Docker volume | -| backend | `dyolink/dyolink-backend:latest` | Runs migrations + seed on start | -| frontend | `dyolink/dyolink-frontend:latest` | URLs baked in at **build time** | +| backend | `REGISTRY_PREFIX/dyolink-backend:TAG` | Gitea `v*` or Hub `dyolink/…` | +| frontend | `REGISTRY_PREFIX/dyolink-frontend:TAG` | URLs baked in at **build time** | | nginx | `nginx:alpine` | SSL termination + reverse proxy | | certbot | `certbot/certbot` | Auto-renews certificates | @@ -289,16 +360,19 @@ Open `https://YOUR_DOMAIN` in a browser. ## Updating the app (new release) -**On Mac** — build & push: +**Preferred:** `git tag vX.Y.Z && git push origin vX.Y.Z` (Gitea production workflow). + +**Fallback (Docker Hub from a Mac):** ```bash -./infrastructure/scripts/build-and-push-prod.sh wixur.ir latest +./infrastructure/scripts/build-and-push-prod.sh nudentic.ir v1.0.1 ``` -**On server:** +**On server** (if not using CI): ```bash cd /opt/dyolink/infrastructure +# TAG in .env or: TAG=v1.0.1 docker compose -f docker-compose.prod.yml --env-file .env pull backend frontend docker compose -f docker-compose.prod.yml --env-file .env pull backend frontend docker compose -f docker-compose.prod.yml --env-file .env up -d ``` @@ -361,6 +435,23 @@ docker volume rm dyolink_postgres_data_prod ./scripts/deploy-prod.sh ``` +### Docker login to `wixur.ir:3000`: `http response to HTTPS client` + +Add `"insecure-registries": ["wixur.ir:3000"]` to `/etc/docker/daemon.json` and restart Docker. + +### `connection reset by peer` while pulling `:v*` from Gitea + +`docker login` succeeded, then `compose pull` failed on a frontend/backend manifest or layer from `wixur.ir:3000`. Auth is fine — the Windows Gitea registry dropped the TCP connection (common right after a large push, or when both images pull in parallel). + +Images are already in the registry. Re-run only the **deploy** job, or on the VPS: + +```bash +cd /opt/dyolink/infrastructure +./scripts/prod-remote-deploy.sh v1.0.2 +``` + +`prod-remote-deploy.sh` pulls backend then frontend with retries. If every attempt RSTs, check Gitea is up and `insecure-registries` includes `wixur.ir:3000`. + ### View logs ```bash @@ -385,12 +476,13 @@ docker compose -f docker-compose.prod.yml --env-file .env exec frontend \ | Path on server | Purpose | |----------------|---------| -| `/opt/dyolink/infrastructure/.env` | Domain, Docker Hub user, Let's Encrypt email | +| `/opt/dyolink/infrastructure/.env` | `DOMAIN`, `REGISTRY_PREFIX`, `TAG`, Let's Encrypt email | | `/opt/dyolink/secrets/database.env` | Postgres credentials | -| `/opt/dyolink/secrets/backend.env` | API secrets, DATABASE_URL, JWT, SMS | +| `/opt/dyolink/secrets/backend.env` | API secrets, DATABASE_URL, JWT, SMS, `FRONTEND_URL` | | `/opt/dyolink/infrastructure/nginx/generated/default.conf` | Auto-generated nginx SSL config | -| `/opt/dyolink/infrastructure/scripts/deploy-prod.sh` | Main deploy entry point | -| `/opt/dyolink/infrastructure/scripts/build-and-push-prod.sh` | Build & push (run on Mac) | +| `/opt/dyolink/infrastructure/scripts/prod-remote-deploy.sh` | Tag deploy (CI SSH) | +| `/opt/dyolink/infrastructure/scripts/deploy-prod.sh` | First-time SSL + stack up | +| `.gitea/workflows/prod-tag-deploy.yml` | CI: build, push `:v*`, SSH deploy | --- diff --git a/infrastructure/STAGING-DEPLOY.md b/infrastructure/STAGING-DEPLOY.md index f402ff2..ab1fc1d 100644 --- a/infrastructure/STAGING-DEPLOY.md +++ b/infrastructure/STAGING-DEPLOY.md @@ -2,9 +2,13 @@ Automatic staging on a **self-hosted Gitea** machine: merge (or push) to **`master`** → build Docker images → push to Gitea Container Registry → deploy on the same host. -**Example:** Gitea at `http://178.131.50.201:3000`, staging app at `http://178.131.50.201:8088`. +| Public URL | What | +|------------|------| +| `https://wixur.ir` | Staging app (port **443** → Windows nginx → Docker). Mobinnet: public **80** is the modem. | +| `http://wixur.ir:8088` | HTTP fallback | +| `http://wixur.ir:3000` | Gitea + container registry | -Production (`nudentic.ir` on Linux, tag-based releases) is documented in [`DEPLOY.md`](DEPLOY.md). +DNS `wixur.ir` must point at the **Windows** host. Production (`https://nudentic.ir` on Linux) is separate — see [`DEPLOY.md`](DEPLOY.md). Do **not** point `wixur.ir` at the Linux VPS. --- @@ -19,7 +23,9 @@ Build backend + frontend → push to Gitea registry ↓ docker compose pull + up -d (docker-compose.registry.yml) ↓ -http://:8088 → nginx → frontend / backend → postgres +https://wixur.ir → Windows nginx :443 → 127.0.0.1:18088 → Docker nginx → app +http://wixur.ir:8088 → portproxy :8088 → 127.0.0.1:18088 (fallback) +http://wixur.ir:3000 → Gitea (native, no Docker) ``` | Service | Image source | @@ -29,7 +35,26 @@ http://:8088 → nginx → frontend / backend → postgres | frontend | `//dyolink-frontend:` | | nginx | `nginx:alpine` | -Frontend public URLs are **baked in at build time** via `PUBLIC_BASE_URL`. +Frontend public URLs are **baked in at build time** via `PUBLIC_BASE_URL`. After changing the public URL, re-run the Gitea workflow (or push to `master`) and set `FRONTEND_URL` in `C:\dyolink\secrets\backend.staging.env` to the same origin. + +--- + +## Cut over from `:8088` to `http://wixur.ir` + +DNS A record for `wixur.ir` → Windows IP (already done if `http://wixur.ir:3000` and `:8088` work). + +On Windows: host nginx on **80** proxies to Docker **18088** ([`nginx/windows-edge-http.conf`](nginx/windows-edge-http.conf)). Remove portproxy on **80** (keep **8088** as fallback). Router must forward **80**. See §7. + +In Gitea → repo → **Settings → Actions → Variables**: + +- `PUBLIC_BASE_URL` = `https://wixur.ir` +- `STAGING_HTTP_PORT` = `80` (optional; workflow default is 80) + +On disk: `FRONTEND_URL=https://wixur.ir` and `COOKIE_SECURE=true` in `C:\dyolink\secrets\backend.staging.env`. + +Then run the **Registry — build, push, deploy** workflow so the frontend image is rebuilt without `:8088`. + +--- --- @@ -59,8 +84,7 @@ Workflow file: [`.gitea/workflows/registry-build-deploy.yml`](../.gitea/workflow "insecure-registries": [ "host.docker.internal:3000", "127.0.0.1:3000", - "178.131.50.201:3000", - "192.168.1.100:3000" + "wixur.ir:3000" ] } ``` @@ -130,7 +154,7 @@ Copy examples and edit: Rules: - `DATABASE_URL` password must match `POSTGRES_PASSWORD`. -- `FRONTEND_URL` must match `PUBLIC_BASE_URL` (e.g. `http://178.131.50.201:8088`). +- `FRONTEND_URL` must match `PUBLIC_BASE_URL` (`http://wixur.ir`). - Replace JWT secrets with long random values (not `CHANGE_ME`). ### 4. Gitea repository Variables @@ -139,13 +163,13 @@ Rules: | Name | Example | Notes | |------|---------|--------| -| `REGISTRY_HOST` | `host.docker.internal:3000` | **Windows + Docker Desktop:** Docker runs in a Linux VM — `127.0.0.1` is the VM, not Gitea. Use `host.docker.internal:3000`. Also set Gitea `ROOT_URL` to match (see below). | +| `REGISTRY_HOST` | `host.docker.internal:3000` | **Windows + Docker Desktop:** Docker runs in a Linux VM — `127.0.0.1` is the VM, not Gitea. Use `host.docker.internal:3000`. Gitea `ROOT_URL` should match this so registry login from CI works. Browsers and the Linux VPS use `http://wixur.ir:3000`. | | `REGISTRY_OWNER` | `admin` | Gitea user/org owning packages | -| `PUBLIC_BASE_URL` | `http://178.131.50.201:8088` | How **users** open staging in a browser (public IP OK) | +| `PUBLIC_BASE_URL` | `https://wixur.ir` | How **users** open staging (HTTPS on 443). No trailing slash. | | `DEPLOY_SECRETS_DIR` | `C:/dyolink/secrets` | Forward slashes OK on Windows | | `CLONE_HOST` | `127.0.0.1:3000` | Git clone (runs on Windows host, not inside Docker VM) | -| `STAGING_HTTP_PORT` | `8088` | Public port (router + portproxy). Optional. | -| `STAGING_LOCAL_PORT` | `18088` | Docker bind on `127.0.0.1`. Must not be `8088` if portproxy already uses 8088. | +| `STAGING_HTTP_PORT` | `80` | Public HTTP port (portproxy). Optional; workflow default is 80. | +| `STAGING_LOCAL_PORT` | `18088` | Docker bind on `127.0.0.1`. Must not be `80` if portproxy already uses 80. | **Naming note:** Gitea rejects variable names starting with `GITEA_` or `GITHUB_`. Use `CLONE_HOST`, not `GITEA_CLONE_URL`. @@ -163,43 +187,42 @@ Create token: profile → **Settings → Applications → Generate New Token**. ### 6. Firewall (once) ```powershell +New-NetFirewallRule -DisplayName "Dyolink Staging HTTP 80" -Direction Inbound -Protocol TCP -LocalPort 80 -Action Allow +# Optional fallback while cutting over from :8088 New-NetFirewallRule -DisplayName "Dyolink Staging 8088" -Direction Inbound -Protocol TCP -LocalPort 8088 -Action Allow ``` -### 7. External access on Windows + Docker Desktop (portproxy) +### 7. Port 80 = host nginx (not portproxy) -Gitea on **:3000** runs natively on Windows. Staging nginx binds **127.0.0.1:18088** (not 8088). Windows **portproxy** then maps public **8088 → 18088**. Do **not** bind Docker on 8088 — portproxy already owns that port (`bind: access permissions` if you try). +Gitea stays on **:3000**. Docker staging nginx binds **127.0.0.1:18088**. A **Windows nginx** (the 1.29.x you already have) listens on **80** and proxies to 18088. Config: [`nginx/windows-edge-http.conf`](nginx/windows-edge-http.conf). -Run **once** in **PowerShell as Administrator** (replace the old 8088→8088 rule if you had one): +**Do not** run portproxy on port 80 at the same time — it will lose to nginx (404 / hang). Keep portproxy **8088 → 18088** as fallback. ```powershell -netsh interface portproxy delete v4tov4 listenaddress=0.0.0.0 listenport=8088 -netsh interface portproxy add v4tov4 listenaddress=0.0.0.0 listenport=8088 connectaddress=127.0.0.1 connectport=18088 -netsh interface portproxy show all -Start-Service iphlpsvc -Set-Service iphlpsvc -StartupType Automatic +# 1) Free port 80 from portproxy (nginx will bind 80) +netsh interface portproxy delete v4tov4 listenaddress=0.0.0.0 listenport=80 + +# 2) Confirm Docker staging is up +curl.exe http://127.0.0.1:18088/health + +# 3) Install windows-edge-http.conf into host nginx, then: +# nginx -t +# nginx -s reload +# (paths depend on where nginx is installed) + +# 4) Must print "healthy" (not 404) +curl.exe http://127.0.0.1/health ``` -Verify on the server: +Replace any **default_server** / leftover `server { listen 80; }` in the host nginx that returns 404, or this file will never win. -```powershell -curl http://127.0.0.1:18088/health -curl http://127.0.0.1:8088/health -``` - -From your Mac: +From another machine (after **router forward TCP 80** → this PC): ```bash -curl http://178.131.50.201:8088/health +curl http://wixur.ir/health ``` -If the public IP still fails but LAN works, add **router port forward 8088** → Windows PC (same as Gitea **3000**). - -To remove portproxy later: - -```powershell -netsh interface portproxy delete v4tov4 listenaddress=0.0.0.0 listenport=8088 -``` +**Mobinnet:** public **80** is the modem. Use **443** instead: router forward **TCP 443 → 192.168.1.100**, then `https://wixur.ir`. Let's Encrypt after mobile-data `/health` works (self-signed is enough for that test). --- @@ -240,13 +263,13 @@ Expect: `dyolink_nginx_staging`, `dyolink_backend_staging`, `dyolink_frontend_st **From browser or another machine:** ```text -http://178.131.50.201:8088 +http://wixur.ir ``` **Health check:** ```powershell -curl http://178.131.50.201:8088/api/health +curl http://wixur.ir/api/health ``` Expected: `{"status":"ok",...}` @@ -263,7 +286,7 @@ On the Windows host, from repo `infrastructure/`: 1. Create `deploy.registry.env` from [`deploy.registry.env.example`](deploy.registry.env.example) 2. Set `REGISTRY_PREFIX`, `IMAGE_TAG`, `STAGING_HTTP_PORT`, `DEPLOY_SECRETS_DIR` -3. `docker login 178.131.50.201:3000 -u ` +3. `docker login host.docker.internal:3000 -u ` (on Windows Docker Desktop; Linux prod will use `wixur.ir:3000`) 4. `docker compose -f docker-compose.registry.yml --env-file deploy.registry.env pull backend frontend` 5. `docker compose -f docker-compose.registry.yml --env-file deploy.registry.env up -d` @@ -282,8 +305,10 @@ On the Windows host, from repo `infrastructure/`: | `Missing database.staging.env` | Check `DEPLOY_SECRETS_DIR` path and file names | | `docker login` denied | Token needs package permissions; check username/secret | | Git clone fails in workflow | Set `CLONE_HOST=127.0.0.1:3000` | +| Port 80 bind forbidden / access permissions | Portproxy already owns 80. Bind Docker to `18088` and point portproxy **80 → 127.0.0.1:18088**. Free IIS if it holds 80. | +| `http://wixur.ir` unreachable / empty reply | Docker Desktop: run **portproxy** (§7). `127.0.0.1:18088/health` must work on Windows first. Router must forward **80**. | | Port 8088 bind forbidden / access permissions | Portproxy already owns 8088. Bind Docker to `18088` and point portproxy **8088 → 127.0.0.1:18088**. | -| Port 8088 unreachable from Mac / empty reply | Docker Desktop: run **portproxy** (§7). `127.0.0.1:18088/health` must work on Windows first. | +| Port 8088 unreachable from Mac / empty reply | Legacy URL. Prefer `http://wixur.ir`. Same 18088 backend; add portproxy 8088 only as fallback. | | Backend restart loop | JWT secrets still placeholder; fix `backend.staging.env` | | Backend DB auth error | `DATABASE_URL` password ≠ `POSTGRES_PASSWORD` | | `dumb-init docker-entrypoint.sh: No such file or directory` | Windows CRLF in shell scripts — fixed in Dockerfiles (rebuild images). | @@ -308,15 +333,16 @@ docker logs dyolink_frontend_staging --tail 50 | `infrastructure/deploy.registry.env.example` | Manual deploy env template | | `infrastructure/database.staging.env.example` | Postgres secrets template | | `infrastructure/backend.staging.env.example` | API secrets template | -| `infrastructure/nginx/http-only.conf` | HTTP reverse proxy for staging | +| `infrastructure/nginx/http-only.conf` | HTTP reverse proxy **inside Docker** staging | +| `infrastructure/nginx/windows-edge-http.conf` | Windows **host** nginx on port 80 → 18088 | --- -## Production (later) +## Production (Linux + tags) | Environment | Trigger | Host | |-------------|---------|------| -| Staging | Push/merge to `master` | Windows + Gitea | -| Production | Git tag `v*.*.*` | Linux + `nudentic.ir` | +| Staging | Push/merge to `master` | Windows + `https://wixur.ir` | +| Production | Git tag `v*.*.*` | Linux + `https://nudentic.ir` | -Production flow will use Docker Hub (or registry) + [`DEPLOY.md`](DEPLOY.md) — not yet wired to the same workflow. +Workflow: [`.gitea/workflows/prod-tag-deploy.yml`](../.gitea/workflows/prod-tag-deploy.yml). Do **not** push production images as `:latest`. Setup: [`DEPLOY.md`](DEPLOY.md). diff --git a/infrastructure/backend.prod.env.example b/infrastructure/backend.prod.env.example index 323cc86..3b65956 100644 --- a/infrastructure/backend.prod.env.example +++ b/infrastructure/backend.prod.env.example @@ -12,7 +12,7 @@ JWT_REFRESH_SECRET=replace_with_a_different_openssl_rand_hex_32_output JWT_REFRESH_EXPIRES_IN=30d # Must match DOMAIN in .env — used for CORS, invite links, cookies -FRONTEND_URL=https://wixur.ir +FRONTEND_URL=https://nudentic.ir # Required for HTTPS — browsers reject Secure cookies over plain HTTP COOKIE_SECURE=true @@ -20,3 +20,7 @@ COOKIE_SECURE=true # SMS (sms.ir) SMS_IR_API_KEY=CHANGE_ME_SMS_IR_API_KEY SMS_IR_TEMPLATE_ID=123456 + +# GlitchTip (Sentry SDK). Empty = disabled. Use the dyolink-backend project DSN. +# SENTRY_DSN=https://PUBLIC_KEY@errors.wixur.ir/1 +# SENTRY_ENVIRONMENT=production diff --git a/infrastructure/backend.staging.env.example b/infrastructure/backend.staging.env.example index 6ae019f..3005fb2 100644 --- a/infrastructure/backend.staging.env.example +++ b/infrastructure/backend.staging.env.example @@ -9,12 +9,16 @@ JWT_EXPIRES_IN=15m JWT_REFRESH_SECRET=another_long_random_secret_different_from_JWT_SECRET JWT_REFRESH_EXPIRES_IN=30d -# CORS, cookies, and invite links — must match how users open the app (nginx host port) -FRONTEND_URL=http://178.131.50.201:8088 +# CORS, cookies, and invite links — must match how users open the app +FRONTEND_URL=https://wixur.ir -# HTTP staging — keep false unless you terminate TLS in front of the app -COOKIE_SECURE=false +# TLS is terminated on Windows nginx :443 — cookies must be Secure +COOKIE_SECURE=true # SMS (sms.ir) SMS_IR_API_KEY=CHANGE_ME_SMS_IR_API_KEY SMS_IR_TEMPLATE_ID=123456 + +# GlitchTip (Sentry SDK). Empty = disabled. Use the dyolink-backend project DSN. +# SENTRY_DSN=https://PUBLIC_KEY@errors.wixur.ir/1 +# SENTRY_ENVIRONMENT=staging diff --git a/infrastructure/deploy.prod.env.example b/infrastructure/deploy.prod.env.example index f8adc1f..3cef703 100644 --- a/infrastructure/deploy.prod.env.example +++ b/infrastructure/deploy.prod.env.example @@ -1,15 +1,27 @@ -# Copy to infrastructure/.env on the server (not committed). +# Copy to infrastructure/.env on the Linux server (not committed). # docker compose -f docker-compose.prod.yml --env-file .env ... -DOMAIN=wixur.ir +DOMAIN=nudentic.ir + +# Gitea Container Registry (tag auto-deploy). Same packages Windows CI pushes +# via host.docker.internal:3000 — Linux pulls via this hostname. +# Format: :/ — no http:// +REGISTRY_PREFIX=wixur.ir:3000/admin + +# Image tag — CI sets this per release (v1.0.1). Do not use latest for Gitea prod +# (staging already owns :latest with the wixur.ir frontend bake). +TAG=v1.0.1 + +# Hub-only fallback: comment out REGISTRY_PREFIX and use: +# REGISTRY_PREFIX=dyolink +# TAG=latest DOCKER_USERNAME=dyolink -TAG=latest # Let's Encrypt — certificate issuance and renewal notices LETSENCRYPT_EMAIL=rameen.naghdi@gmail.com -# Optional: extra hostnames on the same cert (space-separated), e.g. www.wixur.ir -# CERTBOT_EXTRA_DOMAINS=www.wixur.ir +# Optional: extra hostnames on the same cert (space-separated) +# CERTBOT_EXTRA_DOMAINS=www.nudentic.ir # Optional: use Let's Encrypt staging while testing (avoids rate limits) # LETSENCRYPT_STAGING=1 diff --git a/infrastructure/deploy.registry.env.example b/infrastructure/deploy.registry.env.example index 61c4389..b45fae9 100644 --- a/infrastructure/deploy.registry.env.example +++ b/infrastructure/deploy.registry.env.example @@ -6,14 +6,15 @@ # --- Registry boundary (swap when moving Gitea → Docker Hub) --- # Gitea: REGISTRY_PREFIX = :/ # Hub: REGISTRY_PREFIX = docker.io/ (or your username for implicit hub) -REGISTRY_PREFIX=178.131.50.201:3000/yourgiteauser +# Windows CI pushes via host.docker.internal:3000; browsers/Linux use wixur.ir:3000 (same Gitea). +REGISTRY_PREFIX=wixur.ir:3000/yourgiteauser # Short git SHA from CI, or "latest" after a manual pull of :latest IMAGE_TAG=latest -# Public URL port (router + Windows portproxy listen here) -STAGING_HTTP_PORT=8088 -# Docker nginx bind on localhost only (must differ from 8088 when portproxy owns 8088) +# Public HTTP port (router + Windows portproxy listen here) — 80 so users open http://wixur.ir +STAGING_HTTP_PORT=80 +# Docker nginx bind on localhost only (must differ from 80 if portproxy owns 80) STAGING_LOCAL_PORT=18088 # Absolute path on the server where database.staging.env and backend.staging.env live. diff --git a/infrastructure/docker-compose.prod.yml b/infrastructure/docker-compose.prod.yml index 7c00395..31748ff 100644 --- a/infrastructure/docker-compose.prod.yml +++ b/infrastructure/docker-compose.prod.yml @@ -1,13 +1,11 @@ -# Production stack — pull images from Docker Hub, HTTPS via Let's Encrypt (certbot). +# Production stack — pull backend/frontend from Gitea (or Docker Hub fallback). +# HTTPS via Let's Encrypt (certbot) on this host only (nudentic.ir). # -# Server setup (minimal): -# 1. Copy deploy.prod.env.example → .env (DOMAIN, DOCKER_USERNAME, LETSENCRYPT_EMAIL) -# 2. Copy secrets/*.example → ../secrets/ (database.env, backend.env) — outside git -# 3. docker login (private Docker Hub images) -# 4. ./scripts/init-letsencrypt.sh (first time only) -# 5. docker compose -f docker-compose.prod.yml --env-file .env up -d +# Gitea tag deploy: set REGISTRY_PREFIX=wixur.ir:3000/ and TAG=v1.0.1 in .env +# Hub fallback: omit REGISTRY_PREFIX, set DOCKER_USERNAME=dyolink (image dyolink/dyolink-*). # -# Updates: docker compose pull && docker compose up -d +# First SSL: ./scripts/init-letsencrypt.sh then up -d +# Tag updates: CI runs scripts/prod-remote-deploy.sh (or pull + up locally) name: dyolink-prod @@ -39,7 +37,7 @@ services: start_period: 40s backend: - image: ${DOCKER_USERNAME}/dyolink-backend:${TAG:-latest} + image: ${REGISTRY_PREFIX:-dyolink}/dyolink-backend:${TAG:-latest} container_name: dyolink_backend_prod depends_on: postgres: @@ -50,6 +48,8 @@ services: NODE_ENV: production TZ: UTC PORT: "3000" + SENTRY_ENVIRONMENT: production + SENTRY_RELEASE: ${TAG:-latest} expose: - "3000" networks: @@ -68,7 +68,7 @@ services: start_period: 60s frontend: - image: ${DOCKER_USERNAME}/dyolink-frontend:${TAG:-latest} + image: ${REGISTRY_PREFIX:-dyolink}/dyolink-frontend:${TAG:-latest} container_name: dyolink_frontend_prod depends_on: - backend @@ -88,7 +88,8 @@ services: max-size: "10m" max-file: "3" healthcheck: - test: ["CMD", "node", "-e", "require('http').get('http://127.0.0.1:3000/', (r) => {if(r.statusCode!==200)process.exit(1)})"] + # Next.js `/` redirects to `/en` — accept 2xx/3xx. + test: ["CMD", "node", "-e", "require('http').get('http://127.0.0.1:3000/', (r) => { process.exit(r.statusCode >= 200 && r.statusCode < 400 ? 0 : 1); }).on('error', () => process.exit(1))"] interval: 30s timeout: 10s retries: 3 diff --git a/infrastructure/docker-compose.registry.yml b/infrastructure/docker-compose.registry.yml index 7c708d8..b420351 100644 --- a/infrastructure/docker-compose.registry.yml +++ b/infrastructure/docker-compose.registry.yml @@ -2,7 +2,7 @@ # No backend/frontend source on the deployment host except this compose file + config + secrets. # # Required env (see deploy.registry.env.example): -# REGISTRY_PREFIX e.g. 178.131.50.201:3000/yourgiteauser (no protocol, no trailing slash) +# REGISTRY_PREFIX e.g. host.docker.internal:3000/admin (Windows CI) or wixur.ir:3000/admin (no protocol) # IMAGE_TAG short sha or "latest" (CI sets this per deploy) # Optional: # DEPLOY_SECRETS_DIR absolute path on the server to database/backend *.env files (see below) @@ -47,6 +47,8 @@ services: NODE_ENV: production TZ: UTC PORT: "3000" + SENTRY_ENVIRONMENT: staging + SENTRY_RELEASE: ${IMAGE_TAG:-latest} expose: - "3000" networks: @@ -91,8 +93,8 @@ services: frontend: condition: service_healthy ports: - # Bind a private localhost port — Windows portproxy already owns public 8088. - # See STAGING-DEPLOY.md §7: portproxy 0.0.0.0:8088 → 127.0.0.1:18088 + # Bind a private localhost port — Windows portproxy owns public 80 (http://wixur.ir). + # See STAGING-DEPLOY.md §7: portproxy 0.0.0.0:80 → 127.0.0.1:18088 - "127.0.0.1:${STAGING_LOCAL_PORT:-18088}:80" volumes: - ./nginx/http-only.conf:/etc/nginx/conf.d/default.conf:ro diff --git a/infrastructure/docker-compose.staging.yml b/infrastructure/docker-compose.staging.yml index 13ed5ca..cd1c9fb 100644 --- a/infrastructure/docker-compose.staging.yml +++ b/infrastructure/docker-compose.staging.yml @@ -43,6 +43,7 @@ services: NODE_ENV: production TZ: UTC PORT: "3000" + SENTRY_ENVIRONMENT: staging expose: - "3000" networks: @@ -60,9 +61,11 @@ services: context: ../frontend dockerfile: Dockerfile args: - NEXT_PUBLIC_API_URL: ${STAGING_NEXT_PUBLIC_API_URL:-http://178.131.50.201:8088/api} - NEXT_PUBLIC_APP_URL: ${STAGING_NEXT_PUBLIC_APP_URL:-http://178.131.50.201:8088} + NEXT_PUBLIC_API_URL: ${STAGING_NEXT_PUBLIC_API_URL:-http://wixur.ir/api} + NEXT_PUBLIC_APP_URL: ${STAGING_NEXT_PUBLIC_APP_URL:-http://wixur.ir} NEXT_PUBLIC_APP_NAME: ${STAGING_NEXT_PUBLIC_APP_NAME:-Dyolink} + NEXT_PUBLIC_SENTRY_DSN: ${NEXT_PUBLIC_SENTRY_DSN:-} + NEXT_PUBLIC_SENTRY_ENVIRONMENT: staging container_name: dyolink_frontend_staging depends_on: - backend diff --git a/infrastructure/env.staging.example b/infrastructure/env.staging.example index afa238a..895ed65 100644 --- a/infrastructure/env.staging.example +++ b/infrastructure/env.staging.example @@ -1,13 +1,13 @@ # Copy to .env.staging next to docker-compose.staging.yml (optional). # Used only for compose variable substitution (build args, host port). -STAGING_HTTP_PORT=8088 +STAGING_HTTP_PORT=80 # Public URLs baked into the frontend image at build time — must match how users open the app. -STAGING_NEXT_PUBLIC_API_URL=http://178.131.50.201:8088/api -STAGING_NEXT_PUBLIC_APP_URL=http://178.131.50.201:8088 +STAGING_NEXT_PUBLIC_API_URL=http://wixur.ir/api +STAGING_NEXT_PUBLIC_APP_URL=http://wixur.ir STAGING_NEXT_PUBLIC_APP_NAME=Dyolink -# Change the IP/port if your server address differs. +# Must match how users open staging (http://wixur.ir). # Registry / pull-only deploy (see deploy.registry.env.example + docker-compose.registry.yml). diff --git a/infrastructure/nginx/windows-edge-http.conf b/infrastructure/nginx/windows-edge-http.conf new file mode 100644 index 0000000..36044a1 --- /dev/null +++ b/infrastructure/nginx/windows-edge-http.conf @@ -0,0 +1,78 @@ +# Windows host nginx.conf — paste over +# C:\tools\nginx-1.29.5\conf\nginx.conf +# +# :80 — LAN / 127.0.0.1 only (Mobinnet keeps public :80 for the modem) +# :443 — public https://wixur.ir (router must forward 443 → this PC) +# +# Put Let's Encrypt PEMs in conf\ssl\ (win-acme --pemfilesname wixur): +# ssl\wixur-chain.pem +# ssl\wixur-key.pem +# +# After save: nginx -t then restart nginx +# curl.exe https://wixur.ir/health → healthy +# (curl to 127.0.0.1:443 fails name-check; cert is for wixur.ir) + +#user nobody; +worker_processes 1; + +events { + worker_connections 1024; +} + + +http { + include mime.types; + default_type application/octet-stream; + + sendfile on; + keepalive_timeout 65; + client_max_body_size 50M; + + # Shared proxy to Docker staging + map $http_upgrade $connection_upgrade { + default upgrade; + '' close; + } + + server { + listen 80 default_server; + listen [::]:80 default_server; + server_name wixur.ir www.wixur.ir localhost 127.0.0.1; + + location / { + proxy_pass http://127.0.0.1:18088; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 300; + proxy_connect_timeout 300; + } + } + + server { + listen 443 ssl; + listen [::]:443 ssl; + server_name wixur.ir www.wixur.ir localhost 127.0.0.1; + + ssl_certificate ssl/wixur-chain.pem; + ssl_certificate_key ssl/wixur-key.pem; + ssl_protocols TLSv1.2 TLSv1.3; + + location / { + proxy_pass http://127.0.0.1:18088; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 300; + proxy_connect_timeout 300; + } + } +} diff --git a/infrastructure/nginx/windows-ssl-letsencrypt.snippet.conf b/infrastructure/nginx/windows-ssl-letsencrypt.snippet.conf new file mode 100644 index 0000000..7883ea3 --- /dev/null +++ b/infrastructure/nginx/windows-ssl-letsencrypt.snippet.conf @@ -0,0 +1,4 @@ +# After win-acme writes Let's Encrypt PEMs into conf/ssl/: +# ssl_certificate ssl/wixur-chain.pem; +# ssl_certificate_key ssl/wixur-key.pem; +# (exact filenames: dir C:\tools\nginx-1.29.5\conf\ssl ) diff --git a/infrastructure/scripts/prod-remote-deploy.sh b/infrastructure/scripts/prod-remote-deploy.sh new file mode 100755 index 0000000..6a07948 --- /dev/null +++ b/infrastructure/scripts/prod-remote-deploy.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +# Run on the Linux production host (CI SSHs here after pushing :v* images). +# Usage: ./prod-remote-deploy.sh v1.0.1 +set -euo pipefail + +TAG="${1:?usage: prod-remote-deploy.sh v1.0.1}" +INFRA="${PROD_INFRA_DIR:-/opt/dyolink/infrastructure}" +cd "$INFRA" + +if [ ! -f .env ]; then + echo "Missing $INFRA/.env — copy deploy.prod.env.example and set DOMAIN=nudentic.ir REGISTRY_PREFIX=wixur.ir:3000/" + exit 1 +fi + +if ! grep -q '^REGISTRY_PREFIX=.\+' .env; then + echo "Set REGISTRY_PREFIX in .env (Gitea registry, e.g. wixur.ir:3000/admin). Do not use Docker Hub :latest for tag deploys." + exit 1 +fi + +export TAG + +# Windows Gitea often RSTs concurrent or long pulls (connection reset by peer). +# Pull one image at a time with backoff so a flake does not fail the whole tag. +pull_one() { + local service="$1" + local attempt=1 + local max=5 + local delay=8 + while [ "$attempt" -le "$max" ]; do + echo "Pulling $service :$TAG (attempt $attempt/$max)" + if docker compose -f docker-compose.prod.yml --env-file .env pull "$service"; then + return 0 + fi + if [ "$attempt" -eq "$max" ]; then + echo "Failed to pull $service after $max attempts" + return 1 + fi + echo "Pull of $service failed; retrying in ${delay}s..." + sleep "$delay" + delay=$((delay * 2)) + attempt=$((attempt + 1)) + done +} + +echo "Pulling backend/frontend :$TAG from Gitea (REGISTRY_PREFIX in .env)" +pull_one backend +pull_one frontend +docker compose -f docker-compose.prod.yml --env-file .env up -d +echo "=== Status ===" +docker compose -f docker-compose.prod.yml --env-file .env ps +echo "Health: https://nudentic.ir/api/health" diff --git a/infrastructure/scripts/windows-renew-wixur-cert.cmd b/infrastructure/scripts/windows-renew-wixur-cert.cmd new file mode 100644 index 0000000..08e30ce --- /dev/null +++ b/infrastructure/scripts/windows-renew-wixur-cert.cmd @@ -0,0 +1,11 @@ +@echo off +REM Run as Administrator. Stops nginx so win-acme can bind 443 (TLS-ALPN), +REM renews, then starts nginx again. Point the win-acme scheduled task here +REM instead of calling wacs.exe directly. +cd /d C:\tools\nginx-1.29.5 +taskkill /F /IM nginx.exe >nul 2>&1 +timeout /t 2 /nobreak >nul +cd /d C:\tools\win-acme +wacs.exe --renew --closeonfinish +cd /d C:\tools\nginx-1.29.5 +start "" nginx.exe