diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml new file mode 100644 index 00000000..44d1343c --- /dev/null +++ b/.github/workflows/e2e.yml @@ -0,0 +1,139 @@ +name: E2E Tracing Tests + +on: + push: + branches: + - master + - release/** + pull_request: + +jobs: + tracing-e2e: + name: Tracing E2E Tests + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Setup Elixir and Erlang + uses: erlef/setup-beam@e6d7c94229049569db56a7ad5a540c051a010af9 + with: + elixir-version: "1.18" + otp-version: "27.2" + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + + - name: Cache Elixir dependencies + uses: actions/cache@v4 + with: + path: | + deps + _build + test_integrations/phoenix_app/deps + test_integrations/phoenix_app/_build + key: ${{ runner.os }}-elixir-1.18-otp-27.2-mix-${{ hashFiles('**/mix.lock') }} + restore-keys: | + ${{ runner.os }}-elixir-1.18-otp-27.2-mix- + + - name: Cache Node.js dependencies + uses: actions/cache@v4 + with: + path: | + test_integrations/tracing/node_modules + test_integrations/tracing/svelte_mini/node_modules + key: ${{ runner.os }}-node-20-${{ hashFiles('test_integrations/tracing/package-lock.json', 'test_integrations/tracing/svelte_mini/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node-20- + + - name: Install main project dependencies + run: mix deps.get + + - name: Install Phoenix app dependencies + working-directory: test_integrations/phoenix_app + run: mix deps.get + + - name: Compile Phoenix app + working-directory: test_integrations/phoenix_app + run: mix compile + + - name: Install tracing test npm dependencies + working-directory: test_integrations/tracing + run: npm install + + - name: Install Svelte app dependencies + working-directory: test_integrations/tracing/svelte_mini + run: npm install + + - name: Install Playwright browsers + working-directory: test_integrations/tracing + run: npx playwright install --with-deps --only-shell chromium + + - name: Start Phoenix server + working-directory: test_integrations/phoenix_app + run: | + rm -f tmp/sentry_debug_events.log + SENTRY_E2E_TEST_MODE=true mix phx.server & + echo $! > /tmp/phoenix.pid + echo "Phoenix server started with PID $(cat /tmp/phoenix.pid)" + + - name: Start Svelte server + working-directory: test_integrations/tracing/svelte_mini + run: | + SENTRY_E2E_SVELTE_APP_PORT=4001 npm run dev & + echo $! > /tmp/svelte.pid + echo "Svelte server started with PID $(cat /tmp/svelte.pid)" + + - name: Wait for Phoenix server + run: | + echo "Waiting for Phoenix server at http://localhost:4000/health..." + timeout 60 bash -c 'until curl -s http://localhost:4000/health > /dev/null 2>&1; do echo "Waiting..."; sleep 2; done' + echo "Phoenix server is ready!" + curl -s http://localhost:4000/health + + - name: Wait for Svelte server + run: | + echo "Waiting for Svelte server at http://localhost:4001/health..." + timeout 60 bash -c 'until curl -s http://localhost:4001/health > /dev/null 2>&1; do echo "Waiting..."; sleep 2; done' + echo "Svelte server is ready!" + curl -s http://localhost:4001/health + + - name: Run Playwright tests + working-directory: test_integrations/tracing + env: + SENTRY_E2E_PHOENIX_APP_URL: http://localhost:4000 + SENTRY_E2E_SVELTE_APP_URL: http://localhost:4001 + SENTRY_E2E_SERVERS_RUNNING: "true" + run: npx playwright test --reporter=list + + - name: Upload Playwright report + uses: actions/upload-artifact@v4 + if: failure() + with: + name: playwright-report + path: test_integrations/tracing/playwright-report/ + retention-days: 7 + + - name: Upload test screenshots + uses: actions/upload-artifact@v4 + if: failure() + with: + name: test-screenshots + path: test_integrations/tracing/test-results/ + retention-days: 7 + + - name: Show Phoenix server logs + if: failure() + run: | + echo "=== Sentry debug events log ===" + cat test_integrations/phoenix_app/tmp/sentry_debug_events.log 2>/dev/null || echo "No events logged" + + - name: Cleanup servers + if: always() + run: | + [ -f /tmp/phoenix.pid ] && kill $(cat /tmp/phoenix.pid) 2>/dev/null || true + [ -f /tmp/svelte.pid ] && kill $(cat /tmp/svelte.pid) 2>/dev/null || true diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2d76e256..fca5736d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -54,7 +54,7 @@ jobs: uses: actions/checkout@v4 - name: Setup Elixir and Erlang - uses: erlef/setup-beam@v1 + uses: erlef/setup-beam@e6d7c94229049569db56a7ad5a540c051a010af9 with: elixir-version: ${{ matrix.elixir }} otp-version: ${{ matrix.otp }} @@ -117,6 +117,8 @@ jobs: run: mix test - name: Run integration tests + env: + SKIP_TRACING_E2E: "true" run: mix test.integrations - name: Cache Dialyzer PLT diff --git a/.gitignore b/.gitignore index 39814143..4b41496f 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ test_integrations/phoenix_app/db test_integrations/*/_build test_integrations/*/deps +test_integrations/*/test-results/ diff --git a/test_integrations/phoenix_app/config/config.exs b/test_integrations/phoenix_app/config/config.exs index 68901111..66dd0fc3 100644 --- a/test_integrations/phoenix_app/config/config.exs +++ b/test_integrations/phoenix_app/config/config.exs @@ -65,6 +65,14 @@ config :opentelemetry, span_processor: {Sentry.OpenTelemetry.SpanProcessor, []} config :opentelemetry, sampler: {Sentry.OpenTelemetry.Sampler, [drop: ["Elixir.Oban.Stager process"]]} +# Configure OpenTelemetry to use Sentry propagator for distributed tracing +config :opentelemetry, + text_map_propagators: [ + :trace_context, + :baggage, + Sentry.OpenTelemetry.Propagator + ] + # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. import_config "#{config_env()}.exs" diff --git a/test_integrations/phoenix_app/config/runtime.exs b/test_integrations/phoenix_app/config/runtime.exs index 6aa62dab..24cad9a7 100644 --- a/test_integrations/phoenix_app/config/runtime.exs +++ b/test_integrations/phoenix_app/config/runtime.exs @@ -20,6 +20,21 @@ if System.get_env("PHX_SERVER") do config :phoenix_app, PhoenixAppWeb.Endpoint, server: true end +# For e2e tracing tests, use the TestClient to log events to a file +# This must be in runtime.exs because the env var is set at runtime, not compile time +if System.get_env("SENTRY_E2E_TEST_MODE") == "true" do + config :sentry, + dsn: "https://public@sentry.example.com/1", + client: PhoenixApp.TestClient +else + # Allow runtime configuration of Sentry DSN and environment + if dsn = System.get_env("SENTRY_DSN") do + config :sentry, + dsn: dsn, + environment_name: System.get_env("SENTRY_ENVIRONMENT") || config_env() + end +end + if config_env() == :prod do # database_url = # System.get_env("DATABASE_URL") || diff --git a/test_integrations/phoenix_app/lib/phoenix_app/test_client.ex b/test_integrations/phoenix_app/lib/phoenix_app/test_client.ex new file mode 100644 index 00000000..43a33ac8 --- /dev/null +++ b/test_integrations/phoenix_app/lib/phoenix_app/test_client.ex @@ -0,0 +1,86 @@ +defmodule PhoenixApp.TestClient do + @moduledoc """ + A test Sentry client that logs envelopes to a file for e2e test validation. + + This client mimics the behavior of Sentry::DebugTransport in sentry-ruby, + logging all envelopes to a file that can be read by Playwright tests. + """ + + require Logger + + @behaviour Sentry.HTTPClient + + @impl true + def post(_url, _headers, body) do + log_envelope(body) + + # Return success response + {:ok, 200, [], ~s({"id":"test-event-id"})} + end + + defp log_envelope(body) when is_binary(body) do + log_file = Path.join([File.cwd!(), "tmp", "sentry_debug_events.log"]) + + # Ensure the tmp directory exists + log_dir = Path.dirname(log_file) + File.mkdir_p!(log_dir) + + # Parse the envelope binary to extract events and headers + case parse_envelope(body) do + {:ok, envelope_data} -> + # Write the envelope data as JSON + json = Jason.encode!(envelope_data) + File.write!(log_file, json <> "\n", [:append]) + + {:error, reason} -> + Logger.warning("Failed to parse envelope for logging: #{inspect(reason)}") + end + rescue + error -> + Logger.warning("Failed to log envelope: #{inspect(error)}") + end + + defp parse_envelope(body) when is_binary(body) do + # Envelope format: header\nitem_header\nitem_payload[\nitem_header\nitem_payload...] + # See: https://develop.sentry.dev/sdk/envelopes/ + + lines = String.split(body, "\n") + + with {:ok, header_line, rest} <- get_first_line(lines), + {:ok, envelope_headers} <- Jason.decode(header_line), + {:ok, items} <- parse_items(rest) do + + envelope = %{ + headers: envelope_headers, + items: items + } + + {:ok, envelope} + else + error -> {:error, error} + end + end + + defp get_first_line([first | rest]), do: {:ok, first, rest} + defp get_first_line([]), do: {:error, :empty_envelope} + + defp parse_items(lines), do: parse_items(lines, []) + + defp parse_items([], acc), do: {:ok, Enum.reverse(acc)} + + defp parse_items([item_header_line, payload_line | rest], acc) do + with {:ok, _item_header} <- Jason.decode(item_header_line), + {:ok, payload} <- Jason.decode(payload_line) do + parse_items(rest, [payload | acc]) + else + _error -> + # Skip malformed items + parse_items(rest, acc) + end + end + + defp parse_items([_single_line], acc) do + # Handle trailing empty line + {:ok, Enum.reverse(acc)} + end +end diff --git a/test_integrations/phoenix_app/lib/phoenix_app_web/controllers/page_controller.ex b/test_integrations/phoenix_app/lib/phoenix_app_web/controllers/page_controller.ex index 8b31383c..c80e3800 100644 --- a/test_integrations/phoenix_app/lib/phoenix_app_web/controllers/page_controller.ex +++ b/test_integrations/phoenix_app/lib/phoenix_app_web/controllers/page_controller.ex @@ -48,4 +48,35 @@ defmodule PhoenixAppWeb.PageController do render(conn, :home, layout: false) end + + # E2E tracing test endpoints + + def api_error(_conn, _params) do + raise ArithmeticError, "bad argument in arithmetic expression" + end + + def health(conn, _params) do + json(conn, %{status: "ok"}) + end + + def api_data(conn, _params) do + Tracer.with_span "fetch_data" do + users = Repo.all(User) + + Tracer.with_span "process_data" do + user_count = length(users) + + first_user = Repo.get(User, 1) + + json(conn, %{ + message: "Data fetched successfully", + data: %{ + user_count: user_count, + first_user: if(first_user, do: first_user.name, else: nil), + timestamp: DateTime.utc_now() |> DateTime.to_iso8601() + } + }) + end + end + end end diff --git a/test_integrations/phoenix_app/lib/phoenix_app_web/endpoint.ex b/test_integrations/phoenix_app/lib/phoenix_app_web/endpoint.ex index cbc6c40a..894cc5c2 100644 --- a/test_integrations/phoenix_app/lib/phoenix_app_web/endpoint.ex +++ b/test_integrations/phoenix_app/lib/phoenix_app_web/endpoint.ex @@ -44,6 +44,8 @@ defmodule PhoenixAppWeb.Endpoint do plug Plug.RequestId plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint] + plug :cors + plug Plug.Parsers, parsers: [:urlencoded, :multipart, :json], pass: ["*/*"], @@ -53,4 +55,22 @@ defmodule PhoenixAppWeb.Endpoint do plug Plug.Head plug Plug.Session, @session_options plug PhoenixAppWeb.Router + + # CORS plug for e2e tests - allows the Svelte frontend to make + # cross-origin requests to the Phoenix backend + defp cors(conn, _opts) do + conn + |> put_resp_header("access-control-allow-origin", "*") + |> put_resp_header("access-control-allow-methods", "GET, POST, PUT, DELETE, OPTIONS") + |> put_resp_header("access-control-allow-headers", "content-type, sentry-trace, baggage") + |> handle_preflight() + end + + defp handle_preflight(%{method: "OPTIONS"} = conn) do + conn + |> send_resp(200, "") + |> halt() + end + + defp handle_preflight(conn), do: conn end diff --git a/test_integrations/phoenix_app/lib/phoenix_app_web/router.ex b/test_integrations/phoenix_app/lib/phoenix_app_web/router.ex index 597506b1..66a07292 100644 --- a/test_integrations/phoenix_app/lib/phoenix_app_web/router.ex +++ b/test_integrations/phoenix_app/lib/phoenix_app_web/router.ex @@ -12,6 +12,14 @@ defmodule PhoenixAppWeb.Router do pipeline :api do plug :accepts, ["json"] + plug :put_cors_headers + end + + defp put_cors_headers(conn, _opts) do + conn + |> put_resp_header("access-control-allow-origin", "*") + |> put_resp_header("access-control-allow-methods", "GET, POST, PUT, DELETE, OPTIONS") + |> put_resp_header("access-control-allow-headers", "content-type, authorization, sentry-trace, baggage") end scope "/", PhoenixAppWeb do @@ -32,6 +40,15 @@ defmodule PhoenixAppWeb.Router do live "/users/:id/show/edit", UserLive.Show, :edit end + # For e2e DT tests with a front-end app + scope "/", PhoenixAppWeb do + pipe_through :api + + get "/error", PageController, :api_error + get "/health", PageController, :health + get "/api/data", PageController, :api_data + end + # Other scopes may use custom stacks. # scope "/api", PhoenixAppWeb do # pipe_through :api diff --git a/test_integrations/tracing/package-lock.json b/test_integrations/tracing/package-lock.json new file mode 100644 index 00000000..3ba888b0 --- /dev/null +++ b/test_integrations/tracing/package-lock.json @@ -0,0 +1,105 @@ +{ + "name": "tracing-e2e-tests", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "tracing-e2e-tests", + "version": "1.0.0", + "devDependencies": { + "@playwright/test": "^1.48.0", + "@types/node": "^22.19.2", + "typescript": "^5.6.0" + } + }, + "node_modules/@playwright/test": { + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.1.tgz", + "integrity": "sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==", + "dev": true, + "dependencies": { + "playwright": "1.56.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@types/node": { + "version": "22.19.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.2.tgz", + "integrity": "sha512-LPM2G3Syo1GLzXLGJAKdqoU35XvrWzGJ21/7sgZTUpbkBaOasTj8tjwn6w+hCkqaa1TfJ/w67rJSwYItlJ2mYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright": { + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.1.tgz", + "integrity": "sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==", + "dev": true, + "dependencies": { + "playwright-core": "1.56.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.1.tgz", + "integrity": "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true + } + } +} diff --git a/test_integrations/tracing/package.json b/test_integrations/tracing/package.json new file mode 100644 index 00000000..2233f34a --- /dev/null +++ b/test_integrations/tracing/package.json @@ -0,0 +1,15 @@ +{ + "name": "tracing-e2e-tests", + "version": "1.0.0", + "type": "module", + "scripts": { + "test": "playwright test", + "test:debug": "playwright test --debug", + "test:ui": "playwright test --ui" + }, + "devDependencies": { + "@playwright/test": "^1.48.0", + "@types/node": "^22.19.2", + "typescript": "^5.6.0" + } +} diff --git a/test_integrations/tracing/playwright.config.ts b/test_integrations/tracing/playwright.config.ts new file mode 100644 index 00000000..622ffb09 --- /dev/null +++ b/test_integrations/tracing/playwright.config.ts @@ -0,0 +1,54 @@ +import { defineConfig, devices } from "@playwright/test"; + +const PHOENIX_URL = + process.env.SENTRY_E2E_PHOENIX_APP_URL || "http://localhost:4000"; +const SVELTE_URL = + process.env.SENTRY_E2E_SVELTE_APP_URL || "http://localhost:4001"; + +// When servers are started externally (e.g., in CI workflow steps), skip webServer config +const serversRunningExternally = process.env.SENTRY_E2E_SERVERS_RUNNING === "true"; + +export default defineConfig({ + testDir: "./tests", + fullyParallel: false, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: 1, + reporter: "list", + + use: { + baseURL: SVELTE_URL, + trace: "on-first-retry", + screenshot: "only-on-failure", + }, + + projects: [ + { + name: "chromium", + use: { + ...devices["Desktop Chrome"], + headless: true, + }, + }, + ], + + // Skip webServer when servers are managed externally (better CI debugging) + ...(serversRunningExternally + ? {} + : { + webServer: [ + { + command: + 'cd ../phoenix_app && rm -f tmp/sentry_debug_events.log && SENTRY_E2E_TEST_MODE=true mix phx.server', + url: `${PHOENIX_URL}/health`, + reuseExistingServer: true + }, + { + command: + 'cd svelte_mini && SENTRY_E2E_SVELTE_APP_PORT=4001 npm run dev', + url: `${SVELTE_URL}/health`, + reuseExistingServer: true + }, + ], + }), +}); diff --git a/test_integrations/tracing/svelte_mini/index.html b/test_integrations/tracing/svelte_mini/index.html new file mode 100644 index 00000000..88e70534 --- /dev/null +++ b/test_integrations/tracing/svelte_mini/index.html @@ -0,0 +1,15 @@ + + + + + + + Svelte Mini App + + + +
+ + + + diff --git a/test_integrations/tracing/svelte_mini/package-lock.json b/test_integrations/tracing/svelte_mini/package-lock.json new file mode 100644 index 00000000..59ce8d28 --- /dev/null +++ b/test_integrations/tracing/svelte_mini/package-lock.json @@ -0,0 +1,1421 @@ +{ + "name": "svelte-mini", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "svelte-mini", + "version": "1.0.0", + "dependencies": { + "@sentry/svelte": "~10.5" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^5.0.0", + "svelte": "~5.3.0", + "vite": "^6.0.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.5.tgz", + "integrity": "sha512-iDGS/h7D8t7tvZ1t6+WPK04KD0MwzLZrG0se1hzBjSi5fyxlsiggoJHwh18PCFNn7tG43OWb6pdZ6Y+rMlmyNQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.5.tgz", + "integrity": "sha512-wrSAViWvZHBMMlWk6EJhvg8/rjxzyEhEdgfMMjREHEq11EtJ6IP6yfcCH57YAEca2Oe3FNCE9DSTgU70EIGmVw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.5.tgz", + "integrity": "sha512-S87zZPBmRO6u1YXQLwpveZm4JfPpAa6oHBX7/ghSiGH3rz/KDgAu1rKdGutV+WUI6tKDMbaBJomhnT30Y2t4VQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.5.tgz", + "integrity": "sha512-YTbnsAaHo6VrAczISxgpTva8EkfQus0VPEVJCEaboHtZRIb6h6j0BNxRBOwnDciFTZLDPW5r+ZBmhL/+YpTZgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.5.tgz", + "integrity": "sha512-1T8eY2J8rKJWzaznV7zedfdhD1BqVs1iqILhmHDq/bqCUZsrMt+j8VCTHhP0vdfbHK3e1IQ7VYx3jlKqwlf+vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.5.tgz", + "integrity": "sha512-sHTiuXyBJApxRn+VFMaw1U+Qsz4kcNlxQ742snICYPrY+DDL8/ZbaC4DVIB7vgZmp3jiDaKA0WpBdP0aqPJoBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.5.tgz", + "integrity": "sha512-dV3T9MyAf0w8zPVLVBptVlzaXxka6xg1f16VAQmjg+4KMSTWDvhimI/Y6mp8oHwNrmnmVl9XxJ/w/mO4uIQONA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.5.tgz", + "integrity": "sha512-wIGYC1x/hyjP+KAu9+ewDI+fi5XSNiUi9Bvg6KGAh2TsNMA3tSEs+Sh6jJ/r4BV/bx/CyWu2ue9kDnIdRyafcQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.5.tgz", + "integrity": "sha512-Y+qVA0D9d0y2FRNiG9oM3Hut/DgODZbU9I8pLLPwAsU0tUKZ49cyV1tzmB/qRbSzGvY8lpgGkJuMyuhH7Ma+Vg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.5.tgz", + "integrity": "sha512-juaC4bEgJsyFVfqhtGLz8mbopaWD+WeSOYr5E16y+1of6KQjc0BpwZLuxkClqY1i8sco+MdyoXPNiCkQou09+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.5.tgz", + "integrity": "sha512-rIEC0hZ17A42iXtHX+EPJVL/CakHo+tT7W0pbzdAGuWOt2jxDFh7A/lRhsNHBcqL4T36+UiAgwO8pbmn3dE8wA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.5.tgz", + "integrity": "sha512-T7l409NhUE552RcAOcmJHj3xyZ2h7vMWzcwQI0hvn5tqHh3oSoclf9WgTl+0QqffWFG8MEVZZP1/OBglKZx52Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.5.tgz", + "integrity": "sha512-7OK5/GhxbnrMcxIFoYfhV/TkknarkYC1hqUw1wU2xUN3TVRLNT5FmBv4KkheSG2xZ6IEbRAhTooTV2+R5Tk0lQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.5.tgz", + "integrity": "sha512-GwuDBE/PsXaTa76lO5eLJTyr2k8QkPipAyOrs4V/KJufHCZBJ495VCGJol35grx9xryk4V+2zd3Ri+3v7NPh+w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.5.tgz", + "integrity": "sha512-IAE1Ziyr1qNfnmiQLHBURAD+eh/zH1pIeJjeShleII7Vj8kyEm2PF77o+lf3WTHDpNJcu4IXJxNO0Zluro8bOw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.5.tgz", + "integrity": "sha512-Pg6E+oP7GvZ4XwgRJBuSXZjcqpIW3yCBhK4BcsANvb47qMvAbCjR6E+1a/U2WXz1JJxp9/4Dno3/iSJLcm5auw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.5.tgz", + "integrity": "sha512-txGtluxDKTxaMDzUduGP0wdfng24y1rygUMnmlUJ88fzCCULCLn7oE5kb2+tRB+MWq1QDZT6ObT5RrR8HFRKqg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.5.tgz", + "integrity": "sha512-3DFiLPnTxiOQV993fMc+KO8zXHTcIjgaInrqlG8zDp1TlhYl6WgrOHuJkJQ6M8zHEcntSJsUp1XFZSY8C1DYbg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.5.tgz", + "integrity": "sha512-nggc/wPpNTgjGg75hu+Q/3i32R00Lq1B6N1DO7MCU340MRKL3WZJMjA9U4K4gzy3dkZPXm9E1Nc81FItBVGRlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.5.tgz", + "integrity": "sha512-U/54pTbdQpPLBdEzCT6NBCFAfSZMvmjr0twhnD9f4EIvlm9wy3jjQ38yQj1AGznrNO65EWQMgm/QUjuIVrYF9w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.5.tgz", + "integrity": "sha512-2NqKgZSuLH9SXBBV2dWNRCZmocgSOx8OJSdpRaEcRlIfX8YrKxUT6z0F1NpvDVhOsl190UFTRh2F2WDWWCYp3A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.5.tgz", + "integrity": "sha512-JRpZUhCfhZ4keB5v0fe02gQJy05GqboPOaxvjugW04RLSYYoB/9t2lx2u/tMs/Na/1NXfY8QYjgRljRpN+MjTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sentry-internal/browser-utils": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-10.5.0.tgz", + "integrity": "sha512-4KIJdEj/8Ip9yqJleVSFe68r/U5bn5o/lYUwnFNEnDNxmpUbOlr7x3DXYuRFi1sfoMUxK9K1DrjnBkR7YYF00g==", + "license": "MIT", + "dependencies": { + "@sentry/core": "10.5.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry-internal/feedback": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-10.5.0.tgz", + "integrity": "sha512-x79P4VZwUxb1EGZb9OQ5EEgrDWFCUlrbzHBwV/oocQA5Ss1SFz5u6cP5Ak7yJtILiJtdGzAyAoQOy4GKD13D4Q==", + "license": "MIT", + "dependencies": { + "@sentry/core": "10.5.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry-internal/replay": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-10.5.0.tgz", + "integrity": "sha512-Dp4coE/nPzhFrYH3iVrpVKmhNJ1m/jGXMEDBCNg3wJZRszI41Hrj0jCAM0Y2S3Q4IxYOmFYaFbGtVpAznRyOHg==", + "license": "MIT", + "dependencies": { + "@sentry-internal/browser-utils": "10.5.0", + "@sentry/core": "10.5.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry-internal/replay-canvas": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-10.5.0.tgz", + "integrity": "sha512-5nrRKd5swefd9+sFXFZ/NeL3bz/VxBls3ubAQ3afak15FikkSyHq3oKRKpMOtDsiYKXE3Bc0y3rF5A+y3OXjIA==", + "license": "MIT", + "dependencies": { + "@sentry-internal/replay": "10.5.0", + "@sentry/core": "10.5.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/browser": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-10.5.0.tgz", + "integrity": "sha512-o5pEJeZ/iZ7Fmaz2sIirThfnmSVNiP5ZYhacvcDi0qc288TmBbikCX3fXxq3xiSkhXfe1o5QIbNyovzfutyuVw==", + "license": "MIT", + "dependencies": { + "@sentry-internal/browser-utils": "10.5.0", + "@sentry-internal/feedback": "10.5.0", + "@sentry-internal/replay": "10.5.0", + "@sentry-internal/replay-canvas": "10.5.0", + "@sentry/core": "10.5.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/core": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.5.0.tgz", + "integrity": "sha512-jTJ8NhZSKB2yj3QTVRXfCCngQzAOLThQUxCl9A7Mv+XF10tP7xbH/88MVQ5WiOr2IzcmrB9r2nmUe36BnMlLjA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/svelte": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@sentry/svelte/-/svelte-10.5.0.tgz", + "integrity": "sha512-lw50mOZne0t1qiU2rhoJ5d0YVDlpLiLxE40GH/gn2e2chM7DVZWkC85EerVrhfqMuwBDA2n8tXTm+WNfY7IThw==", + "license": "MIT", + "dependencies": { + "@sentry/browser": "10.5.0", + "@sentry/core": "10.5.0", + "magic-string": "^0.30.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "svelte": "3.x || 4.x || 5.x" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-5.1.1.tgz", + "integrity": "sha512-Y1Cs7hhTc+a5E9Va/xwKlAJoariQyHY+5zBgCZg4PFWNYQ1nMN9sjK1zhw1gK69DuqVP++sht/1GZg1aRwmAXQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", + "debug": "^4.4.1", + "deepmerge": "^4.3.1", + "kleur": "^4.1.5", + "magic-string": "^0.30.17", + "vitefu": "^1.0.6" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22" + }, + "peerDependencies": { + "svelte": "^5.0.0", + "vite": "^6.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte-inspector": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-4.0.1.tgz", + "integrity": "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.7" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^5.0.0", + "svelte": "^5.0.0", + "vite": "^6.0.0" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-typescript": { + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/acorn-typescript/-/acorn-typescript-1.4.13.tgz", + "integrity": "sha512-xsc9Xv0xlVfwp2o7sQ+GCQ1PgbkdcpWdTzrwXxO3xDMTAywVS3oXVOcOHuRjAPkS4P9b+yc/qNF15460v+jp4Q==", + "license": "MIT", + "peerDependencies": { + "acorn": ">=8.9.0" + } + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/esm-env": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", + "license": "MIT" + }, + "node_modules/esrap": { + "version": "1.4.9", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.4.9.tgz", + "integrity": "sha512-3OMlcd0a03UGuZpPeUC1HxR3nA23l+HEyCiZw3b3FumJIN9KphoGzDJKMXI1S72jVS1dsenDyQC0kJlO1U9E1g==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/is-reference": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", + "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.6" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/locate-character": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.5.tgz", + "integrity": "sha512-iTNAbFSlRpcHeeWu73ywU/8KuU/LZmNCSxp6fjQkJBD3ivUb8tpDrXhIxEzA05HlYMEwmtaUnb3RP+YNv162OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.53.5", + "@rollup/rollup-android-arm64": "4.53.5", + "@rollup/rollup-darwin-arm64": "4.53.5", + "@rollup/rollup-darwin-x64": "4.53.5", + "@rollup/rollup-freebsd-arm64": "4.53.5", + "@rollup/rollup-freebsd-x64": "4.53.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.5", + "@rollup/rollup-linux-arm-musleabihf": "4.53.5", + "@rollup/rollup-linux-arm64-gnu": "4.53.5", + "@rollup/rollup-linux-arm64-musl": "4.53.5", + "@rollup/rollup-linux-loong64-gnu": "4.53.5", + "@rollup/rollup-linux-ppc64-gnu": "4.53.5", + "@rollup/rollup-linux-riscv64-gnu": "4.53.5", + "@rollup/rollup-linux-riscv64-musl": "4.53.5", + "@rollup/rollup-linux-s390x-gnu": "4.53.5", + "@rollup/rollup-linux-x64-gnu": "4.53.5", + "@rollup/rollup-linux-x64-musl": "4.53.5", + "@rollup/rollup-openharmony-arm64": "4.53.5", + "@rollup/rollup-win32-arm64-msvc": "4.53.5", + "@rollup/rollup-win32-ia32-msvc": "4.53.5", + "@rollup/rollup-win32-x64-gnu": "4.53.5", + "@rollup/rollup-win32-x64-msvc": "4.53.5", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/svelte": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.3.2.tgz", + "integrity": "sha512-a0vmqDZt91jRocPB4U+/o03w5p7ujknsSNvImeNRpfI06Rc/V8ObHmud9VomVlk2k2XnPTZcomtxb2H0kp5E6Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@jridgewell/sourcemap-codec": "^1.5.0", + "@types/estree": "^1.0.5", + "acorn": "^8.12.1", + "acorn-typescript": "^1.4.13", + "aria-query": "^5.3.1", + "axobject-query": "^4.1.0", + "esm-env": "^1.2.1", + "esrap": "^1.2.3", + "is-reference": "^3.0.3", + "locate-character": "^3.0.0", + "magic-string": "^0.30.11", + "zimmerframe": "^1.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/vite": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitefu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz", + "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==", + "dev": true, + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/zimmerframe": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", + "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", + "license": "MIT" + } + } +} diff --git a/test_integrations/tracing/svelte_mini/package.json b/test_integrations/tracing/svelte_mini/package.json new file mode 100644 index 00000000..ef305e42 --- /dev/null +++ b/test_integrations/tracing/svelte_mini/package.json @@ -0,0 +1,18 @@ +{ + "name": "svelte-mini", + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^5.0.0", + "svelte": "~5.3.0", + "vite": "^6.0.0" + }, + "dependencies": { + "@sentry/svelte": "~10.5" + } +} diff --git a/test_integrations/tracing/svelte_mini/src/App.svelte b/test_integrations/tracing/svelte_mini/src/App.svelte new file mode 100644 index 00000000..ca95a96c --- /dev/null +++ b/test_integrations/tracing/svelte_mini/src/App.svelte @@ -0,0 +1,148 @@ + + +
+

Svelte Mini App

+

+ Test distributed tracing between frontend and backend: +

+ +
+ + + +
+ + {#if result} +
+

Result:

+
{result}
+
+ {/if} +
+ + diff --git a/test_integrations/tracing/svelte_mini/src/main.js b/test_integrations/tracing/svelte_mini/src/main.js new file mode 100644 index 00000000..cb854f31 --- /dev/null +++ b/test_integrations/tracing/svelte_mini/src/main.js @@ -0,0 +1,19 @@ +import * as Sentry from "@sentry/svelte"; +import { mount } from "svelte"; +import App from "./App.svelte" + +Sentry.init({ + dsn: import.meta.env.SENTRY_DSN || "https://user:secret@sentry.localdomain/42", + debug: true, + integrations: [Sentry.browserTracingIntegration(), Sentry.replayIntegration()], + tracesSampleRate: 1.0, + replaysSessionSampleRate: 1.0, + replaysOnErrorSampleRate: 1.0, + tracePropagationTargets: ["localhost:4000"], +}); + +const app = mount(App, { + target: document.getElementById("app"), +}) + +export default app diff --git a/test_integrations/tracing/svelte_mini/vite.config.js b/test_integrations/tracing/svelte_mini/vite.config.js new file mode 100644 index 00000000..36590bbf --- /dev/null +++ b/test_integrations/tracing/svelte_mini/vite.config.js @@ -0,0 +1,34 @@ +import { defineConfig } from "vite" +import { svelte } from "@sveltejs/vite-plugin-svelte" + +export default defineConfig({ + plugins: [ + svelte(), + { + name: "health-check", + configureServer(server) { + server.middlewares.use("/health", (req, res, next) => { + if (req.method === "GET") { + res.setHeader("Content-Type", "application/json") + res.setHeader("Access-Control-Allow-Origin", "*") + res.end(JSON.stringify({ + status: "ok", + timestamp: new Date().toISOString(), + service: "svelte-mini" + })) + } else { + next() + } + }) + } + } + ], + server: { + port: parseInt(process.env.SENTRY_E2E_SVELTE_APP_PORT || "4001"), + host: "0.0.0.0", + }, + define: { + SENTRY_E2E_PHOENIX_APP_URL: JSON.stringify(process.env.SENTRY_E2E_PHOENIX_APP_URL || "http://localhost:4000") + }, + envPrefix: ["SENTRY_"] +}) diff --git a/test_integrations/tracing/tests/helpers.ts b/test_integrations/tracing/tests/helpers.ts new file mode 100644 index 00000000..235dc58d --- /dev/null +++ b/test_integrations/tracing/tests/helpers.ts @@ -0,0 +1,99 @@ +import fs from "fs"; +import path from "path"; + +const DEBUG_LOG_PATH = path.join( + process.cwd(), + "..", + "phoenix_app", + "tmp", + "sentry_debug_events.log" +); + +export interface SentryEvent { + type?: string; + transaction?: string; + exception?: Array<{ + type: string; + value: string; + }>; + contexts?: { + trace?: { + trace_id?: string; + span_id?: string; + parent_span_id?: string; + op?: string; + data?: Record; + }; + }; + _meta?: { + dsc?: { + sample_rand?: string; + }; + }; + request?: { + headers?: Record; + }; +} + +export interface SentryEnvelope { + headers: { + trace?: { + trace_id?: string; + sample_rate?: string; + sample_rand?: string; + sampled?: string; + environment?: string; + public_key?: string; + [key: string]: any; + }; + }; + items: SentryEvent[]; +} + +export interface LoggedEvents { + events: SentryEvent[]; + envelopes: SentryEnvelope[]; + event_count: number; +} + +export function getLoggedEvents(): LoggedEvents { + const events: SentryEvent[] = []; + const envelopes: SentryEnvelope[] = []; + + if (!fs.existsSync(DEBUG_LOG_PATH)) { + return { events: [], envelopes: [], event_count: 0 }; + } + + const content = fs.readFileSync(DEBUG_LOG_PATH, "utf-8"); + const lines = content + .trim() + .split("\n") + .filter((line) => line.trim()); + + for (const line of lines) { + try { + const data = JSON.parse(line); + + // Check if it's an envelope format + if (data.headers) { + envelopes.push(data as SentryEnvelope); + if (data.items) { + events.push(...data.items); + } + } else { + // Individual event + events.push(data as SentryEvent); + } + } catch (e) { + // Skip malformed lines + } + } + + return { events, envelopes, event_count: events.length }; +} + +export function clearLoggedEvents(): void { + if (fs.existsSync(DEBUG_LOG_PATH)) { + fs.writeFileSync(DEBUG_LOG_PATH, ""); + } +} diff --git a/test_integrations/tracing/tests/tracing.spec.ts b/test_integrations/tracing/tests/tracing.spec.ts new file mode 100644 index 00000000..dc47c643 --- /dev/null +++ b/test_integrations/tracing/tests/tracing.spec.ts @@ -0,0 +1,218 @@ +import { test, expect } from "@playwright/test"; +import { clearLoggedEvents, getLoggedEvents } from "./helpers"; + +test.describe("Tracing", () => { + test.beforeEach(() => { + clearLoggedEvents(); + }); + + test("validates basic tracing functionality", async ({ page }) => { + await page.goto("/"); + + await expect(page.locator("h1")).toContainText("Svelte Mini App"); + await expect(page.locator("button#trigger-error-btn")).toBeVisible(); + + await page.click("button#trigger-error-btn"); + + await expect(page.locator(".result")).toContainText("Error:"); + await page.waitForTimeout(2000); + + const logged = getLoggedEvents(); + expect(logged.event_count).toBeGreaterThan(0); + + const errorEvents = logged.events.filter((event) => event.exception); + expect(errorEvents.length).toBeGreaterThan(0); + + const errorEvent = errorEvents[errorEvents.length - 1]; + const exceptionValues = errorEvent.exception; + expect(exceptionValues).toBeDefined(); + expect(exceptionValues!.length).toBeGreaterThan(0); + expect(exceptionValues![0].type).toBe("ArithmeticError"); + + const transactionEvents = logged.events.filter( + (event) => event.type === "transaction" + ); + expect(transactionEvents.length).toBeGreaterThan(0); + + const errorTransactions = transactionEvents.filter( + (event) => + event.transaction?.includes("error") || + event.transaction?.includes("GET") + ); + + errorTransactions.forEach((transaction) => { + const traceContext = transaction.contexts?.trace; + expect(traceContext).toBeDefined(); + expect(traceContext?.trace_id).toBeDefined(); + expect(traceContext?.trace_id).toMatch(/^[a-f0-9]{32}$/); + expect(traceContext?.span_id).toBeDefined(); + expect(traceContext?.span_id).toMatch(/^[a-f0-9]{16}$/); + expect(traceContext?.op).toBe("http.server"); + + const traceData = traceContext?.data as Record | undefined; + expect(traceData).toBeDefined(); + expect(traceData?.["http.request.method"]).toBe("GET"); + expect(traceData?.["http.route"]).toBe("/error"); + expect(traceData?.["phoenix.action"]).toBe("api_error"); + }); + }); + + test.describe("OpenTelemetry trace propagation", () => { + test("validates trace IDs are properly generated for backend requests", async ({ + page, + }) => { + await page.goto("/"); + + await expect(page.locator("h1")).toContainText("Svelte Mini App"); + await expect(page.locator("button#trigger-error-btn")).toBeVisible(); + + await page.click("button#trigger-error-btn"); + + await expect(page.locator(".result")).toContainText("Error:"); + + await page.waitForTimeout(2000); + + const logged = getLoggedEvents(); + + const transactionEvents = logged.events.filter( + (event) => event.type === "transaction" + ); + expect(transactionEvents.length).toBeGreaterThan(0); + + const errorTransaction = transactionEvents.find( + (event) => + event.transaction?.includes("/error") || + event.transaction?.includes("GET") + ); + expect(errorTransaction).toBeDefined(); + + const traceContext = errorTransaction!.contexts?.trace; + expect(traceContext).toBeDefined(); + expect(traceContext?.trace_id).toMatch(/^[a-f0-9]{32}$/); + expect(traceContext?.span_id).toMatch(/^[a-f0-9]{16}$/); + expect(traceContext?.op).toBe("http.server"); + }); + + test("validates distributed tracing across multiple requests", async ({ + page, + }) => { + await page.goto("/"); + + await expect(page.locator("h1")).toContainText("Svelte Mini App"); + + for (let i = 0; i < 3; i++) { + await page.click("button#trigger-error-btn"); + await page.waitForTimeout(100); + } + + await expect(page.locator(".result")).toContainText("Error:"); + + await page.waitForTimeout(2000); + + const logged = getLoggedEvents(); + + const transactionEvents = logged.events.filter( + (event) => event.type === "transaction" + ); + + const errorTransactions = transactionEvents.filter((event) => + event.transaction?.includes("/error") + ); + + expect(errorTransactions.length).toBeGreaterThanOrEqual(3); + + const traceIds = errorTransactions + .map((t) => t.contexts?.trace?.trace_id) + .filter(Boolean); + expect(traceIds.length).toBeGreaterThanOrEqual(3); + + traceIds.forEach((traceId) => { + expect(traceId).toMatch(/^[a-f0-9]{32}$/); + }); + + const uniqueTraceIds = [...new Set(traceIds)]; + expect(uniqueTraceIds.length).toBe(1); + + errorTransactions.forEach((transaction) => { + const parentSpanId = transaction.contexts?.trace?.parent_span_id; + expect(parentSpanId).toBeDefined(); + expect(parentSpanId).toMatch(/^[a-f0-9]{16}$/); + }); + }); + + test("validates child span data is preserved in distributed tracing", async ({ + page, + }) => { + await page.goto("/"); + + await expect(page.locator("h1")).toContainText("Svelte Mini App"); + + await page.evaluate(async () => { + const response = await fetch("http://localhost:4000/api/data", { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + return response.json(); + }); + + await page.waitForTimeout(2000); + + const logged = getLoggedEvents(); + + const transactionEvents = logged.events.filter( + (event) => event.type === "transaction" + ); + expect(transactionEvents.length).toBeGreaterThan(0); + + const dataTransactions = transactionEvents.filter( + (event) => + event.transaction?.includes("/api/data") || + event.transaction?.includes("fetch_data") || + (event.contexts?.trace?.data as any)?.["http.route"] === "/api/data" + ); + + expect(dataTransactions.length).toBeGreaterThan(0); + + const dataTransaction = dataTransactions[0]; + expect(dataTransaction.contexts?.trace?.parent_span_id).toBeDefined(); + expect(dataTransaction.contexts?.trace?.parent_span_id).toMatch( + /^[a-f0-9]{16}$/ + ); + + const spans = (dataTransaction as any).spans; + expect(spans).toBeDefined(); + expect(spans.length).toBeGreaterThan(0); + + spans.forEach((span: any) => { + expect(span.span_id).toBeDefined(); + expect(span.span_id).toMatch(/^[a-f0-9]{16}$/); + expect(span.trace_id).toBeDefined(); + expect(span.trace_id).toMatch(/^[a-f0-9]{32}$/); + expect(span.op).toBeDefined(); + expect(span.description).toBeDefined(); + + expect(span.data).toBeDefined(); + expect(typeof span.data).toBe("object"); + + expect(span.data["otel.kind"]).toBeDefined(); + }); + + const dbSpans = spans.filter((span: any) => span.op === "db"); + expect(dbSpans.length).toBeGreaterThan(0); + + dbSpans.forEach((dbSpan: any) => { + expect(dbSpan.data["db.system"]).toBeDefined(); + + expect(dbSpan.description).toBeDefined(); + expect(dbSpan.description.length).toBeGreaterThan(0); + + expect(dbSpan.description).toMatch(/SELECT/i); + + expect(dbSpan.data["db.statement"]).toBeDefined(); + expect(dbSpan.data["db.statement"]).toMatch(/SELECT/i); + }); + }); + }); +}); diff --git a/test_integrations/tracing/tsconfig.json b/test_integrations/tracing/tsconfig.json new file mode 100644 index 00000000..58559850 --- /dev/null +++ b/test_integrations/tracing/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "types": ["node"] + }, + "include": ["tests/**/*.ts", "playwright.config.ts"] +}