diff --git a/.github/dependabot.yml b/.github/dependabot.yml index d332007ee284..0c9602267379 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -13,13 +13,10 @@ updates: schedule: interval: 'weekly' allow: - - dependency-name: "@sentry/cli" - - dependency-name: "@sentry/vite-plugin" - - dependency-name: "@sentry/webpack-plugin" - - dependency-name: "@sentry/rollup-plugin" - - dependency-name: "@sentry/esbuild-plugin" + - dependency-name: "@sentry/*" - dependency-name: "@opentelemetry/*" - dependency-name: "@prisma/instrumentation" + - dependency-name: "opentelemetry-instrumentation-remix" versioning-strategy: increase commit-message: prefix: feat diff --git a/.github/dependency-review-config.yml b/.github/dependency-review-config.yml new file mode 100644 index 000000000000..99deb0e2677c --- /dev/null +++ b/.github/dependency-review-config.yml @@ -0,0 +1,7 @@ +fail-on-severity: 'high' +allow-ghsas: + # dependency review does not allow specific file exclusions + # we use an older version of NextJS in our tests and thus need to + # exclude this + # once our minimum supported version is over 14.1.1 this can be removed + - GHSA-fr5h-rqp8-mj6g diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 299ed59cd220..3f07002e0c5b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -437,7 +437,7 @@ jobs: with: node-version-file: 'package.json' - name: Set up Deno - uses: denoland/setup-deno@v1.5.1 + uses: denoland/setup-deno@v2.0.1 with: deno-version: v1.38.5 - name: Restore caches @@ -858,7 +858,7 @@ jobs: if: always() && needs.job_e2e_prepare.result == 'success' needs: [job_get_metadata, job_build, job_e2e_prepare] runs-on: ubuntu-20.04 - timeout-minutes: 10 + timeout-minutes: 15 env: # We just use a dummy DSN here, only send to the tunnel anyhow E2E_TEST_DSN: 'https://username@domain/123' @@ -904,6 +904,7 @@ jobs: 'nextjs-13', 'nextjs-14', 'nextjs-15', + 'nextjs-turbo', 'nextjs-t3', 'react-17', 'react-19', @@ -1018,12 +1019,12 @@ jobs: - name: Build E2E app working-directory: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }} - timeout-minutes: 5 + timeout-minutes: 7 run: pnpm ${{ matrix.build-command || 'test:build' }} - name: Run E2E test working-directory: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }} - timeout-minutes: 5 + timeout-minutes: 10 run: pnpm test:assert - name: Upload Playwright Traces @@ -1035,14 +1036,20 @@ jobs: overwrite: true retention-days: 7 + - name: Pre-process E2E Test Dumps + if: always() + run: | + node ./scripts/normalize-e2e-test-dump-transaction-events.js + - name: Upload E2E Test Event Dumps uses: actions/upload-artifact@v4 if: always() with: - name: playwright-event-dumps-job_e2e_playwright_tests-${{ matrix.test-application }} + name: E2E Test Dump (${{ matrix.label || matrix.test-application }}) path: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }}/event-dumps overwrite: true retention-days: 7 + if-no-files-found: ignore - name: Upload test results to Codecov if: cancelled() == false @@ -1063,7 +1070,7 @@ jobs: github.actor != 'dependabot[bot]' needs: [job_get_metadata, job_build, job_e2e_prepare] runs-on: ubuntu-20.04 - timeout-minutes: 10 + timeout-minutes: 15 env: E2E_TEST_AUTH_TOKEN: ${{ secrets.E2E_TEST_AUTH_TOKEN }} E2E_TEST_DSN: ${{ secrets.E2E_TEST_DSN }} @@ -1083,10 +1090,6 @@ jobs: 'react-send-to-sentry', 'node-express-send-to-sentry', 'debug-id-sourcemaps', - 'nextjs-app-dir', - 'nextjs-13', - 'nextjs-14', - 'nextjs-15', ] build-command: - false @@ -1125,6 +1128,12 @@ jobs: - test-application: 'nextjs-15' build-command: 'test:build-latest' label: 'nextjs-15 (latest)' + - test-application: 'nextjs-turbo' + build-command: 'test:build-canary' + label: 'nextjs-turbo (canary)' + - test-application: 'nextjs-turbo' + build-command: 'test:build-latest' + label: 'nextjs-turbo (latest)' steps: - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) @@ -1176,14 +1185,29 @@ jobs: - name: Build E2E app working-directory: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }} - timeout-minutes: 5 + timeout-minutes: 7 run: pnpm ${{ matrix.build-command || 'test:build' }} - name: Run E2E test working-directory: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }} - timeout-minutes: 5 + timeout-minutes: 10 run: pnpm ${{ matrix.assert-command || 'test:assert' }} + - name: Pre-process E2E Test Dumps + if: always() + run: | + node ./scripts/normalize-e2e-test-dump-transaction-events.js + + - name: Upload E2E Test Event Dumps + uses: actions/upload-artifact@v4 + if: always() + with: + name: E2E Test Dump (${{ matrix.label || matrix.test-application }}) + path: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }}/event-dumps + overwrite: true + retention-days: 7 + if-no-files-found: ignore + - name: Deploy Astro to Cloudflare uses: cloudflare/pages-action@v1 if: matrix.test-application == 'cloudflare-astro' @@ -1210,7 +1234,7 @@ jobs: ) needs: [job_get_metadata, job_build, job_e2e_prepare] runs-on: ubuntu-20.04 - timeout-minutes: 10 + timeout-minutes: 15 env: E2E_TEST_AUTH_TOKEN: ${{ secrets.E2E_TEST_AUTH_TOKEN }} E2E_TEST_DSN: ${{ secrets.E2E_TEST_DSN }} @@ -1282,12 +1306,12 @@ jobs: - name: Build E2E app working-directory: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }} - timeout-minutes: 5 + timeout-minutes: 7 run: yarn ${{ matrix.build-command || 'test:build' }} - name: Run E2E test working-directory: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }} - timeout-minutes: 5 + timeout-minutes: 10 run: yarn test:assert job_required_jobs_passed: @@ -1321,48 +1345,6 @@ jobs: run: | echo "One of the dependent jobs have failed. You may need to re-run it." && exit 1 - overhead_metrics: - name: Overhead metrics - needs: [job_get_metadata, job_build] - runs-on: ubuntu-20.04 - timeout-minutes: 30 - if: | - contains(github.event.pull_request.labels.*.name, 'ci-overhead-measurements') - steps: - - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }}) - uses: actions/checkout@v4 - with: - ref: ${{ env.HEAD_COMMIT }} - - name: Set up Node - uses: actions/setup-node@v4 - with: - node-version-file: 'package.json' - - name: Restore caches - uses: ./.github/actions/restore-cache - with: - dependency_cache_key: ${{ needs.job_build.outputs.dependency_cache_key }} - - - name: Collect - run: yarn ci:collect - working-directory: dev-packages/overhead-metrics - - - name: Process - id: process - run: yarn ci:process - working-directory: dev-packages/overhead-metrics - # Don't run on forks - the PR comment cannot be added. - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository - env: - GITHUB_TOKEN: ${{ github.token }} - - - name: Upload results - uses: actions/upload-artifact@v4 - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository - with: - name: ${{ steps.process.outputs.artifactName }} - path: ${{ steps.process.outputs.artifactPath }} - retention-days: 7 - job_compile_bindings_profiling_node: name: Compile & Test Profiling Bindings (v${{ matrix.node }}) ${{ matrix.target_platform || matrix.os }}, ${{ matrix.node || matrix.container }}, ${{ matrix.arch || matrix.container }}, ${{ contains(matrix.container, 'alpine') && 'musl' || 'glibc' }} needs: [job_get_metadata, job_build] diff --git a/.github/workflows/canary.yml b/.github/workflows/canary.yml index 1f584d2a921c..b964e6b3d1b0 100644 --- a/.github/workflows/canary.yml +++ b/.github/workflows/canary.yml @@ -96,6 +96,12 @@ jobs: - test-application: 'nextjs-15' build-command: 'test:build-latest' label: 'nextjs-15 (latest)' + - test-application: 'nextjs-turbo' + build-command: 'test:build-canary' + label: 'nextjs-turbo (canary)' + - test-application: 'nextjs-turbo' + build-command: 'test:build-latest' + label: 'nextjs-turbo (latest)' - test-application: 'react-create-hash-router' build-command: 'test:build-canary' label: 'react-create-hash-router (canary)' @@ -140,7 +146,7 @@ jobs: - name: Build E2E app working-directory: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }} - timeout-minutes: 5 + timeout-minutes: 7 run: yarn ${{ matrix.build-command }} - name: Run E2E test diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 73ce7ec1f698..daeb79c8fa30 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -33,6 +33,8 @@ jobs: analyze: name: Analyze runs-on: ubuntu-latest + # Skip for pushes from dependabot, which is not supported + if: github.event_name == 'pull_request' || github.actor != 'dependabot[bot]' strategy: fail-fast: false @@ -48,7 +50,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: config-file: ./.github/codeql/codeql-config.yml queries: security-extended @@ -61,7 +63,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 # ℹī¸ Command-line programs to run using the OS shell. # 📚 https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions @@ -75,4 +77,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.size-limit.js b/.size-limit.js index bdfe8a4397e2..75545fd89194 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -224,7 +224,7 @@ module.exports = [ import: createImport('init'), ignore: ['next/router', 'next/constants'], gzip: true, - limit: '39 KB', + limit: '39.1 KB', }, // SvelteKit SDK (ESM) { diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bb9478862cc..d584d65cff65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,56 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +## 8.36.0 + +### Important Changes + +- **feat(nuxt): Add Sentry Pinia plugin ([#14047](https://github.com/getsentry/sentry-javascript/pull/14047))** + +The Nuxt SDK now allows you to track Pinia state for captured errors. To enable the Pinia plugin, set the `trackPinia` option to `true` in your client config: + +```ts +// sentry.client.config.ts + +Sentry.init({ + trackPinia: true, +}); +``` + +Read more about the Pinia plugin in the [Sentry Pinia Documentation](https://docs.sentry.io/platforms/javascript/guides/nuxt/features/pinia/). + +- **feat(nextjs/vercel-edge/cloudflare): Switch to OTEL for performance monitoring ([#13889](https://github.com/getsentry/sentry-javascript/pull/13889))** + +With this release, the Sentry Next.js, and Cloudflare SDKs will now capture performance data based on OpenTelemetry. +Some exceptions apply in cases where Next.js captures inaccurate data itself. + +NOTE: You may experience minor differences in transaction names in Sentry. +Most importantly transactions for serverside pages router invocations will now be named `GET /[param]/my/route` instead of `/[param]/my/route`. +This means that those transactions are now better aligned with the OpenTelemetry semantic conventions. + +### Other Changes + +- deps: Bump bundler plugins and CLI to 2.22.6 and 2.37.0 respectively ([#14050](https://github.com/getsentry/sentry-javascript/pull/14050)) +- feat(deps): bump @opentelemetry/instrumentation-aws-sdk from 0.44.0 to 0.45.0 ([#14099](https://github.com/getsentry/sentry-javascript/pull/14099)) +- feat(deps): bump @opentelemetry/instrumentation-connect from 0.39.0 to 0.40.0 ([#14101](https://github.com/getsentry/sentry-javascript/pull/14101)) +- feat(deps): bump @opentelemetry/instrumentation-express from 0.43.0 to 0.44.0 ([#14102](https://github.com/getsentry/sentry-javascript/pull/14102)) +- feat(deps): bump @opentelemetry/instrumentation-fs from 0.15.0 to 0.16.0 ([#14098](https://github.com/getsentry/sentry-javascript/pull/14098)) +- feat(deps): bump @opentelemetry/instrumentation-kafkajs from 0.3.0 to 0.4.0 ([#14100](https://github.com/getsentry/sentry-javascript/pull/14100)) +- feat(nextjs): Add method and url to route handler request data ([#14084](https://github.com/getsentry/sentry-javascript/pull/14084)) +- feat(node): Add breadcrumbs for `child_process` and `worker_thread` ([#13896](https://github.com/getsentry/sentry-javascript/pull/13896)) +- fix(core): Ensure standalone spans are not sent if SDK is disabled ([#14088](https://github.com/getsentry/sentry-javascript/pull/14088)) +- fix(nextjs): Await flush in api handlers ([#14023](https://github.com/getsentry/sentry-javascript/pull/14023)) +- fix(nextjs): Don't leak webpack types into exports ([#14116](https://github.com/getsentry/sentry-javascript/pull/14116)) +- fix(nextjs): Fix matching logic for file convention type for root level components ([#14038](https://github.com/getsentry/sentry-javascript/pull/14038)) +- fix(nextjs): Respect directives in value injection loader ([#14083](https://github.com/getsentry/sentry-javascript/pull/14083)) +- fix(nuxt): Only wrap `.mjs` entry files in rollup ([#14060](https://github.com/getsentry/sentry-javascript/pull/14060)) +- fix(nuxt): Re-export all exported bindings ([#14086](https://github.com/getsentry/sentry-javascript/pull/14086)) +- fix(nuxt): Server-side setup in readme ([#14049](https://github.com/getsentry/sentry-javascript/pull/14049)) +- fix(profiling-node): Always warn when running on incompatible major version of Node.js ([#14043](https://github.com/getsentry/sentry-javascript/pull/14043)) +- fix(replay): Fix `onError` callback ([#14002](https://github.com/getsentry/sentry-javascript/pull/14002)) +- perf(otel): Only calculate current timestamp once ([#14094](https://github.com/getsentry/sentry-javascript/pull/14094)) +- test(browser-integration): Add sentry DSN route handler by default ([#14095](https://github.com/getsentry/sentry-javascript/pull/14095)) + ## 8.35.0 ### Beta release of the official Nuxt Sentry SDK diff --git a/dev-packages/browser-integration-tests/loader-suites/loader/noOnLoad/replay/test.ts b/dev-packages/browser-integration-tests/loader-suites/loader/noOnLoad/replay/test.ts index 25bcebcd074c..b690ebb36baa 100644 --- a/dev-packages/browser-integration-tests/loader-suites/loader/noOnLoad/replay/test.ts +++ b/dev-packages/browser-integration-tests/loader-suites/loader/noOnLoad/replay/test.ts @@ -11,14 +11,6 @@ sentryTest('should capture a replay', async ({ getLocalTestUrl, page }) => { sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const req = waitForReplayRequest(page); const url = await getLocalTestUrl({ testDir: __dirname }); diff --git a/dev-packages/browser-integration-tests/loader-suites/loader/noOnLoad/replayError/test.ts b/dev-packages/browser-integration-tests/loader-suites/loader/noOnLoad/replayError/test.ts index 379697881165..87d0f7ff3db4 100644 --- a/dev-packages/browser-integration-tests/loader-suites/loader/noOnLoad/replayError/test.ts +++ b/dev-packages/browser-integration-tests/loader-suites/loader/noOnLoad/replayError/test.ts @@ -9,14 +9,6 @@ sentryTest('should capture a replay & attach an error', async ({ getLocalTestUrl sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const req = waitForReplayRequest(page); const url = await getLocalTestUrl({ testDir: __dirname }); diff --git a/dev-packages/browser-integration-tests/loader-suites/loader/noOnLoad/sdkLoadedInMeanwhile/test.ts b/dev-packages/browser-integration-tests/loader-suites/loader/noOnLoad/sdkLoadedInMeanwhile/test.ts index 62456093a9a6..8a1f1a7c721f 100644 --- a/dev-packages/browser-integration-tests/loader-suites/loader/noOnLoad/sdkLoadedInMeanwhile/test.ts +++ b/dev-packages/browser-integration-tests/loader-suites/loader/noOnLoad/sdkLoadedInMeanwhile/test.ts @@ -28,7 +28,7 @@ sentryTest('it does not download the SDK if the SDK was loaded in the meanwhile' }); }); - const tmpDir = await getLocalTestUrl({ testDir: __dirname, skipRouteHandler: true }); + const tmpDir = await getLocalTestUrl({ testDir: __dirname, skipRouteHandler: true, skipDsnRouteHandler: true }); await page.route(`${TEST_HOST}/*.*`, route => { const file = route.request().url().split('/').pop(); diff --git a/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/captureExceptionInOnLoad/test.ts b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/captureExceptionInOnLoad/test.ts index 90868c3e2e8f..f5355ff765e9 100644 --- a/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/captureExceptionInOnLoad/test.ts +++ b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/captureExceptionInOnLoad/test.ts @@ -4,14 +4,6 @@ import { sentryTest } from '../../../../utils/fixtures'; import { envelopeRequestParser, waitForErrorRequestOnUrl } from '../../../../utils/helpers'; sentryTest('captureException works inside of onLoad', async ({ getLocalTestUrl, page }) => { - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); const req = await waitForErrorRequestOnUrl(page, url); @@ -21,14 +13,6 @@ sentryTest('captureException works inside of onLoad', async ({ getLocalTestUrl, }); sentryTest('should set SENTRY_SDK_SOURCE value', async ({ getLocalTestUrl, page }) => { - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); const req = await waitForErrorRequestOnUrl(page, url); diff --git a/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/customInit/test.ts b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/customInit/test.ts index 7171d2e0121f..689f6b88dd57 100644 --- a/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/customInit/test.ts +++ b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/customInit/test.ts @@ -7,14 +7,6 @@ const bundle = process.env.PW_BUNDLE || ''; const isLazy = LOADER_CONFIGS[bundle]?.lazy; sentryTest('always calls onLoad init correctly', async ({ getLocalTestUrl, page }) => { - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/customIntegrations/test.ts b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/customIntegrations/test.ts index bf08c3df2a0e..b8fc45402b76 100644 --- a/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/customIntegrations/test.ts +++ b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/customIntegrations/test.ts @@ -8,14 +8,6 @@ sentryTest('should handle custom added integrations & default integrations', asy const shouldHaveReplay = !shouldSkipReplayTest(); const shouldHaveBrowserTracing = !shouldSkipTracingTest(); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/customIntegrationsFunction/test.ts b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/customIntegrationsFunction/test.ts index 2b4685979e8c..038feb7c7652 100644 --- a/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/customIntegrationsFunction/test.ts +++ b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/customIntegrationsFunction/test.ts @@ -5,14 +5,6 @@ import { sentryTest } from '../../../../utils/fixtures'; sentryTest( 'should not add default integrations if integrations function is provided', async ({ getLocalTestUrl, page }) => { - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/customReplay/test.ts b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/customReplay/test.ts index d53987b69385..07d877c00211 100644 --- a/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/customReplay/test.ts +++ b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/customReplay/test.ts @@ -8,14 +8,6 @@ sentryTest('should handle custom added Replay integration', async ({ getLocalTes sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const req = waitForReplayRequest(page); const url = await getLocalTestUrl({ testDir: __dirname }); diff --git a/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/keepSentryGlobal/test.ts b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/keepSentryGlobal/test.ts index e4a2bf12e829..0f13098d7ae6 100644 --- a/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/keepSentryGlobal/test.ts +++ b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/keepSentryGlobal/test.ts @@ -4,14 +4,6 @@ import { sentryTest } from '../../../../utils/fixtures'; import { envelopeRequestParser, waitForErrorRequestOnUrl } from '../../../../utils/helpers'; sentryTest('keeps data on window.Sentry intact', async ({ getLocalTestUrl, page }) => { - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); const req = await waitForErrorRequestOnUrl(page, url); diff --git a/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/onLoadLate/test.ts b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/onLoadLate/test.ts index 46bbf81f3c58..2aa720fd274e 100644 --- a/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/onLoadLate/test.ts +++ b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/onLoadLate/test.ts @@ -4,14 +4,6 @@ import { sentryTest } from '../../../../utils/fixtures'; import { envelopeRequestParser, waitForErrorRequestOnUrl } from '../../../../utils/helpers'; sentryTest('late onLoad call is handled', async ({ getLocalTestUrl, page }) => { - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); const req = await waitForErrorRequestOnUrl(page, url); diff --git a/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/replay/test.ts b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/replay/test.ts index c162db40a9e5..afbfbeaba0b8 100644 --- a/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/replay/test.ts +++ b/dev-packages/browser-integration-tests/loader-suites/loader/onLoad/replay/test.ts @@ -8,14 +8,6 @@ sentryTest('should capture a replay', async ({ getLocalTestUrl, page }) => { sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const req = waitForReplayRequest(page); const url = await getLocalTestUrl({ testDir: __dirname }); diff --git a/dev-packages/browser-integration-tests/package.json b/dev-packages/browser-integration-tests/package.json index b791ba4fcca0..d093a3531276 100644 --- a/dev-packages/browser-integration-tests/package.json +++ b/dev-packages/browser-integration-tests/package.json @@ -44,11 +44,11 @@ "@playwright/test": "^1.44.1", "@sentry-internal/rrweb": "2.11.0", "@sentry/browser": "8.35.0", - "axios": "1.6.7", + "axios": "1.7.7", "babel-loader": "^8.2.2", "html-webpack-plugin": "^5.5.0", "pako": "^2.1.0", - "webpack": "^5.94.0" + "webpack": "^5.95.0" }, "devDependencies": { "@types/glob": "8.0.0", diff --git a/dev-packages/browser-integration-tests/playwright.config.ts b/dev-packages/browser-integration-tests/playwright.config.ts index 498e7529f37a..821c0291ccfb 100644 --- a/dev-packages/browser-integration-tests/playwright.config.ts +++ b/dev-packages/browser-integration-tests/playwright.config.ts @@ -30,7 +30,7 @@ const config: PlaywrightTestConfig = { }, ], - reporter: process.env.CI ? [['line'], ['junit', { outputFile: 'results.junit.xml' }]] : 'list', + reporter: process.env.CI ? [['list'], ['junit', { outputFile: 'results.junit.xml' }]] : 'list', globalSetup: require.resolve('./playwright.setup.ts'), globalTeardown: require.resolve('./playwright.teardown.ts'), diff --git a/dev-packages/browser-integration-tests/suites/feedback/attachTo/test.ts b/dev-packages/browser-integration-tests/suites/feedback/attachTo/test.ts index 507b08685092..8c605597020d 100644 --- a/dev-packages/browser-integration-tests/suites/feedback/attachTo/test.ts +++ b/dev-packages/browser-integration-tests/suites/feedback/attachTo/test.ts @@ -23,14 +23,6 @@ sentryTest('should capture feedback with custom button', async ({ getLocalTestUr } }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/feedback/captureFeedback/test.ts b/dev-packages/browser-integration-tests/suites/feedback/captureFeedback/test.ts index eb16cf1e1848..c553412d5933 100644 --- a/dev-packages/browser-integration-tests/suites/feedback/captureFeedback/test.ts +++ b/dev-packages/browser-integration-tests/suites/feedback/captureFeedback/test.ts @@ -23,14 +23,6 @@ sentryTest('should capture feedback', async ({ getLocalTestUrl, page }) => { } }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/hasSampling/test.ts b/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/hasSampling/test.ts index 5a88a429e53c..e42c42433de3 100644 --- a/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/hasSampling/test.ts +++ b/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/hasSampling/test.ts @@ -31,14 +31,6 @@ sentryTest('should capture feedback', async ({ forceFlushReplay, getLocalTestUrl } }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); await Promise.all([page.goto(url), page.getByText('Report a Bug').click(), reqPromise0]); diff --git a/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackCsp/test.ts b/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackCsp/test.ts index bca9b498fed0..a80fa4c2c6bf 100644 --- a/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackCsp/test.ts +++ b/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackCsp/test.ts @@ -23,14 +23,6 @@ sentryTest('should capture feedback', async ({ getLocalTestUrl, page }) => { } }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/feedback/logger/test.ts b/dev-packages/browser-integration-tests/suites/feedback/logger/test.ts index 34fadfc2503b..fe9fdf2e8065 100644 --- a/dev-packages/browser-integration-tests/suites/feedback/logger/test.ts +++ b/dev-packages/browser-integration-tests/suites/feedback/logger/test.ts @@ -19,14 +19,6 @@ sentryTest('should log error correctly', async ({ getLocalTestUrl, page }) => { messages.push(message.text()); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/integrations/captureConsole/test.ts b/dev-packages/browser-integration-tests/suites/integrations/captureConsole/test.ts index c704725822e7..606ef8925afd 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/captureConsole/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/captureConsole/test.ts @@ -7,14 +7,6 @@ import { getMultipleSentryEnvelopeRequests } from '../../../utils/helpers'; sentryTest('it captures console messages correctly', async ({ getLocalTestUrl, page }) => { const url = await getLocalTestUrl({ testDir: __dirname }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const [, events] = await Promise.all([page.goto(url), getMultipleSentryEnvelopeRequests(page, 7)]); expect(events).toHaveLength(7); diff --git a/dev-packages/browser-integration-tests/suites/manual-client/skip-init-browser-extension/test.ts b/dev-packages/browser-integration-tests/suites/manual-client/skip-init-browser-extension/test.ts index aeac53b9957a..7da78e3c1434 100644 --- a/dev-packages/browser-integration-tests/suites/manual-client/skip-init-browser-extension/test.ts +++ b/dev-packages/browser-integration-tests/suites/manual-client/skip-init-browser-extension/test.ts @@ -4,14 +4,6 @@ import { sentryTest } from '../../../utils/fixtures'; sentryTest( 'should not initialize when inside a Firefox/Safari browser extension', async ({ getLocalTestUrl, page }) => { - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const errorLogs: string[] = []; page.on('console', message => { diff --git a/dev-packages/browser-integration-tests/suites/manual-client/skip-init-chrome-extension/test.ts b/dev-packages/browser-integration-tests/suites/manual-client/skip-init-chrome-extension/test.ts index 3c5cc9c7648b..df0d70d779d2 100644 --- a/dev-packages/browser-integration-tests/suites/manual-client/skip-init-chrome-extension/test.ts +++ b/dev-packages/browser-integration-tests/suites/manual-client/skip-init-chrome-extension/test.ts @@ -2,14 +2,6 @@ import { expect } from '@playwright/test'; import { sentryTest } from '../../../utils/fixtures'; sentryTest('should not initialize when inside a Chrome browser extension', async ({ getLocalTestUrl, page }) => { - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const errorLogs: string[] = []; page.on('console', message => { diff --git a/dev-packages/browser-integration-tests/suites/metrics/metricsShim/test.ts b/dev-packages/browser-integration-tests/suites/metrics/metricsShim/test.ts index ba86f0a991f5..d95633393eda 100644 --- a/dev-packages/browser-integration-tests/suites/metrics/metricsShim/test.ts +++ b/dev-packages/browser-integration-tests/suites/metrics/metricsShim/test.ts @@ -22,7 +22,7 @@ sentryTest('exports shim metrics integration for non-tracing bundles', async ({ }); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestPath({ testDir: __dirname, skipDsnRouteHandler: true }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/metrics/timing/test.ts b/dev-packages/browser-integration-tests/suites/metrics/timing/test.ts index c6b369025c7a..215f042dcdf7 100644 --- a/dev-packages/browser-integration-tests/suites/metrics/timing/test.ts +++ b/dev-packages/browser-integration-tests/suites/metrics/timing/test.ts @@ -13,14 +13,6 @@ sentryTest('allows to wrap sync methods with a timing metric', async ({ getLocal sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); const beforeTime = Math.floor(Date.now() / 1000); @@ -96,14 +88,6 @@ sentryTest('allows to wrap async methods with a timing metric', async ({ getLoca sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); const beforeTime = Math.floor(Date.now() / 1000); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/standalone-sdk-disabled/init.js b/dev-packages/browser-integration-tests/suites/public-api/startSpan/standalone-sdk-disabled/init.js new file mode 100644 index 000000000000..b1e11c77c2c0 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/standalone-sdk-disabled/init.js @@ -0,0 +1,18 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +window.fetchCallCount = 0; +window.spanEnded = false; + +const originalWindowFetch = window.fetch; +window.fetch = (...args) => { + window.fetchCallCount++; + return originalWindowFetch(...args); +}; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + tracesSampleRate: 1.0, + enabled: false, +}); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/standalone-sdk-disabled/subject.js b/dev-packages/browser-integration-tests/suites/public-api/startSpan/standalone-sdk-disabled/subject.js new file mode 100644 index 000000000000..07d058f2db97 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/standalone-sdk-disabled/subject.js @@ -0,0 +1,3 @@ +Sentry.startSpan({ name: 'standalone_segment_span', experimental: { standalone: true } }, () => {}); + +window.spanEnded = true; diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/standalone-sdk-disabled/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startSpan/standalone-sdk-disabled/test.ts new file mode 100644 index 000000000000..c8d05c056b20 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/standalone-sdk-disabled/test.ts @@ -0,0 +1,22 @@ +import { expect } from '@playwright/test'; + +import { sentryTest } from '../../../../utils/fixtures'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; + +sentryTest("doesn't send a standalone span envelope if SDK is disabled", async ({ getLocalTestPath, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestPath({ testDir: __dirname }); + await page.goto(url); + + // @ts-expect-error this exists in the test init/subject + await page.waitForFunction(() => !!window.spanEnded); + await page.waitForTimeout(2000); + + // @ts-expect-error this exists in the test init + const fetchCallCount = await page.evaluate(() => window.fetchCallCount); + // We expect no fetch calls because the SDK is disabled + expect(fetchCallCount).toBe(0); +}); diff --git a/dev-packages/browser-integration-tests/suites/replay/bufferModeManual/test.ts b/dev-packages/browser-integration-tests/suites/replay/bufferModeManual/test.ts index 4ac7b9ea9cf1..8d6d87e40d36 100644 --- a/dev-packages/browser-integration-tests/suites/replay/bufferModeManual/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/bufferModeManual/test.ts @@ -45,7 +45,7 @@ sentryTest( }); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestPath({ testDir: __dirname, skipDsnRouteHandler: true }); await page.goto(url); await page.locator('#go-background').click(); @@ -190,7 +190,7 @@ sentryTest( }); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestPath({ testDir: __dirname, skipDsnRouteHandler: true }); await page.goto(url); await page.locator('#go-background').click(); @@ -297,14 +297,6 @@ sentryTest( const reqPromise0 = waitForReplayRequest(page, 0); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); @@ -359,14 +351,6 @@ sentryTest( const reqPromise0 = waitForReplayRequest(page, 0); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); page.goto(url); @@ -440,7 +424,7 @@ sentryTest( }); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestPath({ testDir: __dirname, skipDsnRouteHandler: true }); await page.goto(url); // Start buffering and assert that it is enabled diff --git a/dev-packages/browser-integration-tests/suites/replay/bufferModeReload/test.ts b/dev-packages/browser-integration-tests/suites/replay/bufferModeReload/test.ts index 79fc60497ccc..89c4f4d4f369 100644 --- a/dev-packages/browser-integration-tests/suites/replay/bufferModeReload/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/bufferModeReload/test.ts @@ -15,14 +15,6 @@ sentryTest('continues buffer session in session mode after error & reload', asyn const reqPromise1 = waitForReplayRequest(page, 0); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestPath({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/replay/canvas/manualSnapshot/test.ts b/dev-packages/browser-integration-tests/suites/replay/canvas/manualSnapshot/test.ts index 60a0864b71ee..affa6d2f90c5 100644 --- a/dev-packages/browser-integration-tests/suites/replay/canvas/manualSnapshot/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/canvas/manualSnapshot/test.ts @@ -13,14 +13,6 @@ sentryTest('can manually snapshot canvas', async ({ getLocalTestUrl, page, brows const reqPromise2 = waitForReplayRequest(page, 2); const reqPromise3 = waitForReplayRequest(page, 3); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/replay/canvas/records/test.ts b/dev-packages/browser-integration-tests/suites/replay/canvas/records/test.ts index e2beb0afc2df..c6ba950978cd 100644 --- a/dev-packages/browser-integration-tests/suites/replay/canvas/records/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/canvas/records/test.ts @@ -12,14 +12,6 @@ sentryTest('can record canvas', async ({ getLocalTestUrl, page, browserName }) = const reqPromise1 = waitForReplayRequest(page, 1); const reqPromise2 = waitForReplayRequest(page, 2); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/test.ts b/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/test.ts index 104098eed2cf..514b22af6d5c 100644 --- a/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationFirst/test.ts @@ -10,14 +10,6 @@ sentryTest('sets up canvas when adding ReplayCanvas integration first', async ({ const reqPromise0 = waitForReplayRequest(page, 0); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/test.ts b/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/test.ts index d25066dc065b..a36fc86bcdbe 100644 --- a/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/canvas/withCanvasIntegrationSecond/test.ts @@ -10,14 +10,6 @@ sentryTest('sets up canvas when adding ReplayCanvas integration after Replay', a const reqPromise0 = waitForReplayRequest(page, 0); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/replay/canvas/withoutCanvasIntegration/test.ts b/dev-packages/browser-integration-tests/suites/replay/canvas/withoutCanvasIntegration/test.ts index af3ec4a4bc97..8eac849408a7 100644 --- a/dev-packages/browser-integration-tests/suites/replay/canvas/withoutCanvasIntegration/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/canvas/withoutCanvasIntegration/test.ts @@ -8,14 +8,6 @@ sentryTest('does not setup up canvas without ReplayCanvas integration', async ({ sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/replay/captureComponentName/test.ts b/dev-packages/browser-integration-tests/suites/replay/captureComponentName/test.ts index 9ad1a99c32aa..29c400f4288d 100644 --- a/dev-packages/browser-integration-tests/suites/replay/captureComponentName/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/captureComponentName/test.ts @@ -10,14 +10,6 @@ sentryTest('captures component name attribute when available', async ({ forceFlu const reqPromise0 = waitForReplayRequest(page, 0); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestPath({ testDir: __dirname }); await page.goto(url); @@ -95,14 +87,6 @@ sentryTest('sets element name to component name attribute', async ({ forceFlushR const reqPromise0 = waitForReplayRequest(page, 0); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestPath({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/replay/captureConsoleLog/test.ts b/dev-packages/browser-integration-tests/suites/replay/captureConsoleLog/test.ts index 8ea7d10bd158..b55b23c10f04 100644 --- a/dev-packages/browser-integration-tests/suites/replay/captureConsoleLog/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/captureConsoleLog/test.ts @@ -8,14 +8,6 @@ sentryTest('should capture console messages in replay', async ({ getLocalTestPat sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const reqPromise0 = waitForReplayRequest(page, 0); const url = await getLocalTestPath({ testDir: __dirname }); @@ -59,14 +51,6 @@ sentryTest('should capture very large console logs', async ({ getLocalTestPath, sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const reqPromise0 = waitForReplayRequest(page, 0); const url = await getLocalTestPath({ testDir: __dirname }); diff --git a/dev-packages/browser-integration-tests/suites/replay/captureReplay/test.ts b/dev-packages/browser-integration-tests/suites/replay/captureReplay/test.ts index b2a7fa6dc3ac..b2cd4196643b 100644 --- a/dev-packages/browser-integration-tests/suites/replay/captureReplay/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/captureReplay/test.ts @@ -12,14 +12,6 @@ sentryTest('should capture replays (@sentry/browser export)', async ({ getLocalT const reqPromise0 = waitForReplayRequest(page, 0); const reqPromise1 = waitForReplayRequest(page, 1); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestPath({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/replay/captureReplayFromReplayPackage/test.ts b/dev-packages/browser-integration-tests/suites/replay/captureReplayFromReplayPackage/test.ts index 6267413ff84e..e9db4c92343c 100644 --- a/dev-packages/browser-integration-tests/suites/replay/captureReplayFromReplayPackage/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/captureReplayFromReplayPackage/test.ts @@ -12,14 +12,6 @@ sentryTest('should capture replays (@sentry-internal/replay export)', async ({ g const reqPromise0 = waitForReplayRequest(page, 0); const reqPromise1 = waitForReplayRequest(page, 1); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestPath({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/replay/captureReplayOffline/test.ts b/dev-packages/browser-integration-tests/suites/replay/captureReplayOffline/test.ts index a74a2c891fad..f22abf0e3451 100644 --- a/dev-packages/browser-integration-tests/suites/replay/captureReplayOffline/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/captureReplayOffline/test.ts @@ -12,14 +12,6 @@ sentryTest('should capture replays offline', async ({ getLocalTestPath, page }) const reqPromise0 = waitForReplayRequest(page, 0); const reqPromise1 = waitForReplayRequest(page, 1); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestPath({ testDir: __dirname }); // This would be the obvious way to test offline support but it doesn't appear to work! diff --git a/dev-packages/browser-integration-tests/suites/replay/compressionDisabled/test.ts b/dev-packages/browser-integration-tests/suites/replay/compressionDisabled/test.ts index 31ad7fc22991..a719bcd1d844 100644 --- a/dev-packages/browser-integration-tests/suites/replay/compressionDisabled/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/compressionDisabled/test.ts @@ -19,14 +19,6 @@ sentryTest( const reqPromise0 = waitForReplayRequest(page, 0); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestPath({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/replay/compressionEnabled/test.ts b/dev-packages/browser-integration-tests/suites/replay/compressionEnabled/test.ts index a080b2de5bdf..753fe57a0c01 100644 --- a/dev-packages/browser-integration-tests/suites/replay/compressionEnabled/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/compressionEnabled/test.ts @@ -17,14 +17,6 @@ sentryTest('replay recording should be compressed by default', async ({ getLocal const reqPromise0 = waitForReplayRequest(page, 0); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestPath({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/replay/compressionWorkerUrl/test.ts b/dev-packages/browser-integration-tests/suites/replay/compressionWorkerUrl/test.ts index 0b34803e3b7a..0c5710c0dfb7 100644 --- a/dev-packages/browser-integration-tests/suites/replay/compressionWorkerUrl/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/compressionWorkerUrl/test.ts @@ -21,14 +21,6 @@ sentryTest( const reqPromise0 = waitForReplayRequest(page, 0); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); let customCompressCalled = 0; diff --git a/dev-packages/browser-integration-tests/suites/replay/customEvents/test.ts b/dev-packages/browser-integration-tests/suites/replay/customEvents/test.ts index 053c31c3881e..5c93870d0937 100644 --- a/dev-packages/browser-integration-tests/suites/replay/customEvents/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/customEvents/test.ts @@ -33,14 +33,6 @@ sentryTest( const reqPromise0 = waitForReplayRequest(page, 0); const reqPromise1 = waitForReplayRequest(page, 1); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestPath({ testDir: __dirname }); await page.goto(url); @@ -95,14 +87,6 @@ sentryTest( const reqPromise2 = waitForReplayRequest(page, 2); const reqPromise3 = waitForReplayRequest(page, 3); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestPath({ testDir: __dirname }); await page.goto(url); @@ -198,14 +182,6 @@ sentryTest( const reqPromise0 = waitForReplayRequest(page, 0); const reqPromise1 = waitForReplayRequest(page, 1); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestPath({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/replay/dsc/test.ts b/dev-packages/browser-integration-tests/suites/replay/dsc/test.ts index b6bb4da00abb..62961caef062 100644 --- a/dev-packages/browser-integration-tests/suites/replay/dsc/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/dsc/test.ts @@ -120,14 +120,6 @@ sentryTest( sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestPath({ testDir: __dirname }); await page.goto(url); @@ -181,14 +173,6 @@ sentryTest( sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestPath({ testDir: __dirname }); await page.goto(url); @@ -229,14 +213,6 @@ sentryTest('should add replay_id to error DSC while replay is active', async ({ const hasTracing = !shouldSkipTracingTest(); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestPath({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/replay/errorResponse/test.ts b/dev-packages/browser-integration-tests/suites/replay/errorResponse/test.ts index 67f1d1d12d6d..048ca898d892 100644 --- a/dev-packages/browser-integration-tests/suites/replay/errorResponse/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/errorResponse/test.ts @@ -22,7 +22,7 @@ sentryTest('should stop recording after receiving an error response', async ({ g }); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestPath({ testDir: __dirname, skipDsnRouteHandler: true }); await Promise.all([page.goto(url), waitForReplayRequest(page)]); await page.locator('button').click(); diff --git a/dev-packages/browser-integration-tests/suites/replay/errors/beforeErrorSampling/test.ts b/dev-packages/browser-integration-tests/suites/replay/errors/beforeErrorSampling/test.ts index cbb4826df195..71cea051ce94 100644 --- a/dev-packages/browser-integration-tests/suites/replay/errors/beforeErrorSampling/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/errors/beforeErrorSampling/test.ts @@ -11,14 +11,6 @@ sentryTest( sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestPath({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/replay/errors/droppedError/test.ts b/dev-packages/browser-integration-tests/suites/replay/errors/droppedError/test.ts index 04503ca52cb7..2ae046bf6c58 100644 --- a/dev-packages/browser-integration-tests/suites/replay/errors/droppedError/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/errors/droppedError/test.ts @@ -28,7 +28,7 @@ sentryTest( }); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestPath({ testDir: __dirname, skipDsnRouteHandler: true }); await page.goto(url); await forceFlushReplay(); diff --git a/dev-packages/browser-integration-tests/suites/replay/errors/errorMode/test.ts b/dev-packages/browser-integration-tests/suites/replay/errors/errorMode/test.ts index b2f7e067e969..8eeb544d06ef 100644 --- a/dev-packages/browser-integration-tests/suites/replay/errors/errorMode/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/errors/errorMode/test.ts @@ -48,7 +48,7 @@ sentryTest( }); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestPath({ testDir: __dirname, skipDsnRouteHandler: true }); await Promise.all([ page.goto(url), diff --git a/dev-packages/browser-integration-tests/suites/replay/errors/errorNotSent/test.ts b/dev-packages/browser-integration-tests/suites/replay/errors/errorNotSent/test.ts index c47f4bf4e105..123ec1609a04 100644 --- a/dev-packages/browser-integration-tests/suites/replay/errors/errorNotSent/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/errors/errorNotSent/test.ts @@ -21,7 +21,7 @@ sentryTest( }); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestPath({ testDir: __dirname, skipDsnRouteHandler: true }); await page.goto(url); await forceFlushReplay(); diff --git a/dev-packages/browser-integration-tests/suites/replay/errors/errorsInSession/test.ts b/dev-packages/browser-integration-tests/suites/replay/errors/errorsInSession/test.ts index 5fb1e7b4e340..bc9453f58135 100644 --- a/dev-packages/browser-integration-tests/suites/replay/errors/errorsInSession/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/errors/errorsInSession/test.ts @@ -37,7 +37,7 @@ sentryTest( }); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestPath({ testDir: __dirname, skipDsnRouteHandler: true }); await page.goto(url); const req0 = await reqPromise0; @@ -94,14 +94,6 @@ sentryTest( const reqPromise0 = waitForReplayRequest(page, 0); const reqPromise1 = waitForReplayRequest(page, 1); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestPath({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/replay/errors/immediateError/test.ts b/dev-packages/browser-integration-tests/suites/replay/errors/immediateError/test.ts index 7c82f29256d9..8ce1af848952 100644 --- a/dev-packages/browser-integration-tests/suites/replay/errors/immediateError/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/errors/immediateError/test.ts @@ -11,14 +11,6 @@ sentryTest( sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const req = waitForReplayRequest(page); const url = await getLocalTestUrl({ testDir: __dirname }); diff --git a/dev-packages/browser-integration-tests/suites/replay/eventBufferError/test.ts b/dev-packages/browser-integration-tests/suites/replay/eventBufferError/test.ts index a6924bdda3c9..0502cf8fdcc7 100644 --- a/dev-packages/browser-integration-tests/suites/replay/eventBufferError/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/eventBufferError/test.ts @@ -19,12 +19,6 @@ sentryTest( sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - }); - }); - const url = await getLocalTestPath({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestBody/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestBody/test.ts index f8b48ffec598..b7fd02bc463c 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestBody/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestBody/test.ts @@ -21,14 +21,6 @@ sentryTest('captures text request body', async ({ getLocalTestUrl, page, browser }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); @@ -99,14 +91,6 @@ sentryTest('captures JSON request body', async ({ getLocalTestUrl, page, browser }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); @@ -177,14 +161,6 @@ sentryTest('captures non-text request body', async ({ getLocalTestUrl, page, bro }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); @@ -259,14 +235,6 @@ sentryTest('captures text request body when matching relative URL', async ({ get }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); @@ -335,14 +303,6 @@ sentryTest('does not capture request body when URL does not match', async ({ get }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestHeaders/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestHeaders/test.ts index f1952da08f0a..9bbd1f0c487e 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestHeaders/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestHeaders/test.ts @@ -21,12 +21,6 @@ sentryTest('handles empty/missing request headers', async ({ getLocalTestUrl, pa }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); @@ -92,14 +86,6 @@ sentryTest('captures request headers as POJO', async ({ getLocalTestUrl, page, b }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); @@ -179,12 +165,6 @@ sentryTest('captures request headers on Request', async ({ getLocalTestUrl, page }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); @@ -264,12 +244,6 @@ sentryTest('captures request headers as Headers instance', async ({ getLocalTest }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); @@ -347,14 +321,6 @@ sentryTest('does not captures request headers if URL does not match', async ({ g }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestSize/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestSize/test.ts index 4da3c7142996..2e7c22d29fbe 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestSize/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestSize/test.ts @@ -19,14 +19,6 @@ sentryTest('captures request body size when body is sent', async ({ getLocalTest }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); @@ -104,14 +96,6 @@ sentryTest('captures request size from non-text request body', async ({ getLocal }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseBody/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseBody/test.ts index 7ce8ecb87748..1d727564a852 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseBody/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseBody/test.ts @@ -22,14 +22,6 @@ sentryTest('captures text response body', async ({ getLocalTestUrl, page, browse }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); @@ -102,14 +94,6 @@ sentryTest('captures JSON response body', async ({ getLocalTestUrl, page, browse }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); @@ -182,14 +166,6 @@ sentryTest('captures non-text response body', async ({ getLocalTestUrl, page, br }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); @@ -262,14 +238,6 @@ sentryTest.skip('does not capture response body when URL does not match', async }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseHeaders/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseHeaders/test.ts index ec4a26dd199f..7142a16dd174 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseHeaders/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseHeaders/test.ts @@ -21,14 +21,6 @@ sentryTest('handles empty headers', async ({ getLocalTestUrl, page, browserName }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); @@ -97,14 +89,6 @@ sentryTest('captures response headers', async ({ getLocalTestUrl, page }) => { }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); @@ -179,14 +163,6 @@ sentryTest('does not capture response headers if URL does not match', async ({ g }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseSize/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseSize/test.ts index 0191617d46da..805413f70884 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseSize/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseSize/test.ts @@ -26,14 +26,6 @@ sentryTest('captures response size from Content-Length header if available', asy }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { @@ -116,14 +108,6 @@ sentryTest('captures response size without Content-Length header', async ({ getL }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { @@ -203,14 +187,6 @@ sentryTest('captures response size from non-text response body', async ({ getLoc }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureTimestamps/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureTimestamps/test.ts index d77796f92fc2..e3acdb5dc6e0 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureTimestamps/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureTimestamps/test.ts @@ -34,7 +34,7 @@ sentryTest('captures correct timestamps', async ({ getLocalTestUrl, page, browse return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.fetch'); }); - const url = await getLocalTestUrl({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname, skipDsnRouteHandler: true }); await page.goto(url); await page.evaluate(() => { diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestBody/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestBody/test.ts index d12d74381fc2..7108bde5fc71 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestBody/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestBody/test.ts @@ -20,14 +20,6 @@ sentryTest('captures text request body', async ({ getLocalTestUrl, page, browser }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); @@ -100,14 +92,6 @@ sentryTest('captures JSON request body', async ({ getLocalTestUrl, page, browser }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); @@ -180,14 +164,6 @@ sentryTest('captures non-text request body', async ({ getLocalTestUrl, page, bro }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); @@ -264,14 +240,6 @@ sentryTest('captures text request body when matching relative URL', async ({ get }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); @@ -344,14 +312,6 @@ sentryTest('does not capture request body when URL does not match', async ({ get }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestHeaders/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestHeaders/test.ts index be6ac82d4b5c..8485a7078b38 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestHeaders/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestHeaders/test.ts @@ -20,14 +20,6 @@ sentryTest('captures request headers', async ({ getLocalTestUrl, page, browserNa }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); @@ -105,14 +97,6 @@ sentryTest('does not capture request headers if URL does not match', async ({ ge }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestSize/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestSize/test.ts index 73d063ea1b47..8f263b1d8349 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestSize/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestSize/test.ts @@ -20,14 +20,6 @@ sentryTest('captures request body size when body is sent', async ({ getLocalTest }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); @@ -110,14 +102,6 @@ sentryTest('captures request size from non-text request body', async ({ getLocal }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseBody/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseBody/test.ts index afa2c669f204..8975729b7c54 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseBody/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseBody/test.ts @@ -24,14 +24,6 @@ sentryTest('captures text response body', async ({ getLocalTestUrl, page, browse }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); @@ -108,14 +100,6 @@ sentryTest('captures JSON response body', async ({ getLocalTestUrl, page, browse }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); @@ -192,14 +176,6 @@ sentryTest('captures JSON response body when responseType=json', async ({ getLoc }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); @@ -278,14 +254,6 @@ sentryTest('captures non-text response body', async ({ getLocalTestUrl, page, br }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); @@ -362,14 +330,6 @@ sentryTest('does not capture response body when URL does not match', async ({ ge }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseHeaders/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseHeaders/test.ts index 4726f69c641d..68dcad0af664 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseHeaders/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseHeaders/test.ts @@ -27,14 +27,6 @@ sentryTest('captures response headers', async ({ getLocalTestUrl, page, browserN }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); @@ -117,14 +109,6 @@ sentryTest( }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseSize/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseSize/test.ts index dda9e4e642c1..6ffccfdbc47e 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseSize/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseSize/test.ts @@ -25,14 +25,6 @@ sentryTest( }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); @@ -120,14 +112,6 @@ sentryTest('captures response size without Content-Length header', async ({ getL }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); @@ -212,14 +196,6 @@ sentryTest('captures response size for non-string bodies', async ({ getLocalTest }); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const requestPromise = waitForErrorRequest(page); const replayRequestPromise = collectReplayRequests(page, recordingEvents => { return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureTimestamps/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureTimestamps/test.ts index e89fa6e4a24e..19781d7312bb 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureTimestamps/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureTimestamps/test.ts @@ -34,7 +34,7 @@ sentryTest('captures correct timestamps', async ({ getLocalTestUrl, page, browse return getReplayPerformanceSpans(recordingEvents).some(span => span.op === 'resource.xhr'); }); - const url = await getLocalTestUrl({ testDir: __dirname }); + const url = await getLocalTestUrl({ testDir: __dirname, skipDsnRouteHandler: true }); await page.goto(url); void page.evaluate(() => { diff --git a/dev-packages/browser-integration-tests/suites/replay/fileInput/test.ts b/dev-packages/browser-integration-tests/suites/replay/fileInput/test.ts index be8db2893a4f..153ec956fa4b 100644 --- a/dev-packages/browser-integration-tests/suites/replay/fileInput/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/fileInput/test.ts @@ -26,14 +26,6 @@ sentryTest( const reqPromise0 = waitForReplayRequest(page, 0); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestPath({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/replay/flushing/test.ts b/dev-packages/browser-integration-tests/suites/replay/flushing/test.ts index 91308d7736c8..52d72fb9a58a 100644 --- a/dev-packages/browser-integration-tests/suites/replay/flushing/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/flushing/test.ts @@ -21,14 +21,6 @@ sentryTest('replay events are flushed after max flush delay was reached', async const reqPromise1 = waitForReplayRequest(page, 1); const reqPromise2 = waitForReplayRequest(page, 2); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestPath({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/replay/keyboardEvents/test.ts b/dev-packages/browser-integration-tests/suites/replay/keyboardEvents/test.ts index edeacb7f2db0..d23bfafea394 100644 --- a/dev-packages/browser-integration-tests/suites/replay/keyboardEvents/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/keyboardEvents/test.ts @@ -10,14 +10,6 @@ sentryTest('captures keyboard events', async ({ forceFlushReplay, getLocalTestPa const reqPromise0 = waitForReplayRequest(page, 0); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestPath({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/replay/largeMutations/defaultOptions/test.ts b/dev-packages/browser-integration-tests/suites/replay/largeMutations/defaultOptions/test.ts index 948c3bb4ea41..07f5362f4f7b 100644 --- a/dev-packages/browser-integration-tests/suites/replay/largeMutations/defaultOptions/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/largeMutations/defaultOptions/test.ts @@ -10,14 +10,6 @@ sentryTest( sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestPath({ testDir: __dirname }); // We have to click in order to ensure the LCP is generated, leading to consistent results diff --git a/dev-packages/browser-integration-tests/suites/replay/largeMutations/mutationLimit/test.ts b/dev-packages/browser-integration-tests/suites/replay/largeMutations/mutationLimit/test.ts index 672190e2d0a0..d86617396cf6 100644 --- a/dev-packages/browser-integration-tests/suites/replay/largeMutations/mutationLimit/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/largeMutations/mutationLimit/test.ts @@ -15,14 +15,6 @@ sentryTest( sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestPath({ testDir: __dirname }); // We have to click in order to ensure the LCP is generated, leading to consistent results diff --git a/dev-packages/browser-integration-tests/suites/replay/logger/test.ts b/dev-packages/browser-integration-tests/suites/replay/logger/test.ts index fa034a12b003..e194c80f05c4 100644 --- a/dev-packages/browser-integration-tests/suites/replay/logger/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/logger/test.ts @@ -15,14 +15,6 @@ sentryTest('should output logger messages', async ({ getLocalTestPath, page }) = messages.push(message.text()); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const reqPromise0 = waitForReplayRequest(page, 0); const url = await getLocalTestPath({ testDir: __dirname }); diff --git a/dev-packages/browser-integration-tests/suites/replay/maxReplayDuration/test.ts b/dev-packages/browser-integration-tests/suites/replay/maxReplayDuration/test.ts index a133f7ae77d9..538cc5f3aa20 100644 --- a/dev-packages/browser-integration-tests/suites/replay/maxReplayDuration/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/maxReplayDuration/test.ts @@ -11,14 +11,6 @@ sentryTest('keeps track of max duration across reloads', async ({ getLocalTestPa sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const reqPromise0 = waitForReplayRequest(page, 0); const reqPromise1 = waitForReplayRequest(page, 1); diff --git a/dev-packages/browser-integration-tests/suites/replay/minReplayDuration/test.ts b/dev-packages/browser-integration-tests/suites/replay/minReplayDuration/test.ts index 1bdb0567de77..967de1ecfe0f 100644 --- a/dev-packages/browser-integration-tests/suites/replay/minReplayDuration/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/minReplayDuration/test.ts @@ -17,14 +17,6 @@ sentryTest('doest not send replay before min. duration', async ({ getLocalTestPa return true; }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestPath({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts index 7bacf5a8ae17..3ee84086cc37 100644 --- a/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/multiple-pages/test.ts @@ -37,14 +37,6 @@ sentryTest( sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const reqPromise0 = waitForReplayRequest(page, 0); const reqPromise1 = waitForReplayRequest(page, 1); const reqPromise2 = waitForReplayRequest(page, 2); diff --git a/dev-packages/browser-integration-tests/suites/replay/privacyBlock/test.ts b/dev-packages/browser-integration-tests/suites/replay/privacyBlock/test.ts index 8b000b07e461..a6f40d884e8b 100644 --- a/dev-packages/browser-integration-tests/suites/replay/privacyBlock/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/privacyBlock/test.ts @@ -15,14 +15,6 @@ sentryTest('should allow to manually block elements', async ({ getLocalTestPath, const reqPromise0 = waitForReplayRequest(page, 0); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestPath({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/replay/privacyDefault/test.ts b/dev-packages/browser-integration-tests/suites/replay/privacyDefault/test.ts index 713431ca3063..75ba5d2831c6 100644 --- a/dev-packages/browser-integration-tests/suites/replay/privacyDefault/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/privacyDefault/test.ts @@ -15,14 +15,6 @@ sentryTest('should have the correct default privacy settings', async ({ getLocal const reqPromise0 = waitForReplayRequest(page, 0); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestPath({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/replay/privacyInput/test.ts b/dev-packages/browser-integration-tests/suites/replay/privacyInput/test.ts index f2c506f90132..19dddd68ec0c 100644 --- a/dev-packages/browser-integration-tests/suites/replay/privacyInput/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/privacyInput/test.ts @@ -54,14 +54,6 @@ sentryTest( return inputMutationSegmentIds.length === 2 && inputMutationSegmentIds[1] < event.segment_id; }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestPath({ testDir: __dirname }); await page.goto(url); @@ -139,14 +131,6 @@ sentryTest( return check; }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestPath({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/replay/privacyInputMaskAll/test.ts b/dev-packages/browser-integration-tests/suites/replay/privacyInputMaskAll/test.ts index 88bc531017d9..7d64cd839d22 100644 --- a/dev-packages/browser-integration-tests/suites/replay/privacyInputMaskAll/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/privacyInputMaskAll/test.ts @@ -46,14 +46,6 @@ sentryTest( ); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestPath({ testDir: __dirname }); await page.goto(url); @@ -110,14 +102,6 @@ sentryTest( ); }); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestPath({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/replay/replayIntegrationShim/test.ts b/dev-packages/browser-integration-tests/suites/replay/replayIntegrationShim/test.ts index 952c841b253e..7eb84f7da310 100644 --- a/dev-packages/browser-integration-tests/suites/replay/replayIntegrationShim/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/replayIntegrationShim/test.ts @@ -24,7 +24,7 @@ sentryTest( }); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestPath({ testDir: __dirname, skipDsnRouteHandler: true }); await page.goto(url); await forceFlushReplay(); diff --git a/dev-packages/browser-integration-tests/suites/replay/replayShim/test.ts b/dev-packages/browser-integration-tests/suites/replay/replayShim/test.ts index b906deefb71b..8d3b7bab9aa0 100644 --- a/dev-packages/browser-integration-tests/suites/replay/replayShim/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/replayShim/test.ts @@ -24,7 +24,7 @@ sentryTest( }); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestPath({ testDir: __dirname, skipDsnRouteHandler: true }); await page.goto(url); await forceFlushReplay(); diff --git a/dev-packages/browser-integration-tests/suites/replay/requests/test.ts b/dev-packages/browser-integration-tests/suites/replay/requests/test.ts index a15665b357dc..eedfb5ae5fb2 100644 --- a/dev-packages/browser-integration-tests/suites/replay/requests/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/requests/test.ts @@ -10,14 +10,6 @@ sentryTest('replay recording should contain fetch request span', async ({ getLoc sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - await page.route('https://example.com', route => { return route.fulfill({ status: 200, @@ -48,14 +40,6 @@ sentryTest('replay recording should contain XHR request span', async ({ getLocal sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - await page.route('https://example.com', route => { return route.fulfill({ status: 200, diff --git a/dev-packages/browser-integration-tests/suites/replay/sampling/test.ts b/dev-packages/browser-integration-tests/suites/replay/sampling/test.ts index 92aa50ff3744..cc68be486749 100644 --- a/dev-packages/browser-integration-tests/suites/replay/sampling/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/sampling/test.ts @@ -19,7 +19,7 @@ sentryTest('should not send replays if both sample rates are 0', async ({ getLoc }); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestPath({ testDir: __dirname, skipDsnRouteHandler: true }); await page.goto(url); await page.locator('button').click(); diff --git a/dev-packages/browser-integration-tests/suites/replay/sessionExpiry/test.ts b/dev-packages/browser-integration-tests/suites/replay/sessionExpiry/test.ts index 05842705227e..49a95345fc43 100644 --- a/dev-packages/browser-integration-tests/suites/replay/sessionExpiry/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/sessionExpiry/test.ts @@ -22,14 +22,6 @@ sentryTest('handles an expired session', async ({ browserName, forceFlushReplay, const reqPromise0 = waitForReplayRequest(page, 0); const reqPromise1 = waitForReplayRequest(page, 1); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestPath({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/replay/sessionInactive/test.ts b/dev-packages/browser-integration-tests/suites/replay/sessionInactive/test.ts index 614f9a3d99d7..0ac765c3a969 100644 --- a/dev-packages/browser-integration-tests/suites/replay/sessionInactive/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/sessionInactive/test.ts @@ -23,14 +23,6 @@ sentryTest('handles an inactive session', async ({ getLocalTestPath, page, brows const reqPromise0 = waitForReplayRequest(page, 0); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestPath({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/replay/sessionMaxAge/test.ts b/dev-packages/browser-integration-tests/suites/replay/sessionMaxAge/test.ts index 0a253597f367..d658f407009c 100644 --- a/dev-packages/browser-integration-tests/suites/replay/sessionMaxAge/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/sessionMaxAge/test.ts @@ -26,14 +26,6 @@ sentryTest('handles session that exceeds max age', async ({ forceFlushReplay, ge const reqPromise0 = waitForReplayRequest(page, 0); const reqPromise1 = waitForReplayRequest(page, 1); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestPath({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/replay/slowClick/clickTargets/test.ts b/dev-packages/browser-integration-tests/suites/replay/slowClick/clickTargets/test.ts index b5b8a5b24988..73910e1190e0 100644 --- a/dev-packages/browser-integration-tests/suites/replay/slowClick/clickTargets/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/slowClick/clickTargets/test.ts @@ -35,14 +35,6 @@ import { getCustomRecordingEvents, shouldSkipReplayTest, waitForReplayRequest } sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); await Promise.all([waitForReplayRequest(page, 0), page.goto(url)]); @@ -91,14 +83,6 @@ import { getCustomRecordingEvents, shouldSkipReplayTest, waitForReplayRequest } sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); await Promise.all([waitForReplayRequest(page, 0), page.goto(url)]); diff --git a/dev-packages/browser-integration-tests/suites/replay/slowClick/disable/test.ts b/dev-packages/browser-integration-tests/suites/replay/slowClick/disable/test.ts index c57b5d05b3f2..f5337f1feb63 100644 --- a/dev-packages/browser-integration-tests/suites/replay/slowClick/disable/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/slowClick/disable/test.ts @@ -8,14 +8,6 @@ sentryTest('does not capture slow click when slowClickTimeout === 0', async ({ g sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); await Promise.all([waitForReplayRequest(page, 0), page.goto(url)]); diff --git a/dev-packages/browser-integration-tests/suites/replay/slowClick/error/test.ts b/dev-packages/browser-integration-tests/suites/replay/slowClick/error/test.ts index 570a633443d4..5d106fb9c0e9 100644 --- a/dev-packages/browser-integration-tests/suites/replay/slowClick/error/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/slowClick/error/test.ts @@ -13,14 +13,6 @@ sentryTest('slow click that triggers error is captured', async ({ getLocalTestUr sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); @@ -70,14 +62,6 @@ sentryTest( sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/replay/slowClick/ignore/test.ts b/dev-packages/browser-integration-tests/suites/replay/slowClick/ignore/test.ts index 9d6c49abc29d..1928bfcf2c2e 100644 --- a/dev-packages/browser-integration-tests/suites/replay/slowClick/ignore/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/slowClick/ignore/test.ts @@ -8,14 +8,6 @@ sentryTest('click is ignored on ignoreSelectors', async ({ getLocalTestUrl, page sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); await Promise.all([waitForReplayRequest(page, 0), page.goto(url)]); @@ -58,14 +50,6 @@ sentryTest('click is ignored on div', async ({ getLocalTestUrl, page }) => { sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); await Promise.all([waitForReplayRequest(page, 0), page.goto(url)]); diff --git a/dev-packages/browser-integration-tests/suites/replay/slowClick/multiClick/test.ts b/dev-packages/browser-integration-tests/suites/replay/slowClick/multiClick/test.ts index 16f3036a3cca..04ab9c059f34 100644 --- a/dev-packages/browser-integration-tests/suites/replay/slowClick/multiClick/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/slowClick/multiClick/test.ts @@ -13,14 +13,6 @@ sentryTest('captures multi click when not detecting slow click', async ({ getLoc sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); await Promise.all([waitForReplayRequest(page, 0), page.goto(url)]); @@ -105,14 +97,6 @@ sentryTest('captures multiple multi clicks', async ({ getLocalTestUrl, page, for sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); await Promise.all([waitForReplayRequest(page, 0), page.goto(url)]); diff --git a/dev-packages/browser-integration-tests/suites/replay/slowClick/mutation/test.ts b/dev-packages/browser-integration-tests/suites/replay/slowClick/mutation/test.ts index aafdced81505..1c5aab9f4183 100644 --- a/dev-packages/browser-integration-tests/suites/replay/slowClick/mutation/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/slowClick/mutation/test.ts @@ -8,14 +8,6 @@ sentryTest('mutation after threshold results in slow click', async ({ forceFlush sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); const replayRequestPromise = waitForReplayRequest(page, 0); @@ -70,14 +62,6 @@ sentryTest('multiple clicks are counted', async ({ getLocalTestUrl, page }) => { sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); const replayRequestPromise = waitForReplayRequest(page, 0); @@ -131,14 +115,6 @@ sentryTest('immediate mutation does not trigger slow click', async ({ forceFlush sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); const replayRequestPromise = waitForReplayRequest(page, 0); @@ -198,14 +174,6 @@ sentryTest('inline click handler does not trigger slow click', async ({ forceFlu sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); const replayRequestPromise = waitForReplayRequest(page, 0); @@ -250,14 +218,6 @@ sentryTest('mouseDown events are considered', async ({ getLocalTestUrl, page }) sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); const replayRequestPromise = waitForReplayRequest(page, 0); diff --git a/dev-packages/browser-integration-tests/suites/replay/slowClick/scroll/test.ts b/dev-packages/browser-integration-tests/suites/replay/slowClick/scroll/test.ts index a7101351061f..cb0e467de4f6 100644 --- a/dev-packages/browser-integration-tests/suites/replay/slowClick/scroll/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/slowClick/scroll/test.ts @@ -8,14 +8,6 @@ sentryTest('immediate scroll does not trigger slow click', async ({ getLocalTest sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); await Promise.all([waitForReplayRequest(page, 0), page.goto(url)]); @@ -57,14 +49,6 @@ sentryTest('late scroll triggers slow click', async ({ getLocalTestUrl, page }) sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); await Promise.all([waitForReplayRequest(page, 0), page.goto(url)]); diff --git a/dev-packages/browser-integration-tests/suites/replay/slowClick/timeout/test.ts b/dev-packages/browser-integration-tests/suites/replay/slowClick/timeout/test.ts index 8adf24302089..1bc8219d22ab 100644 --- a/dev-packages/browser-integration-tests/suites/replay/slowClick/timeout/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/slowClick/timeout/test.ts @@ -8,14 +8,6 @@ sentryTest('mutation after timeout results in slow click', async ({ getLocalTest sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); await Promise.all([waitForReplayRequest(page, 0), page.goto(url)]); @@ -63,14 +55,6 @@ sentryTest('console.log results in slow click', async ({ getLocalTestUrl, page } sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); await Promise.all([waitForReplayRequest(page, 0), page.goto(url)]); diff --git a/dev-packages/browser-integration-tests/suites/replay/slowClick/windowOpen/test.ts b/dev-packages/browser-integration-tests/suites/replay/slowClick/windowOpen/test.ts index d68f27a5f45b..3a5187b0fc85 100644 --- a/dev-packages/browser-integration-tests/suites/replay/slowClick/windowOpen/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/slowClick/windowOpen/test.ts @@ -8,14 +8,6 @@ sentryTest('window.open() is considered for slow click', async ({ getLocalTestUr sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - await page.route('http://example.com/', route => { return route.fulfill({ status: 200, diff --git a/dev-packages/browser-integration-tests/suites/replay/throttleBreadcrumbs/test.ts b/dev-packages/browser-integration-tests/suites/replay/throttleBreadcrumbs/test.ts index 0eb0495eb1ea..62bfda2b0a8c 100644 --- a/dev-packages/browser-integration-tests/suites/replay/throttleBreadcrumbs/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/throttleBreadcrumbs/test.ts @@ -15,14 +15,6 @@ sentryTest( const reqPromise0 = waitForReplayRequest(page, 0); const reqPromise1 = waitForReplayRequest(page, 1); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/replay/unicode/compressed/test.ts b/dev-packages/browser-integration-tests/suites/replay/unicode/compressed/test.ts index 741a702fbef9..fd8250fe86ad 100644 --- a/dev-packages/browser-integration-tests/suites/replay/unicode/compressed/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/unicode/compressed/test.ts @@ -15,14 +15,6 @@ sentryTest('replay should handle unicode characters', async ({ getLocalTestPath, const reqPromise0 = waitForReplayRequest(page, 0); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestPath({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/replay/unicode/uncompressed/test.ts b/dev-packages/browser-integration-tests/suites/replay/unicode/uncompressed/test.ts index 1ee9d73e5f57..782d44702c34 100644 --- a/dev-packages/browser-integration-tests/suites/replay/unicode/uncompressed/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/unicode/uncompressed/test.ts @@ -15,14 +15,6 @@ sentryTest('replay should handle unicode characters', async ({ getLocalTestPath, const reqPromise0 = waitForReplayRequest(page, 0); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestPath({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/sessions/initial-scope/test.ts b/dev-packages/browser-integration-tests/suites/sessions/initial-scope/test.ts index 9d7b07f1cfd1..e140cf14ebea 100644 --- a/dev-packages/browser-integration-tests/suites/sessions/initial-scope/test.ts +++ b/dev-packages/browser-integration-tests/suites/sessions/initial-scope/test.ts @@ -21,14 +21,6 @@ sentryTest('should start a new session with navigation.', async ({ getLocalTestU await page.route('**/foo', (route: Route) => route.continue({ url })); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const initSession = await getFirstSentryEnvelopeRequest(page, url); await page.click('#navigate'); diff --git a/dev-packages/browser-integration-tests/suites/sessions/start-session/test.ts b/dev-packages/browser-integration-tests/suites/sessions/start-session/test.ts index 65f9eef8e9ae..6dffb1f85902 100644 --- a/dev-packages/browser-integration-tests/suites/sessions/start-session/test.ts +++ b/dev-packages/browser-integration-tests/suites/sessions/start-session/test.ts @@ -19,14 +19,6 @@ sentryTest('should start a new session with navigation.', async ({ getLocalTestU const url = await getLocalTestUrl({ testDir: __dirname }); await page.route('**/foo', (route: Route) => route.continue({ url })); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const initSession = await getFirstSentryEnvelopeRequest(page, url); await page.locator('#navigate').click(); diff --git a/dev-packages/browser-integration-tests/suites/sessions/v7-hub-start-session/test.ts b/dev-packages/browser-integration-tests/suites/sessions/v7-hub-start-session/test.ts index 65f9eef8e9ae..6dffb1f85902 100644 --- a/dev-packages/browser-integration-tests/suites/sessions/v7-hub-start-session/test.ts +++ b/dev-packages/browser-integration-tests/suites/sessions/v7-hub-start-session/test.ts @@ -19,14 +19,6 @@ sentryTest('should start a new session with navigation.', async ({ getLocalTestU const url = await getLocalTestUrl({ testDir: __dirname }); await page.route('**/foo', (route: Route) => route.continue({ url })); - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const initSession = await getFirstSentryEnvelopeRequest(page, url); await page.locator('#navigate').click(); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegrationShim/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegrationShim/test.ts index c52bf2a6b68c..b55a5fef4d98 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegrationShim/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegrationShim/test.ts @@ -24,7 +24,7 @@ sentryTest( }); }); - const url = await getLocalTestPath({ testDir: __dirname }); + const url = await getLocalTestPath({ testDir: __dirname, skipDsnRouteHandler: true }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-late/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-late/test.ts index 1b6bc5bc686d..1fee219c8e5b 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-late/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-late/test.ts @@ -16,14 +16,6 @@ sentryTest('should capture an INP click event span after pageload', async ({ bro sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestPath({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-parametrized-late/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-parametrized-late/test.ts index a9d5191b5cf3..18be7b654140 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-parametrized-late/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-parametrized-late/test.ts @@ -18,14 +18,6 @@ sentryTest( sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestPath({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-parametrized/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-parametrized/test.ts index 87ba1fd8632c..bf5021250b42 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-parametrized/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-parametrized/test.ts @@ -17,14 +17,6 @@ sentryTest( sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestPath({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp/test.ts index 594bd9904052..e42028f9dff1 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp/test.ts @@ -18,14 +18,6 @@ sentryTest( sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestPath({ testDir: __dirname }); await page.goto(url); @@ -109,14 +101,6 @@ sentryTest( sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestPath({ testDir: __dirname }); await page.goto(url); diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation/test.ts b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation/test.ts index 9d158ea5491e..b62923be0e9b 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation/test.ts @@ -74,14 +74,6 @@ sentryTest('error after navigation has navigation traceId', async ({ getLocalTes sentryTest.skip(); } - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - const url = await getLocalTestUrl({ testDir: __dirname }); // ensure pageload transaction is finished diff --git a/dev-packages/browser-integration-tests/utils/fixtures.ts b/dev-packages/browser-integration-tests/utils/fixtures.ts index e154ddc25988..9455e8c8626c 100644 --- a/dev-packages/browser-integration-tests/utils/fixtures.ts +++ b/dev-packages/browser-integration-tests/utils/fixtures.ts @@ -31,8 +31,12 @@ const getAsset = (assetDir: string, asset: string): string => { export type TestFixtures = { _autoSnapshotSuffix: void; testDir: string; - getLocalTestPath: (options: { testDir: string }) => Promise; - getLocalTestUrl: (options: { testDir: string; skipRouteHandler?: boolean }) => Promise; + getLocalTestPath: (options: { testDir: string; skipDsnRouteHandler?: boolean }) => Promise; + getLocalTestUrl: (options: { + testDir: string; + skipRouteHandler?: boolean; + skipDsnRouteHandler?: boolean; + }) => Promise; forceFlushReplay: () => Promise; enableConsole: () => void; runInChromium: (fn: (...args: unknown[]) => unknown, args?: unknown[]) => unknown; @@ -55,7 +59,7 @@ const sentryTest = base.extend({ ], getLocalTestUrl: ({ page }, use) => { - return use(async ({ testDir, skipRouteHandler = false }) => { + return use(async ({ testDir, skipRouteHandler = false, skipDsnRouteHandler = false }) => { const pagePath = `${TEST_HOST}/index.html`; const tmpDir = path.join(testDir, 'dist', crypto.randomUUID()); @@ -68,6 +72,16 @@ const sentryTest = base.extend({ return tmpDir; } + if (!skipDsnRouteHandler) { + await page.route('https://dsn.ingest.sentry.io/**/*', route => { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ id: 'test-id' }), + }); + }); + } + await page.route(`${TEST_HOST}/*.*`, route => { const file = route.request().url().split('/').pop(); const filePath = path.resolve(tmpDir, `./${file}`); @@ -96,13 +110,23 @@ const sentryTest = base.extend({ }); }, - getLocalTestPath: ({}, use) => { - return use(async ({ testDir }) => { + getLocalTestPath: ({ page }, use) => { + return use(async ({ testDir, skipDsnRouteHandler }) => { const tmpDir = path.join(testDir, 'dist', crypto.randomUUID()); const pagePath = `file:///${path.resolve(tmpDir, './index.html')}`; await build(testDir, tmpDir); + if (!skipDsnRouteHandler) { + await page.route('https://dsn.ingest.sentry.io/**/*', route => { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ id: 'test-id' }), + }); + }); + } + return pagePath; }); }, diff --git a/dev-packages/bundle-analyzer-scenarios/package.json b/dev-packages/bundle-analyzer-scenarios/package.json index 8733d3b578b0..d3061f63ee2c 100644 --- a/dev-packages/bundle-analyzer-scenarios/package.json +++ b/dev-packages/bundle-analyzer-scenarios/package.json @@ -9,7 +9,7 @@ "private": true, "dependencies": { "html-webpack-plugin": "^5.6.0", - "webpack": "^5.94.0", + "webpack": "^5.95.0", "webpack-bundle-analyzer": "^4.10.2" }, "scripts": { diff --git a/dev-packages/e2e-tests/publish-packages.ts b/dev-packages/e2e-tests/publish-packages.ts index 408d046977a2..4f2cc4056826 100644 --- a/dev-packages/e2e-tests/publish-packages.ts +++ b/dev-packages/e2e-tests/publish-packages.ts @@ -12,6 +12,8 @@ const packageTarballPaths = glob.sync('packages/*/sentry-*.tgz', { // Publish built packages to the fake registry packageTarballPaths.forEach(tarballPath => { + // eslint-disable-next-line no-console + console.log(`Publishing tarball ${tarballPath} ...`); // `--userconfig` flag needs to be before `publish` childProcess.exec( `npm --userconfig ${__dirname}/test-registry.npmrc publish ${tarballPath}`, @@ -19,14 +21,10 @@ packageTarballPaths.forEach(tarballPath => { cwd: repositoryRoot, // Can't use __dirname here because npm would try to publish `@sentry-internal/e2e-tests` encoding: 'utf8', }, - (err, stdout, stderr) => { - // eslint-disable-next-line no-console - console.log(stdout); - // eslint-disable-next-line no-console - console.log(stderr); + err => { if (err) { // eslint-disable-next-line no-console - console.error(err); + console.error(`Error publishing tarball ${tarballPath}`, err); process.exit(1); } }, diff --git a/dev-packages/e2e-tests/test-applications/debug-id-sourcemaps/package.json b/dev-packages/e2e-tests/test-applications/debug-id-sourcemaps/package.json index bc61532b71fa..9295b7997ee6 100644 --- a/dev-packages/e2e-tests/test-applications/debug-id-sourcemaps/package.json +++ b/dev-packages/e2e-tests/test-applications/debug-id-sourcemaps/package.json @@ -13,9 +13,9 @@ "@sentry/node": "latest || *" }, "devDependencies": { - "rollup": "^4.0.2", + "rollup": "^4.24.2", "vitest": "^0.34.6", - "@sentry/rollup-plugin": "2.22.3" + "@sentry/rollup-plugin": "2.22.6" }, "volta": { "extends": "../../package.json" diff --git a/dev-packages/e2e-tests/test-applications/ember-classic/playwright.config.ts b/dev-packages/e2e-tests/test-applications/ember-classic/playwright.config.ts index a092503a9fc2..8d378b127c72 100644 --- a/dev-packages/e2e-tests/test-applications/ember-classic/playwright.config.ts +++ b/dev-packages/e2e-tests/test-applications/ember-classic/playwright.config.ts @@ -35,7 +35,7 @@ const config: PlaywrightTestConfig = { forbidOnly: !!process.env.CI, retries: 0, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: process.env.CI ? [['line'], ['junit', { outputFile: 'results.junit.xml' }]] : 'list', + reporter: process.env.CI ? [['list'], ['junit', { outputFile: 'results.junit.xml' }]] : 'list', /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ diff --git a/dev-packages/e2e-tests/test-applications/ember-embroider/playwright.config.ts b/dev-packages/e2e-tests/test-applications/ember-embroider/playwright.config.ts index a092503a9fc2..8d378b127c72 100644 --- a/dev-packages/e2e-tests/test-applications/ember-embroider/playwright.config.ts +++ b/dev-packages/e2e-tests/test-applications/ember-embroider/playwright.config.ts @@ -35,7 +35,7 @@ const config: PlaywrightTestConfig = { forbidOnly: !!process.env.CI, retries: 0, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: process.env.CI ? [['line'], ['junit', { outputFile: 'results.junit.xml' }]] : 'list', + reporter: process.env.CI ? [['list'], ['junit', { outputFile: 'results.junit.xml' }]] : 'list', /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ diff --git a/dev-packages/e2e-tests/test-applications/nextjs-13/.gitignore b/dev-packages/e2e-tests/test-applications/nextjs-13/.gitignore index b7a8bf3b3701..68d4c4a9cbf2 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-13/.gitignore +++ b/dev-packages/e2e-tests/test-applications/nextjs-13/.gitignore @@ -38,5 +38,6 @@ next-env.d.ts !*.d.ts test-results +event-dumps .vscode diff --git a/dev-packages/e2e-tests/test-applications/nextjs-13/package.json b/dev-packages/e2e-tests/test-applications/nextjs-13/package.json index 5be9ecbfc32c..3e7a0ac88266 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-13/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-13/package.json @@ -17,7 +17,7 @@ "@types/node": "18.11.17", "@types/react": "18.0.26", "@types/react-dom": "18.0.9", - "next": "13.2.0", + "next": "13.5.7", "react": "18.2.0", "react-dom": "18.2.0", "typescript": "4.9.5" diff --git a/dev-packages/e2e-tests/test-applications/nextjs-13/pages/customPageExtension.page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-13/pages/[param]/customPageExtension.page.tsx similarity index 100% rename from dev-packages/e2e-tests/test-applications/nextjs-13/pages/customPageExtension.page.tsx rename to dev-packages/e2e-tests/test-applications/nextjs-13/pages/[param]/customPageExtension.page.tsx diff --git a/dev-packages/e2e-tests/test-applications/nextjs-13/pages/error-getServerSideProps.tsx b/dev-packages/e2e-tests/test-applications/nextjs-13/pages/[param]/error-getServerSideProps.tsx similarity index 100% rename from dev-packages/e2e-tests/test-applications/nextjs-13/pages/error-getServerSideProps.tsx rename to dev-packages/e2e-tests/test-applications/nextjs-13/pages/[param]/error-getServerSideProps.tsx diff --git a/dev-packages/e2e-tests/test-applications/nextjs-13/sentry.client.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-13/sentry.client.config.ts index 85bd765c9c44..f2c7e4aef94d 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-13/sentry.client.config.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-13/sentry.client.config.ts @@ -1,3 +1,5 @@ +'use client'; + import * as Sentry from '@sentry/nextjs'; Sentry.init({ diff --git a/dev-packages/e2e-tests/test-applications/nextjs-13/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/nextjs-13/start-event-proxy.mjs index 9983d484bcbc..b45472a5484f 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-13/start-event-proxy.mjs +++ b/dev-packages/e2e-tests/test-applications/nextjs-13/start-event-proxy.mjs @@ -1,6 +1,14 @@ +import * as fs from 'fs'; +import * as path from 'path'; import { startEventProxyServer } from '@sentry-internal/test-utils'; +const packageJson = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json'))); + startEventProxyServer({ port: 3031, proxyServerName: 'nextjs-13', + envelopeDumpPath: path.join( + process.cwd(), + `event-dumps/next-13-v${packageJson.dependencies.next}-${process.env.TEST_ENV}.dump`, + ), }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-13/tests/client/pages-dir-pageload.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-13/tests/client/pages-dir-pageload.test.ts index 8c74b2c99427..af59b41c2908 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-13/tests/client/pages-dir-pageload.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-13/tests/client/pages-dir-pageload.test.ts @@ -46,3 +46,38 @@ test('should create a pageload transaction when the `pages` directory is used', type: 'transaction', }); }); + +test('should create a pageload transaction with correct name when an error occurs in getServerSideProps', async ({ + page, +}) => { + const transactionPromise = waitForTransaction('nextjs-13', async transactionEvent => { + return ( + transactionEvent.transaction === '/[param]/error-getServerSideProps' && + transactionEvent.contexts?.trace?.op === 'pageload' + ); + }); + + await page.goto(`/something/error-getServerSideProps`, { waitUntil: 'networkidle' }); + + const transaction = await transactionPromise; + + expect(transaction).toMatchObject({ + contexts: { + trace: { + data: { + 'sentry.op': 'pageload', + 'sentry.origin': 'auto.pageload.nextjs.pages_router_instrumentation', + 'sentry.source': 'route', + }, + op: 'pageload', + origin: 'auto.pageload.nextjs.pages_router_instrumentation', + }, + }, + transaction: '/[param]/error-getServerSideProps', + transaction_info: { source: 'route' }, + type: 'transaction', + }); + + // Ensure the transaction name is not '/_error' + expect(transaction.transaction).not.toBe('/_error'); +}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-13/tests/isomorphic/getInitialProps.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-13/tests/isomorphic/getInitialProps.test.ts index 22da2071d533..570b19b3271d 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-13/tests/isomorphic/getInitialProps.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-13/tests/isomorphic/getInitialProps.test.ts @@ -11,7 +11,7 @@ test('should propagate serverside `getInitialProps` trace to client', async ({ p const serverTransactionPromise = waitForTransaction('nextjs-13', async transactionEvent => { return ( - transactionEvent.transaction === '/[param]/withInitialProps' && + transactionEvent.transaction === 'GET /[param]/withInitialProps' && transactionEvent.contexts?.trace?.op === 'http.server' ); }); @@ -47,7 +47,7 @@ test('should propagate serverside `getInitialProps` trace to client', async ({ p status: 'ok', }, }, - transaction: '/[param]/withInitialProps', + transaction: 'GET /[param]/withInitialProps', transaction_info: { source: 'route', }, diff --git a/dev-packages/e2e-tests/test-applications/nextjs-13/tests/isomorphic/getServerSideProps.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-13/tests/isomorphic/getServerSideProps.test.ts index 20bbbc9437f6..765864dbf4a1 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-13/tests/isomorphic/getServerSideProps.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-13/tests/isomorphic/getServerSideProps.test.ts @@ -11,7 +11,7 @@ test('Should record performance for getServerSideProps', async ({ page }) => { const serverTransactionPromise = waitForTransaction('nextjs-13', async transactionEvent => { return ( - transactionEvent.transaction === '/[param]/withServerSideProps' && + transactionEvent.transaction === 'GET /[param]/withServerSideProps' && transactionEvent.contexts?.trace?.op === 'http.server' ); }); @@ -47,7 +47,7 @@ test('Should record performance for getServerSideProps', async ({ page }) => { status: 'ok', }, }, - transaction: '/[param]/withServerSideProps', + transaction: 'GET /[param]/withServerSideProps', transaction_info: { source: 'route', }, diff --git a/dev-packages/e2e-tests/test-applications/nextjs-13/tests/server/excluded-api-endpoints.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-13/tests/server/excluded-api-endpoints.test.ts index 63082fee6e07..2d3854e2a2a4 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-13/tests/server/excluded-api-endpoints.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-13/tests/server/excluded-api-endpoints.test.ts @@ -1,7 +1,7 @@ import { expect, test } from '@playwright/test'; import { waitForTransaction } from '@sentry-internal/test-utils'; -test('should not automatically create transactions for routes that were excluded from auto wrapping (string)', async ({ +test('should not apply build-time instrumentation for routes that were excluded from auto wrapping (string)', async ({ request, }) => { const transactionPromise = waitForTransaction('nextjs-13', async transactionEvent => { @@ -13,17 +13,13 @@ test('should not automatically create transactions for routes that were excluded expect(await (await request.get(`/api/endpoint-excluded-with-string`)).text()).toBe('{"success":true}'); - let transactionPromiseReceived = false; - transactionPromise.then(() => { - transactionPromiseReceived = true; - }); - - await new Promise(resolve => setTimeout(resolve, 5_000)); + const transaction = await transactionPromise; - expect(transactionPromiseReceived).toBe(false); + expect(transaction.contexts?.trace?.data?.['sentry.origin']).toBeDefined(); + expect(transaction.contexts?.trace?.data?.['sentry.origin']).not.toBe('auto.http.nextjs'); // This is the origin set by the build time instrumentation }); -test('should not automatically create transactions for routes that were excluded from auto wrapping (regex)', async ({ +test('should not apply build-time instrumentation for routes that were excluded from auto wrapping (regex)', async ({ request, }) => { const transactionPromise = waitForTransaction('nextjs-13', async transactionEvent => { @@ -35,12 +31,8 @@ test('should not automatically create transactions for routes that were excluded expect(await (await request.get(`/api/endpoint-excluded-with-regex`)).text()).toBe('{"success":true}'); - let transactionPromiseReceived = false; - transactionPromise.then(() => { - transactionPromiseReceived = true; - }); - - await new Promise(resolve => setTimeout(resolve, 5_000)); + const transaction = await transactionPromise; - expect(transactionPromiseReceived).toBe(false); + expect(transaction.contexts?.trace?.data?.['sentry.origin']).toBeDefined(); + expect(transaction.contexts?.trace?.data?.['sentry.origin']).not.toBe('auto.http.nextjs'); // This is the origin set by the build time instrumentation }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-13/tests/server/getServerSideProps.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-13/tests/server/getServerSideProps.test.ts index 0c99ba302dfa..9ae79d7bd4b0 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-13/tests/server/getServerSideProps.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-13/tests/server/getServerSideProps.test.ts @@ -8,12 +8,12 @@ test('Should report an error event for errors thrown in getServerSideProps', asy const transactionEventPromise = waitForTransaction('nextjs-13', transactionEvent => { return ( - transactionEvent.transaction === '/error-getServerSideProps' && + transactionEvent.transaction === 'GET /[param]/error-getServerSideProps' && transactionEvent.contexts?.trace?.op === 'http.server' ); }); - await page.goto('/error-getServerSideProps'); + await page.goto('/dogsaregreat/error-getServerSideProps'); expect(await errorEventPromise).toMatchObject({ contexts: { @@ -40,7 +40,7 @@ test('Should report an error event for errors thrown in getServerSideProps', asy url: expect.stringMatching(/^http.*\/error-getServerSideProps/), }, timestamp: expect.any(Number), - transaction: 'getServerSideProps (/error-getServerSideProps)', + transaction: 'getServerSideProps (/[param]/error-getServerSideProps)', }); expect(await transactionEventPromise).toMatchObject({ @@ -60,11 +60,11 @@ test('Should report an error event for errors thrown in getServerSideProps', asy data: { 'http.response.status_code': 500, 'sentry.op': 'http.server', - 'sentry.origin': 'auto.function.nextjs', + 'sentry.origin': 'auto', 'sentry.source': 'route', }, op: 'http.server', - origin: 'auto.function.nextjs', + origin: 'auto', span_id: expect.any(String), status: 'internal_error', trace_id: expect.any(String), @@ -80,7 +80,7 @@ test('Should report an error event for errors thrown in getServerSideProps', asy }, start_timestamp: expect.any(Number), timestamp: expect.any(Number), - transaction: '/error-getServerSideProps', + transaction: 'GET /[param]/error-getServerSideProps', transaction_info: { source: 'route' }, type: 'transaction', }); @@ -95,11 +95,12 @@ test('Should report an error event for errors thrown in getServerSideProps in pa const transactionEventPromise = waitForTransaction('nextjs-13', transactionEvent => { return ( - transactionEvent.transaction === '/customPageExtension' && transactionEvent.contexts?.trace?.op === 'http.server' + transactionEvent.transaction === 'GET /[param]/customPageExtension' && + transactionEvent.contexts?.trace?.op === 'http.server' ); }); - await page.goto('/customPageExtension'); + await page.goto('/123/customPageExtension'); expect(await errorEventPromise).toMatchObject({ contexts: { @@ -126,7 +127,7 @@ test('Should report an error event for errors thrown in getServerSideProps in pa url: expect.stringMatching(/^http.*\/customPageExtension/), }, timestamp: expect.any(Number), - transaction: 'getServerSideProps (/customPageExtension)', + transaction: 'getServerSideProps (/[param]/customPageExtension)', }); expect(await transactionEventPromise).toMatchObject({ @@ -146,11 +147,11 @@ test('Should report an error event for errors thrown in getServerSideProps in pa data: { 'http.response.status_code': 500, 'sentry.op': 'http.server', - 'sentry.origin': 'auto.function.nextjs', + 'sentry.origin': 'auto', 'sentry.source': 'route', }, op: 'http.server', - origin: 'auto.function.nextjs', + origin: 'auto', span_id: expect.any(String), status: 'internal_error', trace_id: expect.any(String), @@ -166,7 +167,7 @@ test('Should report an error event for errors thrown in getServerSideProps in pa }, start_timestamp: expect.any(Number), timestamp: expect.any(Number), - transaction: '/customPageExtension', + transaction: 'GET /[param]/customPageExtension', transaction_info: { source: 'route' }, type: 'transaction', }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-14/.gitignore b/dev-packages/e2e-tests/test-applications/nextjs-14/.gitignore index e799cc33c4e7..ebdbfc025b6a 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-14/.gitignore +++ b/dev-packages/e2e-tests/test-applications/nextjs-14/.gitignore @@ -43,3 +43,4 @@ next-env.d.ts .vscode test-results +event-dumps diff --git a/dev-packages/e2e-tests/test-applications/nextjs-14/sentry.client.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-14/sentry.client.config.ts index 85bd765c9c44..f2c7e4aef94d 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-14/sentry.client.config.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-14/sentry.client.config.ts @@ -1,3 +1,5 @@ +'use client'; + import * as Sentry from '@sentry/nextjs'; Sentry.init({ diff --git a/dev-packages/e2e-tests/test-applications/nextjs-14/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/nextjs-14/start-event-proxy.mjs index 39babfb19b2a..7e0ebce43981 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-14/start-event-proxy.mjs +++ b/dev-packages/e2e-tests/test-applications/nextjs-14/start-event-proxy.mjs @@ -1,6 +1,14 @@ +import * as fs from 'fs'; +import * as path from 'path'; import { startEventProxyServer } from '@sentry-internal/test-utils'; +const packageJson = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json'))); + startEventProxyServer({ port: 3031, proxyServerName: 'nextjs-14', + envelopeDumpPath: path.join( + process.cwd(), + `event-dumps/next-14-v${packageJson.dependencies.next}-${process.env.TEST_ENV}.dump`, + ), }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/sentry.client.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-15/sentry.client.config.ts index 85bd765c9c44..f2c7e4aef94d 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-15/sentry.client.config.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/sentry.client.config.ts @@ -1,3 +1,5 @@ +'use client'; + import * as Sentry from '@sentry/nextjs'; Sentry.init({ diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/nextjs-15/start-event-proxy.mjs index 959b40d253e8..d78a76402bbb 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-15/start-event-proxy.mjs +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/start-event-proxy.mjs @@ -9,6 +9,6 @@ startEventProxyServer({ proxyServerName: 'nextjs-15', envelopeDumpPath: path.join( process.cwd(), - `event-dumps/next-${packageJson.dependencies.next}-${process.env.TEST_ENV}.dump`, + `event-dumps/next-15-v${packageJson.dependencies.next}-${process.env.TEST_ENV}.dump`, ), }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/.gitignore b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/.gitignore index e799cc33c4e7..ebdbfc025b6a 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/.gitignore +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/.gitignore @@ -43,3 +43,4 @@ next-env.d.ts .vscode test-results +event-dumps diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/app/layout.tsx b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/app/layout.tsx index d2aae8c9cd8d..006a01fcfa76 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/app/layout.tsx +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/app/layout.tsx @@ -9,31 +9,49 @@ export default function Layout({ children }: { children: React.ReactNode }) {

Layout (/)

  • - / + + / +
  • - /client-component + + /client-component +
  • - /client-component/parameter/42 + + /client-component/parameter/42 +
  • - /client-component/parameter/foo/bar/baz + + /client-component/parameter/foo/bar/baz +
  • - /server-component + + /server-component +
  • - /server-component/parameter/42 + + /server-component/parameter/42 +
  • - /server-component/parameter/foo/bar/baz + + /server-component/parameter/foo/bar/baz +
  • - /not-found + + /not-found +
  • - /redirect + + /redirect +
{children} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/middleware.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/middleware.ts index 6096fcfb1493..abc565f438b4 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/middleware.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/middleware.ts @@ -20,5 +20,5 @@ export async function middleware(request: NextRequest) { // See "Matching Paths" below to learn more export const config = { - matcher: ['/api/endpoint-behind-middleware'], + matcher: ['/api/endpoint-behind-middleware', '/api/endpoint-behind-faulty-middleware'], }; diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/pages/api/async-context-edge-endpoint.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/pages/api/async-context-edge-endpoint.ts index 6dc023fdf1ed..d6a129f9e056 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/pages/api/async-context-edge-endpoint.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/pages/api/async-context-edge-endpoint.ts @@ -7,21 +7,22 @@ export const config = { export default async function handler() { // Without a working async context strategy the two spans created by `Sentry.startSpan()` would be nested. - const outerSpanPromise = Sentry.withIsolationScope(() => { - return Sentry.startSpan({ name: 'outer-span' }, () => { - return new Promise(resolve => setTimeout(resolve, 300)); - }); + const outerSpanPromise = Sentry.startSpan({ name: 'outer-span' }, () => { + return new Promise(resolve => setTimeout(resolve, 300)); }); - setTimeout(() => { - Sentry.withIsolationScope(() => { - return Sentry.startSpan({ name: 'inner-span' }, () => { + const innerSpanPromise = new Promise(resolve => { + setTimeout(() => { + Sentry.startSpan({ name: 'inner-span' }, () => { return new Promise(resolve => setTimeout(resolve, 100)); + }).then(() => { + resolve(); }); - }); - }, 100); + }, 100); + }); await outerSpanPromise; + await innerSpanPromise; return new Response('ok', { status: 200 }); } diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/pages/api/endpoint-behind-faulty-middleware.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/pages/api/endpoint-behind-faulty-middleware.ts new file mode 100644 index 000000000000..2ca75a33ba7e --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/pages/api/endpoint-behind-faulty-middleware.ts @@ -0,0 +1,9 @@ +import type { NextApiRequest, NextApiResponse } from 'next'; + +type Data = { + name: string; +}; + +export default function handler(req: NextApiRequest, res: NextApiResponse) { + res.status(200).json({ name: 'John Doe' }); +} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/sentry.client.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/sentry.client.config.ts index 85bd765c9c44..f2c7e4aef94d 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/sentry.client.config.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/sentry.client.config.ts @@ -1,3 +1,5 @@ +'use client'; + import * as Sentry from '@sentry/nextjs'; Sentry.init({ diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/start-event-proxy.mjs index 7e8016bf98a5..c243a72b8f07 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/start-event-proxy.mjs +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/start-event-proxy.mjs @@ -1,6 +1,14 @@ +import * as fs from 'fs'; +import * as path from 'path'; import { startEventProxyServer } from '@sentry-internal/test-utils'; +const packageJson = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json'))); + startEventProxyServer({ port: 3031, proxyServerName: 'nextjs-app-dir', + envelopeDumpPath: path.join( + process.cwd(), + `event-dumps/next-app-dir-v${packageJson.dependencies.next}-${process.env.TEST_ENV}.dump`, + ), }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/async-context-edge.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/async-context-edge.test.ts index ecce719f0656..cb92cb2bab49 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/async-context-edge.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/async-context-edge.test.ts @@ -3,7 +3,10 @@ import { waitForTransaction } from '@sentry-internal/test-utils'; test('Should allow for async context isolation in the edge SDK', async ({ request }) => { const edgerouteTransactionPromise = waitForTransaction('nextjs-app-dir', async transactionEvent => { - return transactionEvent?.transaction === 'GET /api/async-context-edge-endpoint'; + return ( + transactionEvent?.transaction === 'GET /api/async-context-edge-endpoint' && + transactionEvent.contexts?.runtime?.name === 'vercel-edge' + ); }); await request.get('/api/async-context-edge-endpoint'); @@ -13,8 +16,5 @@ test('Should allow for async context isolation in the edge SDK', async ({ reques const outerSpan = asyncContextEdgerouteTransaction.spans?.find(span => span.description === 'outer-span'); const innerSpan = asyncContextEdgerouteTransaction.spans?.find(span => span.description === 'inner-span'); - // @ts-expect-error parent_span_id exists - expect(outerSpan?.parent_span_id).toStrictEqual(asyncContextEdgerouteTransaction.contexts?.trace?.span_id); - // @ts-expect-error parent_span_id exists - expect(innerSpan?.parent_span_id).toStrictEqual(asyncContextEdgerouteTransaction.contexts?.trace?.span_id); + expect(outerSpan?.parent_span_id).toStrictEqual(innerSpan?.parent_span_id); }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/client-app-routing-instrumentation.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/client-app-routing-instrumentation.test.ts index 35984640bcf6..abfe9b323d0f 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/client-app-routing-instrumentation.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/client-app-routing-instrumentation.test.ts @@ -42,9 +42,7 @@ test('Creates a navigation transaction for app router routes', async ({ page }) // It seems to differ between Next.js versions whether the route is parameterized or not (transactionEvent?.transaction === 'GET /server-component/parameter/foo/bar/baz' || transactionEvent?.transaction === 'GET /server-component/parameter/[...parameters]') && - transactionEvent.contexts?.trace?.data?.['http.target'].startsWith('/server-component/parameter/foo/bar/baz') && - (await clientNavigationTransactionPromise).contexts?.trace?.trace_id === - transactionEvent.contexts?.trace?.trace_id + transactionEvent.contexts?.trace?.data?.['http.target'].startsWith('/server-component/parameter/foo/bar/baz') ); }); @@ -52,6 +50,10 @@ test('Creates a navigation transaction for app router routes', async ({ page }) expect(await clientNavigationTransactionPromise).toBeDefined(); expect(await serverComponentTransactionPromise).toBeDefined(); + + expect((await serverComponentTransactionPromise).contexts?.trace?.trace_id).toBe( + (await clientNavigationTransactionPromise).contexts?.trace?.trace_id, + ); }); test('Creates a navigation transaction for `router.push()`', async ({ page }) => { diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/edge-route.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/edge-route.test.ts index df7ce7afd19a..88460e3ab533 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/edge-route.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/edge-route.test.ts @@ -4,7 +4,8 @@ import { waitForError, waitForTransaction } from '@sentry-internal/test-utils'; test('Should create a transaction for edge routes', async ({ request }) => { const edgerouteTransactionPromise = waitForTransaction('nextjs-app-dir', async transactionEvent => { return ( - transactionEvent?.transaction === 'GET /api/edge-endpoint' && transactionEvent?.contexts?.trace?.status === 'ok' + transactionEvent?.transaction === 'GET /api/edge-endpoint' && + transactionEvent.contexts?.runtime?.name === 'vercel-edge' ); }); @@ -19,47 +20,42 @@ test('Should create a transaction for edge routes', async ({ request }) => { expect(edgerouteTransaction.contexts?.trace?.status).toBe('ok'); expect(edgerouteTransaction.contexts?.trace?.op).toBe('http.server'); - expect(edgerouteTransaction.contexts?.runtime?.name).toBe('vercel-edge'); expect(edgerouteTransaction.request?.headers?.['x-yeet']).toBe('test-value'); }); -test('Should create a transaction with error status for faulty edge routes', async ({ request }) => { +test('Faulty edge routes', async ({ request }) => { const edgerouteTransactionPromise = waitForTransaction('nextjs-app-dir', async transactionEvent => { return ( transactionEvent?.transaction === 'GET /api/error-edge-endpoint' && - transactionEvent?.contexts?.trace?.status === 'internal_error' + transactionEvent.contexts?.runtime?.name === 'vercel-edge' ); }); - request.get('/api/error-edge-endpoint').catch(() => { - // Noop - }); - - const edgerouteTransaction = await edgerouteTransactionPromise; - - expect(edgerouteTransaction.contexts?.trace?.status).toBe('internal_error'); - expect(edgerouteTransaction.contexts?.trace?.op).toBe('http.server'); - expect(edgerouteTransaction.contexts?.runtime?.name).toBe('vercel-edge'); - - // Assert that isolation scope works properly - expect(edgerouteTransaction.tags?.['my-isolated-tag']).toBe(true); - expect(edgerouteTransaction.tags?.['my-global-scope-isolated-tag']).not.toBeDefined(); -}); - -test('Should record exceptions for faulty edge routes', async ({ request }) => { const errorEventPromise = waitForError('nextjs-app-dir', errorEvent => { - return errorEvent?.exception?.values?.[0]?.value === 'Edge Route Error'; + return ( + errorEvent?.exception?.values?.[0]?.value === 'Edge Route Error' && + errorEvent.contexts?.runtime?.name === 'vercel-edge' + ); }); request.get('/api/error-edge-endpoint').catch(() => { // Noop }); - const errorEvent = await errorEventPromise; + const [edgerouteTransaction, errorEvent] = await Promise.all([ + test.step('should create a transaction', () => edgerouteTransactionPromise), + test.step('should create an error event', () => errorEventPromise), + ]); - // Assert that isolation scope works properly - expect(errorEvent.tags?.['my-isolated-tag']).toBe(true); - expect(errorEvent.tags?.['my-global-scope-isolated-tag']).not.toBeDefined(); + test.step('should create transactions with the right fields', () => { + expect(edgerouteTransaction.contexts?.trace?.status).toBe('unknown_error'); + expect(edgerouteTransaction.contexts?.trace?.op).toBe('http.server'); + }); - expect(errorEvent.transaction).toBe('GET /api/error-edge-endpoint'); + test.step('should have scope isolation', () => { + expect(edgerouteTransaction.tags?.['my-isolated-tag']).toBe(true); + expect(edgerouteTransaction.tags?.['my-global-scope-isolated-tag']).not.toBeDefined(); + expect(errorEvent.tags?.['my-isolated-tag']).toBe(true); + expect(errorEvent.tags?.['my-global-scope-isolated-tag']).not.toBeDefined(); + }); }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/edge.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/edge.test.ts index f5277dee6f66..934cfa2e472d 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/edge.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/edge.test.ts @@ -1,6 +1,8 @@ import { expect, test } from '@playwright/test'; import { waitForError, waitForTransaction } from '@sentry-internal/test-utils'; +const packageJson = require('../package.json'); + test('Should record exceptions for faulty edge server components', async ({ page }) => { const errorEventPromise = waitForError('nextjs-app-dir', errorEvent => { return errorEvent?.exception?.values?.[0]?.value === 'Edge Server Component Error'; @@ -20,8 +22,14 @@ test('Should record exceptions for faulty edge server components', async ({ page }); test('Should record transaction for edge server components', async ({ page }) => { + const nextjsVersion = packageJson.dependencies.next; + const nextjsMajor = Number(nextjsVersion.split('.')[0]); + const serverComponentTransactionPromise = waitForTransaction('nextjs-app-dir', async transactionEvent => { - return transactionEvent?.transaction === 'Page Server Component (/edge-server-components)'; + return ( + transactionEvent?.transaction === 'GET /edge-server-components' && + transactionEvent.contexts?.runtime?.name === 'vercel-edge' + ); }); await page.goto('/edge-server-components'); @@ -29,9 +37,14 @@ test('Should record transaction for edge server components', async ({ page }) => const serverComponentTransaction = await serverComponentTransactionPromise; expect(serverComponentTransaction).toBeDefined(); - expect(serverComponentTransaction.request?.headers).toBeDefined(); + expect(serverComponentTransaction.contexts?.trace?.op).toBe('http.server'); - // Assert that isolation scope works properly - expect(serverComponentTransaction.tags?.['my-isolated-tag']).toBe(true); - expect(serverComponentTransaction.tags?.['my-global-scope-isolated-tag']).not.toBeDefined(); + // For some reason headers aren't picked up on Next.js 13 - also causing scope isolation to be broken + if (nextjsMajor >= 14) { + expect(serverComponentTransaction.request?.headers).toBeDefined(); + + // Assert that isolation scope works properly + expect(serverComponentTransaction.tags?.['my-isolated-tag']).toBe(true); + expect(serverComponentTransaction.tags?.['my-global-scope-isolated-tag']).not.toBeDefined(); + } }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/middleware.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/middleware.test.ts index 11a5f48799bd..a00a29672ed6 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/middleware.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/middleware.test.ts @@ -3,7 +3,7 @@ import { waitForError, waitForTransaction } from '@sentry-internal/test-utils'; test('Should create a transaction for middleware', async ({ request }) => { const middlewareTransactionPromise = waitForTransaction('nextjs-app-dir', async transactionEvent => { - return transactionEvent?.transaction === 'middleware' && transactionEvent?.contexts?.trace?.status === 'ok'; + return transactionEvent?.transaction === 'middleware GET /api/endpoint-behind-middleware'; }); const response = await request.get('/api/endpoint-behind-middleware'); @@ -12,53 +12,50 @@ test('Should create a transaction for middleware', async ({ request }) => { const middlewareTransaction = await middlewareTransactionPromise; expect(middlewareTransaction.contexts?.trace?.status).toBe('ok'); - expect(middlewareTransaction.contexts?.trace?.op).toBe('middleware.nextjs'); + expect(middlewareTransaction.contexts?.trace?.op).toBe('http.server.middleware'); expect(middlewareTransaction.contexts?.runtime?.name).toBe('vercel-edge'); + expect(middlewareTransaction.transaction_info?.source).toBe('url'); // Assert that isolation scope works properly expect(middlewareTransaction.tags?.['my-isolated-tag']).toBe(true); expect(middlewareTransaction.tags?.['my-global-scope-isolated-tag']).not.toBeDefined(); }); -test('Should create a transaction with error status for faulty middleware', async ({ request }) => { +test('Faulty middlewares', async ({ request }) => { const middlewareTransactionPromise = waitForTransaction('nextjs-app-dir', async transactionEvent => { - return ( - transactionEvent?.transaction === 'middleware' && transactionEvent?.contexts?.trace?.status === 'internal_error' - ); + return transactionEvent?.transaction === 'middleware GET /api/endpoint-behind-faulty-middleware'; }); - request.get('/api/endpoint-behind-middleware', { headers: { 'x-should-throw': '1' } }).catch(() => { - // Noop - }); - - const middlewareTransaction = await middlewareTransactionPromise; - - expect(middlewareTransaction.contexts?.trace?.status).toBe('internal_error'); - expect(middlewareTransaction.contexts?.trace?.op).toBe('middleware.nextjs'); - expect(middlewareTransaction.contexts?.runtime?.name).toBe('vercel-edge'); -}); - -test('Records exceptions happening in middleware', async ({ request }) => { const errorEventPromise = waitForError('nextjs-app-dir', errorEvent => { return errorEvent?.exception?.values?.[0]?.value === 'Middleware Error'; }); - request.get('/api/endpoint-behind-middleware', { headers: { 'x-should-throw': '1' } }).catch(() => { + request.get('/api/endpoint-behind-faulty-middleware', { headers: { 'x-should-throw': '1' } }).catch(() => { // Noop }); - const errorEvent = await errorEventPromise; + await test.step('should record transactions', async () => { + const middlewareTransaction = await middlewareTransactionPromise; + expect(middlewareTransaction.contexts?.trace?.status).toBe('unknown_error'); + expect(middlewareTransaction.contexts?.trace?.op).toBe('http.server.middleware'); + expect(middlewareTransaction.contexts?.runtime?.name).toBe('vercel-edge'); + expect(middlewareTransaction.transaction_info?.source).toBe('url'); + }); - // Assert that isolation scope works properly - expect(errorEvent.tags?.['my-isolated-tag']).toBe(true); - expect(errorEvent.tags?.['my-global-scope-isolated-tag']).not.toBeDefined(); - expect(errorEvent.transaction).toBe('middleware'); + await test.step('should record exceptions', async () => { + const errorEvent = await errorEventPromise; + + // Assert that isolation scope works properly + expect(errorEvent.tags?.['my-isolated-tag']).toBe(true); + expect(errorEvent.tags?.['my-global-scope-isolated-tag']).not.toBeDefined(); + expect(errorEvent.transaction).toBe('middleware GET /api/endpoint-behind-faulty-middleware'); + }); }); test('Should trace outgoing fetch requests inside middleware and create breadcrumbs for it', async ({ request }) => { const middlewareTransactionPromise = waitForTransaction('nextjs-app-dir', async transactionEvent => { return ( - transactionEvent?.transaction === 'middleware' && + transactionEvent?.transaction === 'middleware GET /api/endpoint-behind-middleware' && !!transactionEvent.spans?.find(span => span.op === 'http.client') ); }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/pages-ssr-errors.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/pages-ssr-errors.test.ts index a67e4328ba1c..10a4cd77f111 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/pages-ssr-errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/pages-ssr-errors.test.ts @@ -8,7 +8,7 @@ test('Will capture error for SSR rendering error with a connected trace (Class C const serverComponentTransaction = waitForTransaction('nextjs-app-dir', async transactionEvent => { return ( - transactionEvent?.transaction === '/pages-router/ssr-error-class' && + transactionEvent?.transaction === 'GET /pages-router/ssr-error-class' && (await errorEventPromise).contexts?.trace?.trace_id === transactionEvent.contexts?.trace?.trace_id ); }); @@ -26,7 +26,7 @@ test('Will capture error for SSR rendering error with a connected trace (Functio const ssrTransactionPromise = waitForTransaction('nextjs-app-dir', async transactionEvent => { return ( - transactionEvent?.transaction === '/pages-router/ssr-error-fc' && + transactionEvent?.transaction === 'GET /pages-router/ssr-error-fc' && (await errorEventPromise).contexts?.trace?.trace_id === transactionEvent.contexts?.trace?.trace_id ); }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/route-handlers.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/route-handlers.test.ts index afa02e60884a..648ee81839ac 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/route-handlers.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/route-handlers.test.ts @@ -54,19 +54,25 @@ test('Should record exceptions and transactions for faulty route handlers', asyn expect(routehandlerError.tags?.['my-isolated-tag']).toBe(true); expect(routehandlerError.tags?.['my-global-scope-isolated-tag']).not.toBeDefined(); - expect(routehandlerTransaction.contexts?.trace?.status).toBe('unknown_error'); + expect(routehandlerTransaction.contexts?.trace?.status).toBe('internal_error'); expect(routehandlerTransaction.contexts?.trace?.op).toBe('http.server'); - expect(routehandlerTransaction.contexts?.trace?.origin).toBe('auto.function.nextjs'); + expect(routehandlerTransaction.contexts?.trace?.origin).toContain('auto'); expect(routehandlerError.exception?.values?.[0].value).toBe('route-handler-error'); + expect(routehandlerError.request?.method).toBe('PUT'); + expect(routehandlerError.request?.url).toContain('/route-handlers/baz/error'); + expect(routehandlerError.transaction).toBe('PUT /route-handlers/[param]/error'); }); test.describe('Edge runtime', () => { test('should create a transaction for route handlers', async ({ request }) => { const routehandlerTransactionPromise = waitForTransaction('nextjs-app-dir', async transactionEvent => { - return transactionEvent?.transaction === 'PATCH /route-handlers/[param]/edge'; + return ( + transactionEvent?.transaction === 'PATCH /route-handlers/[param]/edge' && + transactionEvent.contexts?.runtime?.name === 'vercel-edge' + ); }); const response = await request.patch('/route-handlers/bar/edge'); @@ -80,11 +86,17 @@ test.describe('Edge runtime', () => { test('should record exceptions and transactions for faulty route handlers', async ({ request }) => { const errorEventPromise = waitForError('nextjs-app-dir', errorEvent => { - return errorEvent?.exception?.values?.[0]?.value === 'route-handler-edge-error'; + return ( + errorEvent?.exception?.values?.[0]?.value === 'route-handler-edge-error' && + errorEvent.contexts?.runtime?.name === 'vercel-edge' + ); }); const routehandlerTransactionPromise = waitForTransaction('nextjs-app-dir', async transactionEvent => { - return transactionEvent?.transaction === 'DELETE /route-handlers/[param]/edge'; + return ( + transactionEvent?.transaction === 'DELETE /route-handlers/[param]/edge' && + transactionEvent.contexts?.runtime?.name === 'vercel-edge' + ); }); await request.delete('/route-handlers/baz/edge').catch(() => { @@ -100,12 +112,10 @@ test.describe('Edge runtime', () => { expect(routehandlerError.tags?.['my-isolated-tag']).toBe(true); expect(routehandlerError.tags?.['my-global-scope-isolated-tag']).not.toBeDefined(); - expect(routehandlerTransaction.contexts?.trace?.status).toBe('internal_error'); + expect(routehandlerTransaction.contexts?.trace?.status).toBe('unknown_error'); expect(routehandlerTransaction.contexts?.trace?.op).toBe('http.server'); - expect(routehandlerTransaction.contexts?.runtime?.name).toBe('vercel-edge'); expect(routehandlerError.exception?.values?.[0].value).toBe('route-handler-edge-error'); - expect(routehandlerError.contexts?.runtime?.name).toBe('vercel-edge'); expect(routehandlerError.transaction).toBe('DELETE /route-handlers/[param]/edge'); }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/server-components.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/server-components.test.ts index 49afe791328f..75f30075a47f 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/server-components.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/server-components.test.ts @@ -16,7 +16,7 @@ test('Sends a transaction for a request to app router', async ({ page }) => { expect(transactionEvent.contexts?.trace).toEqual({ data: expect.objectContaining({ 'sentry.op': 'http.server', - 'sentry.origin': 'auto.http.otel.http', + 'sentry.origin': 'auto', 'sentry.sample_rate': 1, 'sentry.source': 'route', 'http.method': 'GET', @@ -27,7 +27,7 @@ test('Sends a transaction for a request to app router', async ({ page }) => { 'otel.kind': 'SERVER', }), op: 'http.server', - origin: 'auto.http.otel.http', + origin: 'auto', span_id: expect.any(String), status: 'ok', trace_id: expect.any(String), diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/transactions.test.ts index 8d2489bab34d..278b6b1074eb 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/transactions.test.ts @@ -76,6 +76,34 @@ test('Should send a transaction for instrumented server actions', async ({ page expect(Object.keys(transactionEvent.request?.headers || {}).length).toBeGreaterThan(0); }); +test('Should send a wrapped server action as a child of a nextjs transaction', async ({ page }) => { + const nextjsVersion = packageJson.dependencies.next; + const nextjsMajor = Number(nextjsVersion.split('.')[0]); + test.skip(!isNaN(nextjsMajor) && nextjsMajor < 14, 'only applies to nextjs apps >= version 14'); + test.skip(process.env.TEST_ENV === 'development', 'this magically only works in production'); + + const nextjsPostTransactionPromise = waitForTransaction('nextjs-app-dir', async transactionEvent => { + return ( + transactionEvent?.transaction === 'POST /server-action' && transactionEvent.contexts?.trace?.origin === 'auto' + ); + }); + + const serverActionTransactionPromise = waitForTransaction('nextjs-app-dir', async transactionEvent => { + return transactionEvent?.transaction === 'serverAction/myServerAction'; + }); + + await page.goto('/server-action'); + await page.getByText('Run Action').click(); + + const nextjsTransaction = await nextjsPostTransactionPromise; + const serverActionTransaction = await serverActionTransactionPromise; + + expect(nextjsTransaction).toBeDefined(); + expect(serverActionTransaction).toBeDefined(); + + expect(nextjsTransaction.contexts?.trace?.span_id).toBe(serverActionTransaction.contexts?.trace?.parent_span_id); +}); + test('Should set not_found status for server actions calling notFound()', async ({ page }) => { const nextjsVersion = packageJson.dependencies.next; const nextjsMajor = Number(nextjsVersion.split('.')[0]); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-t3/sentry.client.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-t3/sentry.client.config.ts index 0e3121a8f01b..6d63ba9325fe 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-t3/sentry.client.config.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-t3/sentry.client.config.ts @@ -1,3 +1,5 @@ +'use client'; + import * as Sentry from '@sentry/nextjs'; Sentry.init({ diff --git a/dev-packages/e2e-tests/test-applications/nextjs-turbo/.gitignore b/dev-packages/e2e-tests/test-applications/nextjs-turbo/.gitignore new file mode 100644 index 000000000000..ebdbfc025b6a --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-turbo/.gitignore @@ -0,0 +1,46 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +!*.d.ts + +# Sentry +.sentryclirc + +.vscode + +test-results +event-dumps diff --git a/dev-packages/e2e-tests/test-applications/nextjs-turbo/.npmrc b/dev-packages/e2e-tests/test-applications/nextjs-turbo/.npmrc new file mode 100644 index 000000000000..070f80f05092 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-turbo/.npmrc @@ -0,0 +1,2 @@ +@sentry:registry=http://127.0.0.1:4873 +@sentry-internal:registry=http://127.0.0.1:4873 diff --git a/dev-packages/e2e-tests/test-applications/nextjs-turbo/app/[param]/rsc-page-error/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-turbo/app/[param]/rsc-page-error/page.tsx new file mode 100644 index 000000000000..a6ae11918445 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-turbo/app/[param]/rsc-page-error/page.tsx @@ -0,0 +1,9 @@ +export const dynamic = 'force-dynamic'; + +export default function Page() { + if (Math.random() > -1) { + throw new Error('page rsc render error'); + } + + return null; +} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-turbo/app/global-error.tsx b/dev-packages/e2e-tests/test-applications/nextjs-turbo/app/global-error.tsx new file mode 100644 index 000000000000..912ad3606a61 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-turbo/app/global-error.tsx @@ -0,0 +1,27 @@ +'use client'; + +import * as Sentry from '@sentry/nextjs'; +import NextError from 'next/error'; +import { useEffect } from 'react'; + +export default function GlobalError({ + error, +}: { + error: Error & { digest?: string }; +}) { + useEffect(() => { + Sentry.captureException(error); + }, [error]); + + return ( + + + {/* `NextError` is the default Next.js error page component. Its type + definition requires a `statusCode` prop. However, since the App Router + does not expose status codes for errors, we simply pass 0 to render a + generic error message. */} + + + + ); +} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-turbo/app/layout.tsx b/dev-packages/e2e-tests/test-applications/nextjs-turbo/app/layout.tsx new file mode 100644 index 000000000000..999836e58b3b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-turbo/app/layout.tsx @@ -0,0 +1,12 @@ +import { HackComponentToRunSideEffectsInSentryClientConfig } from '../sentry.client.config'; + +export default function Layout({ children }: { children: React.ReactNode }) { + return ( + + + + {children} + + + ); +} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-turbo/globals.d.ts b/dev-packages/e2e-tests/test-applications/nextjs-turbo/globals.d.ts new file mode 100644 index 000000000000..109dbcd55648 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-turbo/globals.d.ts @@ -0,0 +1,4 @@ +interface Window { + recordedTransactions?: string[]; + capturedExceptionId?: string; +} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-turbo/instrumentation.ts b/dev-packages/e2e-tests/test-applications/nextjs-turbo/instrumentation.ts new file mode 100644 index 000000000000..964f937c439a --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-turbo/instrumentation.ts @@ -0,0 +1,13 @@ +import * as Sentry from '@sentry/nextjs'; + +export async function register() { + if (process.env.NEXT_RUNTIME === 'nodejs') { + await import('./sentry.server.config'); + } + + if (process.env.NEXT_RUNTIME === 'edge') { + await import('./sentry.edge.config'); + } +} + +export const onRequestError = Sentry.captureRequestError; diff --git a/dev-packages/e2e-tests/test-applications/nextjs-turbo/next-env.d.ts b/dev-packages/e2e-tests/test-applications/nextjs-turbo/next-env.d.ts new file mode 100644 index 000000000000..725dd6f24515 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-turbo/next-env.d.ts @@ -0,0 +1,6 @@ +/// +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. diff --git a/dev-packages/e2e-tests/test-applications/nextjs-turbo/next.config.js b/dev-packages/e2e-tests/test-applications/nextjs-turbo/next.config.js new file mode 100644 index 000000000000..e09e64bac6a2 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-turbo/next.config.js @@ -0,0 +1,12 @@ +const { withSentryConfig } = require('@sentry/nextjs'); + +/** @type {import('next').NextConfig} */ +const nextConfig = { + experimental: { + turbo: {}, // Enables Turbopack for builds + }, +}; + +module.exports = withSentryConfig(nextConfig, { + silent: true, +}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-turbo/package.json b/dev-packages/e2e-tests/test-applications/nextjs-turbo/package.json new file mode 100644 index 000000000000..900e0b5b2efc --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-turbo/package.json @@ -0,0 +1,49 @@ +{ + "name": "create-next-app", + "version": "0.1.0", + "private": true, + "scripts": { + "build": "next build > .tmp_build_stdout 2> .tmp_build_stderr || (cat .tmp_build_stdout && cat .tmp_build_stderr && exit 1)", + "clean": "npx rimraf node_modules pnpm-lock.yaml", + "test:prod": "TEST_ENV=production playwright test", + "test:dev": "TEST_ENV=development playwright test", + "test:build": "pnpm install && npx playwright install && pnpm build", + "test:build-canary": "pnpm install && pnpm add next@canary && pnpm add react@canary && pnpm add react-dom@canary && npx playwright install && pnpm build", + "test:build-latest": "pnpm install && pnpm add next@latest && pnpm add react@rc && pnpm add react-dom@rc && npx playwright install && pnpm build", + "test:assert": "pnpm test:prod && pnpm test:dev" + }, + "dependencies": { + "@sentry/nextjs": "latest || *", + "@types/node": "18.11.17", + "@types/react": "18.0.26", + "@types/react-dom": "18.0.9", + "next": "15.0.0", + "react": "rc", + "react-dom": "rc", + "typescript": "4.9.5" + }, + "devDependencies": { + "@playwright/test": "^1.44.1", + "@sentry-internal/test-utils": "link:../../../test-utils", + "@sentry-internal/feedback": "latest || *", + "@sentry-internal/replay-canvas": "latest || *", + "@sentry-internal/browser-utils": "latest || *", + "@sentry/browser": "latest || *", + "@sentry/core": "latest || *", + "@sentry/nextjs": "latest || *", + "@sentry/node": "latest || *", + "@sentry/opentelemetry": "latest || *", + "@sentry/react": "latest || *", + "@sentry-internal/replay": "latest || *", + "@sentry/types": "latest || *", + "@sentry/utils": "latest || *", + "@sentry/vercel-edge": "latest || *", + "import-in-the-middle": "1.11.2" + }, + "overrides": { + "import-in-the-middle": "1.11.2" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-turbo/pages/[param]/pages-router-client-trace-propagation.tsx b/dev-packages/e2e-tests/test-applications/nextjs-turbo/pages/[param]/pages-router-client-trace-propagation.tsx new file mode 100644 index 000000000000..be0391c3618e --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-turbo/pages/[param]/pages-router-client-trace-propagation.tsx @@ -0,0 +1,10 @@ +export default function Page() { +

Hello World!

; +} + +// getServerSideProps makes this page dynamic and allows tracing data to be inserted +export async function getServerSideProps() { + return { + props: {}, + }; +} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-turbo/pages/_app.tsx b/dev-packages/e2e-tests/test-applications/nextjs-turbo/pages/_app.tsx new file mode 100644 index 000000000000..6b90ee6bc586 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-turbo/pages/_app.tsx @@ -0,0 +1,6 @@ +import type { AppProps } from 'next/app'; +import '../sentry.client.config'; + +export default function CustomApp({ Component, pageProps }: AppProps) { + return ; +} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-turbo/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/nextjs-turbo/playwright.config.mjs new file mode 100644 index 000000000000..a62bec62a5c8 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-turbo/playwright.config.mjs @@ -0,0 +1,19 @@ +import { getPlaywrightConfig } from '@sentry-internal/test-utils'; +const testEnv = process.env.TEST_ENV; + +if (!testEnv) { + throw new Error('No test env defined'); +} + +const config = getPlaywrightConfig( + { + startCommand: testEnv === 'development' ? 'pnpm next dev -p 3030 --turbo' : 'pnpm next start -p 3030', + port: 3030, + }, + { + // This comes with the risk of tests leaking into each other but the tests run quite slow so we should parallelize + workers: '100%', + }, +); + +export default config; diff --git a/dev-packages/e2e-tests/test-applications/nextjs-turbo/sentry.client.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-turbo/sentry.client.config.ts new file mode 100644 index 000000000000..7a49f1b55e11 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-turbo/sentry.client.config.ts @@ -0,0 +1,17 @@ +'use client'; + +import * as Sentry from '@sentry/nextjs'; + +if (typeof window !== 'undefined') { + Sentry.init({ + environment: 'qa', // dynamic sampling bias to keep transactions + dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN, + tunnel: `http://localhost:3031/`, // proxy server + tracesSampleRate: 1.0, + sendDefaultPii: true, + }); +} + +export function HackComponentToRunSideEffectsInSentryClientConfig() { + return null; +} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-turbo/sentry.edge.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-turbo/sentry.edge.config.ts new file mode 100644 index 000000000000..067d2ead0b8b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-turbo/sentry.edge.config.ts @@ -0,0 +1,13 @@ +import * as Sentry from '@sentry/nextjs'; + +Sentry.init({ + environment: 'qa', // dynamic sampling bias to keep transactions + dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN, + tunnel: `http://localhost:3031/`, // proxy server + tracesSampleRate: 1.0, + sendDefaultPii: true, + transportOptions: { + // We are doing a lot of events at once in this test + bufferSize: 1000, + }, +}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-turbo/sentry.server.config.ts b/dev-packages/e2e-tests/test-applications/nextjs-turbo/sentry.server.config.ts new file mode 100644 index 000000000000..067d2ead0b8b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-turbo/sentry.server.config.ts @@ -0,0 +1,13 @@ +import * as Sentry from '@sentry/nextjs'; + +Sentry.init({ + environment: 'qa', // dynamic sampling bias to keep transactions + dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN, + tunnel: `http://localhost:3031/`, // proxy server + tracesSampleRate: 1.0, + sendDefaultPii: true, + transportOptions: { + // We are doing a lot of events at once in this test + bufferSize: 1000, + }, +}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-turbo/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/nextjs-turbo/start-event-proxy.mjs new file mode 100644 index 000000000000..2773cf8fa977 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-turbo/start-event-proxy.mjs @@ -0,0 +1,14 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { startEventProxyServer } from '@sentry-internal/test-utils'; + +const packageJson = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json'))); + +startEventProxyServer({ + port: 3031, + proxyServerName: 'nextjs-turbo', + envelopeDumpPath: path.join( + process.cwd(), + `event-dumps/nextjs-turbo-${packageJson.dependencies.next}-${process.env.TEST_ENV}.dump`, + ), +}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-turbo/tests/app-router/rsc-error.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-turbo/tests/app-router/rsc-error.test.ts new file mode 100644 index 000000000000..604faae7ea59 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-turbo/tests/app-router/rsc-error.test.ts @@ -0,0 +1,14 @@ +import { expect, test } from '@playwright/test'; +import { waitForError } from '@sentry-internal/test-utils'; + +test('Should capture errors from server components', async ({ page }) => { + const errorEventPromise = waitForError('nextjs-turbo', errorEvent => { + return !!errorEvent?.exception?.values?.some(value => value.value === 'page rsc render error'); + }); + + await page.goto(`/123/rsc-page-error`); + + const errorEvent = await errorEventPromise; + + expect(errorEvent).toBeDefined(); +}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-turbo/tests/pages-router/client-trace-propagation.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-turbo/tests/pages-router/client-trace-propagation.test.ts new file mode 100644 index 000000000000..20a9181d7f8e --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-turbo/tests/pages-router/client-trace-propagation.test.ts @@ -0,0 +1,20 @@ +import { expect, test } from '@playwright/test'; +import { waitForTransaction } from '@sentry-internal/test-utils'; + +test('Should propagate traces from server to client in pages router', async ({ page }) => { + const serverTransactionPromise = waitForTransaction('nextjs-turbo', async transactionEvent => { + return transactionEvent?.transaction === 'GET /[param]/pages-router-client-trace-propagation'; + }); + + const pageloadTransactionPromise = waitForTransaction('nextjs-turbo', async transactionEvent => { + return transactionEvent?.transaction === '/[param]/pages-router-client-trace-propagation'; + }); + + await page.goto(`/123/pages-router-client-trace-propagation`); + + const serverTransaction = await serverTransactionPromise; + const pageloadTransaction = await pageloadTransactionPromise; + + expect(serverTransaction.contexts?.trace?.trace_id).toBeDefined(); + expect(pageloadTransaction.contexts?.trace?.trace_id).toBe(serverTransaction.contexts?.trace?.trace_id); +}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-turbo/tsconfig.json b/dev-packages/e2e-tests/test-applications/nextjs-turbo/tsconfig.json new file mode 100644 index 000000000000..ef9e351d7a7b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-turbo/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "es2018", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "plugins": [ + { + "name": "next" + } + ], + "incremental": true + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "next.config.js", ".next/types/**/*.ts"], + "exclude": ["node_modules", "playwright.config.ts"] +} diff --git a/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts b/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts index a35bf4657c64..546639e8a766 100644 --- a/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts +++ b/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts @@ -50,6 +50,8 @@ const DEPENDENTS: Dependent[] = [ ignoreExports: [ // not supported in bun: 'NodeClient', + // Bun doesn't emit the required diagnostics_channel events + 'processThreadBreadcrumbIntegration', ], }, { diff --git a/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/playwright.config.mjs index f29509db795c..39bf757e0fd8 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/playwright.config.mjs +++ b/dev-packages/e2e-tests/test-applications/node-express-send-to-sentry/playwright.config.mjs @@ -23,7 +23,7 @@ const config = { /* Retry on CI only */ retries: 0, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: process.env.CI ? [['line'], ['junit', { outputFile: 'results.junit.xml' }]] : 'list', + reporter: process.env.CI ? [['list'], ['junit', { outputFile: 'results.junit.xml' }]] : 'list', /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ diff --git a/dev-packages/e2e-tests/test-applications/nuxt-4/app/pages/pinia-cart.vue b/dev-packages/e2e-tests/test-applications/nuxt-4/app/pages/pinia-cart.vue new file mode 100644 index 000000000000..3d210cf459de --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nuxt-4/app/pages/pinia-cart.vue @@ -0,0 +1,73 @@ + + + + + + + diff --git a/dev-packages/e2e-tests/test-applications/nuxt-4/nuxt.config.ts b/dev-packages/e2e-tests/test-applications/nuxt-4/nuxt.config.ts index c00ba0d5d9ed..da988a9ee003 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-4/nuxt.config.ts +++ b/dev-packages/e2e-tests/test-applications/nuxt-4/nuxt.config.ts @@ -4,7 +4,7 @@ export default defineNuxtConfig({ compatibilityDate: '2024-04-03', imports: { autoImport: false }, - modules: ['@sentry/nuxt/module'], + modules: ['@pinia/nuxt', '@sentry/nuxt/module'], runtimeConfig: { public: { diff --git a/dev-packages/e2e-tests/test-applications/nuxt-4/package.json b/dev-packages/e2e-tests/test-applications/nuxt-4/package.json index db56273a7493..178804768e87 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-4/package.json +++ b/dev-packages/e2e-tests/test-applications/nuxt-4/package.json @@ -14,6 +14,7 @@ "test:assert": "pnpm test" }, "dependencies": { + "@pinia/nuxt": "^0.5.5", "@sentry/nuxt": "latest || *", "nuxt": "^3.13.2" }, diff --git a/dev-packages/e2e-tests/test-applications/nuxt-4/sentry.client.config.ts b/dev-packages/e2e-tests/test-applications/nuxt-4/sentry.client.config.ts index 7547bafa6618..b32effbff3b8 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-4/sentry.client.config.ts +++ b/dev-packages/e2e-tests/test-applications/nuxt-4/sentry.client.config.ts @@ -7,4 +7,11 @@ Sentry.init({ tunnel: `http://localhost:3031/`, // proxy server tracesSampleRate: 1.0, trackComponents: true, + trackPinia: { + actionTransformer: action => `Transformed: ${action}`, + stateTransformer: state => ({ + transformed: true, + ...state, + }), + }, }); diff --git a/dev-packages/e2e-tests/test-applications/nuxt-4/stores/cart.ts b/dev-packages/e2e-tests/test-applications/nuxt-4/stores/cart.ts new file mode 100644 index 000000000000..cad52916ac25 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nuxt-4/stores/cart.ts @@ -0,0 +1,43 @@ +import { acceptHMRUpdate, defineStore } from '#imports'; + +export const useCartStore = defineStore({ + id: 'cart', + state: () => ({ + rawItems: [] as string[], + }), + getters: { + items: (state): Array<{ name: string; amount: number }> => + state.rawItems.reduce( + (items: any, item: any) => { + const existingItem = items.find((it: any) => it.name === item); + + if (!existingItem) { + items.push({ name: item, amount: 1 }); + } else { + existingItem.amount++; + } + + return items; + }, + [] as Array<{ name: string; amount: number }>, + ), + }, + actions: { + addItem(name: string) { + this.rawItems.push(name); + }, + + removeItem(name: string) { + const i = this.rawItems.lastIndexOf(name); + if (i > -1) this.rawItems.splice(i, 1); + }, + + throwError() { + throw new Error('error'); + }, + }, +}); + +if (import.meta.hot) { + import.meta.hot.accept(acceptHMRUpdate(useCartStore, import.meta.hot)); +} diff --git a/dev-packages/e2e-tests/test-applications/nuxt-4/tests/pinia.test.ts b/dev-packages/e2e-tests/test-applications/nuxt-4/tests/pinia.test.ts new file mode 100644 index 000000000000..44b057a29f15 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nuxt-4/tests/pinia.test.ts @@ -0,0 +1,35 @@ +import { expect, test } from '@playwright/test'; +import { waitForError } from '@sentry-internal/test-utils'; + +test('sends pinia action breadcrumbs and state context', async ({ page }) => { + await page.goto('/pinia-cart'); + + await page.locator('#item-input').fill('item'); + await page.locator('#item-add').click(); + + const errorPromise = waitForError('nuxt-4', async errorEvent => { + return errorEvent?.exception?.values?.[0].value === 'This is an error'; + }); + + await page.locator('#throw-error').click(); + + const error = await errorPromise; + + expect(error).toBeTruthy(); + expect(error.breadcrumbs?.length).toBeGreaterThan(0); + + const actionBreadcrumb = error.breadcrumbs?.find(breadcrumb => breadcrumb.category === 'action'); + + expect(actionBreadcrumb).toBeDefined(); + expect(actionBreadcrumb?.message).toBe('Transformed: addItem'); + expect(actionBreadcrumb?.level).toBe('info'); + + const stateContext = error.contexts?.state?.state; + + expect(stateContext).toBeDefined(); + expect(stateContext?.type).toBe('pinia'); + expect(stateContext?.value).toEqual({ + transformed: true, + rawItems: ['item'], + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/react-send-to-sentry/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/playwright.config.mjs index aa8fc9bfd4b7..566614052236 100644 --- a/dev-packages/e2e-tests/test-applications/react-send-to-sentry/playwright.config.mjs +++ b/dev-packages/e2e-tests/test-applications/react-send-to-sentry/playwright.config.mjs @@ -23,7 +23,7 @@ const config = { /* Opt out of parallel tests on CI. */ workers: 1, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: process.env.CI ? [['line'], ['junit', { outputFile: 'results.junit.xml' }]] : 'list', + reporter: process.env.CI ? [['list'], ['junit', { outputFile: 'results.junit.xml' }]] : 'list', /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ diff --git a/dev-packages/e2e-tests/test-applications/solidstart/package.json b/dev-packages/e2e-tests/test-applications/solidstart/package.json index e831a14c1f47..97a7e8812d73 100644 --- a/dev-packages/e2e-tests/test-applications/solidstart/package.json +++ b/dev-packages/e2e-tests/test-applications/solidstart/package.json @@ -3,20 +3,9 @@ "version": "0.0.0", "scripts": { "clean": "pnpx rimraf node_modules pnpm-lock.yaml .vinxi .output", - "clean:build": "pnpx rimraf .vinxi .output", "dev": "NODE_OPTIONS='--import ./src/instrument.server.mjs' vinxi dev", - "build": "vinxi build", - "//": [ - "We are using `vinxi dev` to start the server because `vinxi start` is experimental and ", - "doesn't correctly resolve modules for @sentry/solidstart/solidrouter.", - "This is currently not an issue outside of our repo. See: https://github.com/nksaraf/vinxi/issues/177", - "We run the build command to ensure building succeeds. However, keeping", - "build output around slows down the vite dev server when using `@sentry/vite-plugin` so we clear it out", - "before actually running the tests.", - "Cleaning the build output should be removed once we can use `vinxi start`." - ], - "preview": "pnpm clean:build && HOST=localhost PORT=3030 NODE_OPTIONS='--import ./src/instrument.server.mjs' vinxi dev", - "start": "HOST=localhost PORT=3030 NODE_OPTIONS='--import ./src/instrument.server.mjs' vinxi start", + "build": "vinxi build && sh ./post_build.sh", + "preview": "HOST=localhost PORT=3030 NODE_OPTIONS='--import ./src/instrument.server.mjs' vinxi start", "test:prod": "TEST_ENV=production playwright test", "test:build": "pnpm install && npx playwright install && pnpm build", "test:assert": "pnpm test:prod" @@ -41,5 +30,8 @@ "vite": "^5.2.8", "vite-plugin-solid": "^2.10.2", "vitest": "^1.5.0" + }, + "overrides": { + "@vercel/nft": "0.27.4" } } diff --git a/dev-packages/e2e-tests/test-applications/solidstart/post_build.sh b/dev-packages/e2e-tests/test-applications/solidstart/post_build.sh new file mode 100644 index 000000000000..6ed67c9afb8a --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidstart/post_build.sh @@ -0,0 +1,8 @@ +# TODO: Investigate the need for this script periodically and remove once these modules are correctly resolved. + +# This script copies `import-in-the-middle` and `@sentry/solidstart` from the E2E test project root `node_modules` +# to the nitro server build output `node_modules` as these are not properly resolved in our yarn workspace/pnpm +# e2e structure. Some files like `hook.mjs` and `@sentry/solidstart/solidrouter.server.js` are missing. This is +# not reproducible in an external project (when pinning `@vercel/nft` to `v0.27.0` and higher). +cp -r node_modules/.pnpm/import-in-the-middle@1.*/node_modules/import-in-the-middle .output/server/node_modules +cp -rL node_modules/@sentry/solidstart .output/server/node_modules/@sentry diff --git a/dev-packages/e2e-tests/test-applications/solidstart/tests/errorboundary.test.ts b/dev-packages/e2e-tests/test-applications/solidstart/tests/errorboundary.test.ts index b6164d541b93..b709760aab94 100644 --- a/dev-packages/e2e-tests/test-applications/solidstart/tests/errorboundary.test.ts +++ b/dev-packages/e2e-tests/test-applications/solidstart/tests/errorboundary.test.ts @@ -10,7 +10,6 @@ test('captures an exception', async ({ page }) => { ); }); - await page.goto('/error-boundary'); await page.goto('/error-boundary'); await page.locator('#caughtErrorBtn').click(); const errorEvent = await errorEventPromise; @@ -41,7 +40,6 @@ test('captures a second exception after resetting the boundary', async ({ page } ); }); - await page.goto('/error-boundary'); await page.goto('/error-boundary'); await page.locator('#caughtErrorBtn').click(); const firstErrorEvent = await firstErrorEventPromise; diff --git a/dev-packages/e2e-tests/test-applications/tanstack-router/package.json b/dev-packages/e2e-tests/test-applications/tanstack-router/package.json index 7bee3d4ba828..f2b82afa15c8 100644 --- a/dev-packages/e2e-tests/test-applications/tanstack-router/package.json +++ b/dev-packages/e2e-tests/test-applications/tanstack-router/package.json @@ -24,7 +24,7 @@ "@typescript-eslint/parser": "^7.2.0", "@vitejs/plugin-react-swc": "^3.5.0", "typescript": "^5.2.2", - "vite": "^5.2.0", + "vite": "^5.2.14", "@playwright/test": "^1.44.1", "@sentry-internal/test-utils": "link:../../../test-utils" }, diff --git a/dev-packages/e2e-tests/test-applications/tanstack-router/yarn.lock b/dev-packages/e2e-tests/test-applications/tanstack-router/yarn.lock index 75a09184cb81..6ef9cc8aa826 100644 --- a/dev-packages/e2e-tests/test-applications/tanstack-router/yarn.lock +++ b/dev-packages/e2e-tests/test-applications/tanstack-router/yarn.lock @@ -157,85 +157,95 @@ dependencies: playwright "1.46.1" -"@rollup/rollup-android-arm-eabi@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz#bbd0e616b2078cd2d68afc9824d1fadb2f2ffd27" - integrity sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ== - -"@rollup/rollup-android-arm64@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz#97255ef6384c5f73f4800c0de91f5f6518e21203" - integrity sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA== - -"@rollup/rollup-darwin-arm64@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz#b6dd74e117510dfe94541646067b0545b42ff096" - integrity sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w== - -"@rollup/rollup-darwin-x64@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz#e07d76de1cec987673e7f3d48ccb8e106d42c05c" - integrity sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA== - -"@rollup/rollup-linux-arm-gnueabihf@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz#9f1a6d218b560c9d75185af4b8bb42f9f24736b8" - integrity sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA== - -"@rollup/rollup-linux-arm-musleabihf@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz#53618b92e6ffb642c7b620e6e528446511330549" - integrity sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A== - -"@rollup/rollup-linux-arm64-gnu@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz#99a7ba5e719d4f053761a698f7b52291cefba577" - integrity sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw== - -"@rollup/rollup-linux-arm64-musl@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz#f53db99a45d9bc00ce94db8a35efa7c3c144a58c" - integrity sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ== - -"@rollup/rollup-linux-powerpc64le-gnu@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz#cbb0837408fe081ce3435cf3730e090febafc9bf" - integrity sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA== - -"@rollup/rollup-linux-riscv64-gnu@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz#8ed09c1d1262ada4c38d791a28ae0fea28b80cc9" - integrity sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg== - -"@rollup/rollup-linux-s390x-gnu@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz#938138d3c8e0c96f022252a28441dcfb17afd7ec" - integrity sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg== - -"@rollup/rollup-linux-x64-gnu@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz#1a7481137a54740bee1ded4ae5752450f155d942" - integrity sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w== - -"@rollup/rollup-linux-x64-musl@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz#f1186afc601ac4f4fc25fac4ca15ecbee3a1874d" - integrity sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg== - -"@rollup/rollup-win32-arm64-msvc@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz#ed6603e93636a96203c6915be4117245c1bd2daf" - integrity sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA== - -"@rollup/rollup-win32-ia32-msvc@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz#14e0b404b1c25ebe6157a15edb9c46959ba74c54" - integrity sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg== - -"@rollup/rollup-win32-x64-msvc@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz#5d694d345ce36b6ecf657349e03eb87297e68da4" - integrity sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g== +"@rollup/rollup-android-arm-eabi@4.24.2": + version "4.24.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.2.tgz#07db37fcd9d401aae165f662c0069efd61d4ffcc" + integrity sha512-ufoveNTKDg9t/b7nqI3lwbCG/9IJMhADBNjjz/Jn6LxIZxD7T5L8l2uO/wD99945F1Oo8FvgbbZJRguyk/BdzA== + +"@rollup/rollup-android-arm64@4.24.2": + version "4.24.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.2.tgz#160975402adf85ecd58a0721ad60ae1779a68147" + integrity sha512-iZoYCiJz3Uek4NI0J06/ZxUgwAfNzqltK0MptPDO4OR0a88R4h0DSELMsflS6ibMCJ4PnLvq8f7O1d7WexUvIA== + +"@rollup/rollup-darwin-arm64@4.24.2": + version "4.24.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.2.tgz#2b126f0aa4349694fe2941bcbcc4b0982b7f1a49" + integrity sha512-/UhrIxobHYCBfhi5paTkUDQ0w+jckjRZDZ1kcBL132WeHZQ6+S5v9jQPVGLVrLbNUebdIRpIt00lQ+4Z7ys4Rg== + +"@rollup/rollup-darwin-x64@4.24.2": + version "4.24.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.2.tgz#3f4987eff6195532037c50b8db92736e326b5bb2" + integrity sha512-1F/jrfhxJtWILusgx63WeTvGTwE4vmsT9+e/z7cZLKU8sBMddwqw3UV5ERfOV+H1FuRK3YREZ46J4Gy0aP3qDA== + +"@rollup/rollup-freebsd-arm64@4.24.2": + version "4.24.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.24.2.tgz#15fe184ecfafc635879500f6985c954e57697c44" + integrity sha512-1YWOpFcGuC6iGAS4EI+o3BV2/6S0H+m9kFOIlyFtp4xIX5rjSnL3AwbTBxROX0c8yWtiWM7ZI6mEPTI7VkSpZw== + +"@rollup/rollup-freebsd-x64@4.24.2": + version "4.24.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.24.2.tgz#c72d37315d36b6e0763b7aabb6ae53c361b45e05" + integrity sha512-3qAqTewYrCdnOD9Gl9yvPoAoFAVmPJsBvleabvx4bnu1Kt6DrB2OALeRVag7BdWGWLhP1yooeMLEi6r2nYSOjg== + +"@rollup/rollup-linux-arm-gnueabihf@4.24.2": + version "4.24.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.2.tgz#f274f81abf845dcca5f1f40d434a09a79a3a73a0" + integrity sha512-ArdGtPHjLqWkqQuoVQ6a5UC5ebdX8INPuJuJNWRe0RGa/YNhVvxeWmCTFQ7LdmNCSUzVZzxAvUznKaYx645Rig== + +"@rollup/rollup-linux-arm-musleabihf@4.24.2": + version "4.24.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.2.tgz#9edaeb1a9fa7d4469917cb0614f665f1cf050625" + integrity sha512-B6UHHeNnnih8xH6wRKB0mOcJGvjZTww1FV59HqJoTJ5da9LCG6R4SEBt6uPqzlawv1LoEXSS0d4fBlHNWl6iYw== + +"@rollup/rollup-linux-arm64-gnu@4.24.2": + version "4.24.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.2.tgz#6eb6851f594336bfa00f074f58a00a61e9751493" + integrity sha512-kr3gqzczJjSAncwOS6i7fpb4dlqcvLidqrX5hpGBIM1wtt0QEVtf4wFaAwVv8QygFU8iWUMYEoJZWuWxyua4GQ== + +"@rollup/rollup-linux-arm64-musl@4.24.2": + version "4.24.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.2.tgz#9d8dc8e80df8f156d2888ecb8d6c96d653580731" + integrity sha512-TDdHLKCWgPuq9vQcmyLrhg/bgbOvIQ8rtWQK7MRxJ9nvaxKx38NvY7/Lo6cYuEnNHqf6rMqnivOIPIQt6H2AoA== + +"@rollup/rollup-linux-powerpc64le-gnu@4.24.2": + version "4.24.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.2.tgz#358e3e7dda2d60c46ff7c74f7075045736df5b50" + integrity sha512-xv9vS648T3X4AxFFZGWeB5Dou8ilsv4VVqJ0+loOIgDO20zIhYfDLkk5xoQiej2RiSQkld9ijF/fhLeonrz2mw== + +"@rollup/rollup-linux-riscv64-gnu@4.24.2": + version "4.24.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.2.tgz#b08461ace599c3f0b5f27051f1756b6cf1c78259" + integrity sha512-tbtXwnofRoTt223WUZYiUnbxhGAOVul/3StZ947U4A5NNjnQJV5irKMm76G0LGItWs6y+SCjUn/Q0WaMLkEskg== + +"@rollup/rollup-linux-s390x-gnu@4.24.2": + version "4.24.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.2.tgz#daab36c9b5c8ac4bfe5a9c4c39ad711464b7dfee" + integrity sha512-gc97UebApwdsSNT3q79glOSPdfwgwj5ELuiyuiMY3pEWMxeVqLGKfpDFoum4ujivzxn6veUPzkGuSYoh5deQ2Q== + +"@rollup/rollup-linux-x64-gnu@4.24.2": + version "4.24.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.2.tgz#4cc3a4f31920bdb028dbfd7ce0e972a17424a63c" + integrity sha512-jOG/0nXb3z+EM6SioY8RofqqmZ+9NKYvJ6QQaa9Mvd3RQxlH68/jcB/lpyVt4lCiqr04IyaC34NzhUqcXbB5FQ== + +"@rollup/rollup-linux-x64-musl@4.24.2": + version "4.24.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.2.tgz#59800e26c538517ee05f4645315d9e1aded93200" + integrity sha512-XAo7cJec80NWx9LlZFEJQxqKOMz/lX3geWs2iNT5CHIERLFfd90f3RYLLjiCBm1IMaQ4VOX/lTC9lWfzzQm14Q== + +"@rollup/rollup-win32-arm64-msvc@4.24.2": + version "4.24.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.2.tgz#c80e2c33c952b6b171fa6ad9a97dfbb2e4ebee44" + integrity sha512-A+JAs4+EhsTjnPQvo9XY/DC0ztaws3vfqzrMNMKlwQXuniBKOIIvAAI8M0fBYiTCxQnElYu7mLk7JrhlQ+HeOw== + +"@rollup/rollup-win32-ia32-msvc@4.24.2": + version "4.24.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.2.tgz#a1e9d275cb16f6d5feb9c20aee7e897b1e193359" + integrity sha512-ZhcrakbqA1SCiJRMKSU64AZcYzlZ/9M5LaYil9QWxx9vLnkQ9Vnkve17Qn4SjlipqIIBFKjBES6Zxhnvh0EAEw== + +"@rollup/rollup-win32-x64-msvc@4.24.2": + version "4.24.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.2.tgz#0610af0fb8fec52be779d5b163bbbd6930150467" + integrity sha512-2mLH46K1u3r6uwc95hU+OR9q/ggYMpnS7pSp83Ece1HUQgF9Nh/QwTK5rcgbFnV9j+08yBrU5sA/P0RK2MSBNA== "@sentry-internal/browser-utils@8.4.0": version "8.4.0" @@ -276,7 +286,7 @@ "@sentry/utils" "8.4.0" "@sentry-internal/test-utils@link:../../../test-utils": - version "8.26.0" + version "8.35.0" "@sentry/browser@8.4.0": version "8.4.0" @@ -431,10 +441,10 @@ resolved "https://registry.yarnpkg.com/@tanstack/store/-/store-0.1.3.tgz#b8410435dac0a0f6d3fe77d49509f296905d4c73" integrity sha512-GnolmC8Fr4mvsHE1fGQmR3Nm0eBO3KnZjDU0a+P3TeQNM/dDscFGxtA7p31NplQNW3KwBw4t1RVFmz0VeKLxcw== -"@types/estree@1.0.5": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" - integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== +"@types/estree@1.0.6": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" + integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== "@types/prop-types@*": version "15.7.12" @@ -825,28 +835,30 @@ reusify@^1.0.4: integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== rollup@^4.13.0: - version "4.18.0" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.18.0.tgz#497f60f0c5308e4602cf41136339fbf87d5f5dda" - integrity sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg== + version "4.24.2" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.24.2.tgz#04bbe819c1a0cd933533b79687f5dc43efb7a7f0" + integrity sha512-do/DFGq5g6rdDhdpPq5qb2ecoczeK6y+2UAjdJ5trjQJj5f1AiVdLRWRc9A9/fFukfvJRgM0UXzxBIYMovm5ww== dependencies: - "@types/estree" "1.0.5" + "@types/estree" "1.0.6" optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.18.0" - "@rollup/rollup-android-arm64" "4.18.0" - "@rollup/rollup-darwin-arm64" "4.18.0" - "@rollup/rollup-darwin-x64" "4.18.0" - "@rollup/rollup-linux-arm-gnueabihf" "4.18.0" - "@rollup/rollup-linux-arm-musleabihf" "4.18.0" - "@rollup/rollup-linux-arm64-gnu" "4.18.0" - "@rollup/rollup-linux-arm64-musl" "4.18.0" - "@rollup/rollup-linux-powerpc64le-gnu" "4.18.0" - "@rollup/rollup-linux-riscv64-gnu" "4.18.0" - "@rollup/rollup-linux-s390x-gnu" "4.18.0" - "@rollup/rollup-linux-x64-gnu" "4.18.0" - "@rollup/rollup-linux-x64-musl" "4.18.0" - "@rollup/rollup-win32-arm64-msvc" "4.18.0" - "@rollup/rollup-win32-ia32-msvc" "4.18.0" - "@rollup/rollup-win32-x64-msvc" "4.18.0" + "@rollup/rollup-android-arm-eabi" "4.24.2" + "@rollup/rollup-android-arm64" "4.24.2" + "@rollup/rollup-darwin-arm64" "4.24.2" + "@rollup/rollup-darwin-x64" "4.24.2" + "@rollup/rollup-freebsd-arm64" "4.24.2" + "@rollup/rollup-freebsd-x64" "4.24.2" + "@rollup/rollup-linux-arm-gnueabihf" "4.24.2" + "@rollup/rollup-linux-arm-musleabihf" "4.24.2" + "@rollup/rollup-linux-arm64-gnu" "4.24.2" + "@rollup/rollup-linux-arm64-musl" "4.24.2" + "@rollup/rollup-linux-powerpc64le-gnu" "4.24.2" + "@rollup/rollup-linux-riscv64-gnu" "4.24.2" + "@rollup/rollup-linux-s390x-gnu" "4.24.2" + "@rollup/rollup-linux-x64-gnu" "4.24.2" + "@rollup/rollup-linux-x64-musl" "4.24.2" + "@rollup/rollup-win32-arm64-msvc" "4.24.2" + "@rollup/rollup-win32-ia32-msvc" "4.24.2" + "@rollup/rollup-win32-x64-msvc" "4.24.2" fsevents "~2.3.2" run-parallel@^1.1.9: @@ -910,10 +922,10 @@ use-sync-external-store@^1.2.0: resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz#c3b6390f3a30eba13200d2302dcdf1e7b57b2ef9" integrity sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw== -vite@^5.2.0: - version "5.2.11" - resolved "https://registry.yarnpkg.com/vite/-/vite-5.2.11.tgz#726ec05555431735853417c3c0bfb36003ca0cbd" - integrity sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ== +vite@^5.2.14: + version "5.2.14" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.2.14.tgz#fd5f60facf6b5f90ec7da6323c467a365d380c3d" + integrity sha512-TFQLuwWLPms+NBNlh0D9LZQ+HXW471COABxw/9TEUBrjuHMo9BrYBPrN/SYAwIuVL+rLerycxiLT41t4f5MZpA== dependencies: esbuild "^0.20.1" postcss "^8.4.38" diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index 0870127845c7..353f2b231e97 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -29,7 +29,7 @@ "@hapi/hapi": "^21.3.10", "@nestjs/common": "^10.3.7", "@nestjs/core": "^10.3.3", - "@nestjs/platform-express": "^10.3.3", + "@nestjs/platform-express": "^10.4.6", "@prisma/client": "5.9.1", "@sentry/aws-serverless": "8.35.0", "@sentry/node": "8.35.0", @@ -40,12 +40,12 @@ "@types/pg": "^8.6.5", "amqplib": "^0.10.4", "apollo-server": "^3.11.1", - "axios": "^1.6.7", + "axios": "^1.7.7", "connect": "^3.7.0", "cors": "^2.8.5", "cron": "^3.1.6", "dataloader": "2.2.2", - "express": "^4.17.3", + "express": "^4.21.1", "generic-pool": "^3.9.0", "graphql": "^16.3.0", "http-terminator": "^3.2.0", @@ -56,8 +56,8 @@ "mongodb-memory-server-global": "^7.6.3", "mongoose": "^5.13.22", "mysql": "^2.18.1", - "mysql2": "^3.7.1", - "nock": "^13.1.0", + "mysql2": "^3.11.3", + "nock": "^13.5.5", "node-cron": "^3.0.3", "node-schedule": "^2.1.1", "pg": "^8.7.3", diff --git a/dev-packages/node-integration-tests/suites/anr/test.ts b/dev-packages/node-integration-tests/suites/anr/test.ts index c3cb935532b1..78f89d7451c0 100644 --- a/dev-packages/node-integration-tests/suites/anr/test.ts +++ b/dev-packages/node-integration-tests/suites/anr/test.ts @@ -56,12 +56,12 @@ const ANR_EVENT_WITH_SCOPE = { user: { email: 'person@home.com', }, - breadcrumbs: [ + breadcrumbs: expect.arrayContaining([ { timestamp: expect.any(Number), message: 'important message!', }, - ], + ]), }; conditionalTest({ min: 16 })('should report ANR when event loop blocked', () => { diff --git a/dev-packages/node-integration-tests/suites/breadcrumbs/process-thread/app.mjs b/dev-packages/node-integration-tests/suites/breadcrumbs/process-thread/app.mjs new file mode 100644 index 000000000000..903470806ad9 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/breadcrumbs/process-thread/app.mjs @@ -0,0 +1,27 @@ +import { spawn } from 'child_process'; +import { join } from 'path'; +import { loggingTransport } from '@sentry-internal/node-integration-tests'; +import * as Sentry from '@sentry/node'; +import { Worker } from 'worker_threads'; + +const __dirname = new URL('.', import.meta.url).pathname; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + transport: loggingTransport, +}); + +await new Promise(resolve => { + const child = spawn('sleep', ['a']); + child.on('error', resolve); + child.on('exit', resolve); +}); + +await new Promise(resolve => { + const worker = new Worker(join(__dirname, 'worker.mjs')); + worker.on('error', resolve); + worker.on('exit', resolve); +}); + +throw new Error('This is a test error'); diff --git a/dev-packages/node-integration-tests/suites/breadcrumbs/process-thread/test.ts b/dev-packages/node-integration-tests/suites/breadcrumbs/process-thread/test.ts new file mode 100644 index 000000000000..f675ca4250dd --- /dev/null +++ b/dev-packages/node-integration-tests/suites/breadcrumbs/process-thread/test.ts @@ -0,0 +1,48 @@ +import type { Event } from '@sentry/types'; +import { conditionalTest } from '../../../utils'; +import { cleanupChildProcesses, createRunner } from '../../../utils/runner'; + +const EVENT = { + // and an exception that is our ANR + exception: { + values: [ + { + type: 'Error', + value: 'This is a test error', + }, + ], + }, + breadcrumbs: [ + { + timestamp: expect.any(Number), + category: 'child_process', + message: "Child process exited with code '1'", + level: 'warning', + data: { + spawnfile: 'sleep', + }, + }, + { + timestamp: expect.any(Number), + category: 'worker_thread', + message: "Worker thread errored with 'Worker error'", + level: 'error', + data: { + threadId: expect.any(Number), + }, + }, + ], +}; + +conditionalTest({ min: 20 })('should capture process and thread breadcrumbs', () => { + afterAll(() => { + cleanupChildProcesses(); + }); + + test('ESM', done => { + createRunner(__dirname, 'app.mjs') + .withMockSentryServer() + .expect({ event: EVENT as Event }) + .start(done); + }); +}); diff --git a/dev-packages/node-integration-tests/suites/breadcrumbs/process-thread/worker.mjs b/dev-packages/node-integration-tests/suites/breadcrumbs/process-thread/worker.mjs new file mode 100644 index 000000000000..049063bd26b4 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/breadcrumbs/process-thread/worker.mjs @@ -0,0 +1 @@ +throw new Error('Worker error'); diff --git a/dev-packages/node-integration-tests/suites/tracing/requests/fetch-breadcrumbs/scenario.ts b/dev-packages/node-integration-tests/suites/tracing/requests/fetch-breadcrumbs/scenario.ts index eff91b2cd3e4..8d704042a8ce 100644 --- a/dev-packages/node-integration-tests/suites/tracing/requests/fetch-breadcrumbs/scenario.ts +++ b/dev-packages/node-integration-tests/suites/tracing/requests/fetch-breadcrumbs/scenario.ts @@ -7,6 +7,7 @@ Sentry.init({ tracePropagationTargets: [/\/v0/, 'v1'], integrations: [], transport: loggingTransport, + tracesSampleRate: 0.0, // Ensure this gets a correct hint beforeBreadcrumb(breadcrumb, hint) { breadcrumb.data = breadcrumb.data || {}; diff --git a/dev-packages/node-integration-tests/suites/tracing/requests/http-no-tracing/scenario.ts b/dev-packages/node-integration-tests/suites/tracing/requests/http-no-tracing/scenario.ts index 1eb618d97dcc..3ae59e5ee6b7 100644 --- a/dev-packages/node-integration-tests/suites/tracing/requests/http-no-tracing/scenario.ts +++ b/dev-packages/node-integration-tests/suites/tracing/requests/http-no-tracing/scenario.ts @@ -7,11 +7,20 @@ Sentry.init({ tracePropagationTargets: [/\/v0/, 'v1'], integrations: [], transport: loggingTransport, + // Ensure this gets a correct hint + beforeBreadcrumb(breadcrumb, hint) { + breadcrumb.data = breadcrumb.data || {}; + const req = hint?.request as { path?: string }; + breadcrumb.data.ADDED_PATH = req?.path; + return breadcrumb; + }, }); import * as http from 'http'; async function run(): Promise { + Sentry.addBreadcrumb({ message: 'manual breadcrumb' }); + await makeHttpRequest(`${process.env.SERVER_URL}/api/v0`); await makeHttpGet(`${process.env.SERVER_URL}/api/v1`); await makeHttpRequest(`${process.env.SERVER_URL}/api/v2`); diff --git a/dev-packages/node-integration-tests/suites/tracing/requests/http-no-tracing/test.ts b/dev-packages/node-integration-tests/suites/tracing/requests/http-no-tracing/test.ts index e65278c3efd5..3ab1090806cb 100644 --- a/dev-packages/node-integration-tests/suites/tracing/requests/http-no-tracing/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/requests/http-no-tracing/test.ts @@ -48,7 +48,7 @@ test('outgoing http requests are correctly instrumented with tracing disabled', data: { 'http.method': 'GET', url: `${SERVER_URL}/api/v0`, - status_code: 404, + status_code: 200, ADDED_PATH: '/api/v0', }, timestamp: expect.any(Number), @@ -59,7 +59,7 @@ test('outgoing http requests are correctly instrumented with tracing disabled', data: { 'http.method': 'GET', url: `${SERVER_URL}/api/v1`, - status_code: 404, + status_code: 200, ADDED_PATH: '/api/v1', }, timestamp: expect.any(Number), @@ -70,7 +70,7 @@ test('outgoing http requests are correctly instrumented with tracing disabled', data: { 'http.method': 'GET', url: `${SERVER_URL}/api/v2`, - status_code: 404, + status_code: 200, ADDED_PATH: '/api/v2', }, timestamp: expect.any(Number), @@ -81,7 +81,7 @@ test('outgoing http requests are correctly instrumented with tracing disabled', data: { 'http.method': 'GET', url: `${SERVER_URL}/api/v3`, - status_code: 404, + status_code: 200, ADDED_PATH: '/api/v3', }, timestamp: expect.any(Number), diff --git a/dev-packages/node-integration-tests/utils/server.ts b/dev-packages/node-integration-tests/utils/server.ts index 71a7adf9798f..5f9afeeb556d 100644 --- a/dev-packages/node-integration-tests/utils/server.ts +++ b/dev-packages/node-integration-tests/utils/server.ts @@ -70,9 +70,9 @@ export function createTestServer(done: (error?: unknown) => void) { const address = server.address() as AddressInfo; resolve([ `http://localhost:${address.port}`, - () => { + (error?: unknown) => { server.close(); - done(); + done(error); }, ]); }); diff --git a/dev-packages/overhead-metrics/.eslintrc.cjs b/dev-packages/overhead-metrics/.eslintrc.cjs deleted file mode 100644 index 3eed32128e5c..000000000000 --- a/dev-packages/overhead-metrics/.eslintrc.cjs +++ /dev/null @@ -1,17 +0,0 @@ -module.exports = { - extends: ['../../.eslintrc.js'], - ignorePatterns: ['test-apps'], - overrides: [ - { - files: ['*.ts'], - rules: { - 'no-console': 'off', - '@typescript-eslint/no-non-null-assertion': 'off', - '@sentry-internal/sdk/no-optional-chaining': 'off', - '@sentry-internal/sdk/no-nullish-coalescing': 'off', - '@sentry-internal/sdk/no-class-field-initializers': 'off', - 'jsdoc/require-jsdoc': 'off', - }, - }, - ], -}; diff --git a/dev-packages/overhead-metrics/.gitignore b/dev-packages/overhead-metrics/.gitignore deleted file mode 100644 index 505d701f0e12..000000000000 --- a/dev-packages/overhead-metrics/.gitignore +++ /dev/null @@ -1 +0,0 @@ -out diff --git a/dev-packages/overhead-metrics/README.md b/dev-packages/overhead-metrics/README.md deleted file mode 100644 index 51e7d2587ac0..000000000000 --- a/dev-packages/overhead-metrics/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# Overhead performance metrics - -Evaluates Sentry & Replay impact on website performance by running a web app in Chromium via Playwright and collecting -various metrics. - -The general idea is to run a web app without Sentry, and then run the same app again with Sentry and another one with -Sentry+Replay included. For the three scenarios, we collect some metrics (CPU, memory, vitals) and later compare them -and post as a comment in a PR. Changes in the metrics, compared to previous runs from the main branch, should be -evaluated on case-by-case basis when preparing and reviewing the PR. - -## Resources - -- https://github.com/addyosmani/puppeteer-webperf diff --git a/dev-packages/overhead-metrics/configs/README.md b/dev-packages/overhead-metrics/configs/README.md deleted file mode 100644 index ceb96835f975..000000000000 --- a/dev-packages/overhead-metrics/configs/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Replay metrics configuration & entrypoints (scripts) - -- [dev](dev) contains scripts launched during local development -- [ci](ci) contains scripts launched in CI diff --git a/dev-packages/overhead-metrics/configs/ci/collect.ts b/dev-packages/overhead-metrics/configs/ci/collect.ts deleted file mode 100644 index 88a510fabdf0..000000000000 --- a/dev-packages/overhead-metrics/configs/ci/collect.ts +++ /dev/null @@ -1,62 +0,0 @@ -import type { Metrics } from '../../src/collector.js'; -import { MetricsCollector } from '../../src/collector.js'; -import type { NumberProvider } from '../../src/results/metrics-stats.js'; -import { MetricsStats } from '../../src/results/metrics-stats.js'; -import { BookingAppScenario } from '../../src/scenarios.js'; -import { printStats } from '../../src/util/console.js'; -import { latestResultFile } from './env.js'; - -function checkStdDev(results: Metrics[], name: string, provider: NumberProvider, max: number): boolean { - const value = MetricsStats.stddev(results, provider); - if (value == undefined) { - console.warn(`✗ | Discarding results because StandardDeviation(${name}) is undefined`); - return false; - } else if (value > max) { - console.warn( - `✗ | Discarding results because StandardDeviation(${name}) is larger than ${max}. Actual value: ${value}`, - ); - return false; - } else { - console.log(`✓ | StandardDeviation(${name}) is ${value} (<= ${max})`); - } - return true; -} - -const collector = new MetricsCollector({ headless: true, cpuThrottling: 2 }); -const result = await collector.execute({ - name: 'jank', - scenarios: [ - new BookingAppScenario('index.html', 100), - new BookingAppScenario('with-sentry.html', 100), - new BookingAppScenario('with-replay.html', 100), - ], - runs: 10, - tries: 10, - async shouldAccept(results: Metrics[]): Promise { - await printStats(results); - - if ( - !checkStdDev(results, 'lcp', MetricsStats.lcp, 50) || - !checkStdDev(results, 'cls', MetricsStats.cls, 0.1) || - !checkStdDev(results, 'cpu', MetricsStats.cpu, 1) || - !checkStdDev(results, 'memory-mean', MetricsStats.memoryMean, 1000 * 1024) || - !checkStdDev(results, 'memory-max', MetricsStats.memoryMax, 1000 * 1024) - ) { - return false; - } - - const cpuUsage = MetricsStats.mean(results, MetricsStats.cpu)!; - if (cpuUsage > 0.85) { - // Note: complexity on the "JankTest" is defined by the `minimum = ...,` setting in app.js - specifying the number of animated elements. - console.warn( - `✗ | Discarding results because CPU usage is too high and may be inaccurate: ${(cpuUsage * 100).toFixed(2)} %.`, - 'Consider simplifying the scenario or changing the CPU throttling factor.', - ); - return false; - } - - return true; - }, -}); - -result.writeToFile(latestResultFile); diff --git a/dev-packages/overhead-metrics/configs/ci/env.ts b/dev-packages/overhead-metrics/configs/ci/env.ts deleted file mode 100644 index 511941e433b7..000000000000 --- a/dev-packages/overhead-metrics/configs/ci/env.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const previousResultsDir = 'out/previous-results'; -export const baselineResultsDir = 'out/baseline-results'; -export const latestResultFile = 'out/latest-result.json'; -export const artifactName = 'replay-sdk-metrics'; diff --git a/dev-packages/overhead-metrics/configs/ci/process.ts b/dev-packages/overhead-metrics/configs/ci/process.ts deleted file mode 100644 index 31e7842844ef..000000000000 --- a/dev-packages/overhead-metrics/configs/ci/process.ts +++ /dev/null @@ -1,54 +0,0 @@ -import path from 'path'; -import fs from 'fs-extra'; - -import { ResultsAnalyzer } from '../../src/results/analyzer.js'; -import { PrCommentBuilder } from '../../src/results/pr-comment.js'; -import { Result } from '../../src/results/result.js'; -import { ResultsSet } from '../../src/results/results-set.js'; -import { Git } from '../../src/util/git.js'; -import { GitHub } from '../../src/util/github.js'; -import { artifactName, baselineResultsDir, latestResultFile, previousResultsDir } from './env.js'; - -const latestResult = Result.readFromFile(latestResultFile); -const branch = await Git.branch; -const baseBranch = await Git.baseBranch; -const branchIsBase = await Git.branchIsBase; - -await GitHub.downloadPreviousArtifact(baseBranch, baselineResultsDir, artifactName); - -if (branchIsBase) { - await GitHub.downloadPreviousArtifact(branch, previousResultsDir, artifactName); -} else { - // Copy over same results - await fs.copy(baselineResultsDir, previousResultsDir); -} - -GitHub.writeOutput('artifactName', artifactName); -GitHub.writeOutput('artifactPath', path.resolve(previousResultsDir)); - -const previousResults = new ResultsSet(previousResultsDir); - -const prComment = new PrCommentBuilder(); -if (baseBranch != branch) { - const baseResults = new ResultsSet(baselineResultsDir); - await prComment.addCurrentResult(await ResultsAnalyzer.analyze(latestResult, baseResults), 'Baseline'); - await prComment.addAdditionalResultsSet( - `Baseline results on branch: ${baseBranch}`, - // We skip the first one here because it's already included as `Baseline` column above in addCurrentResult(). - baseResults - .items() - .slice(1, 10), - ); -} else { - await prComment.addCurrentResult(await ResultsAnalyzer.analyze(latestResult, previousResults), 'Previous'); -} - -await prComment.addAdditionalResultsSet( - `Previous results on branch: ${branch}`, - previousResults.items().slice(0, 10), -); - -await GitHub.addOrUpdateComment(prComment); - -// Copy the latest test run results to the archived result dir. -await previousResults.add(latestResultFile, true); diff --git a/dev-packages/overhead-metrics/configs/dev/collect.ts b/dev-packages/overhead-metrics/configs/dev/collect.ts deleted file mode 100644 index 4b2ffbc5480a..000000000000 --- a/dev-packages/overhead-metrics/configs/dev/collect.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { Metrics } from '../../src/collector.js'; -import { MetricsCollector } from '../../src/collector.js'; -import { MetricsStats } from '../../src/results/metrics-stats.js'; -import { BookingAppScenario } from '../../src/scenarios.js'; -import { printStats } from '../../src/util/console.js'; -import { latestResultFile } from './env.js'; - -const collector = new MetricsCollector(); -const result = await collector.execute({ - name: 'dummy', - scenarios: [ - new BookingAppScenario('index.html', 50), - new BookingAppScenario('with-sentry.html', 50), - new BookingAppScenario('with-replay.html', 50), - new BookingAppScenario('index.html', 500), - new BookingAppScenario('with-sentry.html', 500), - new BookingAppScenario('with-replay.html', 500), - ], - runs: 1, - tries: 1, - async shouldAccept(results: Metrics[]): Promise { - printStats(results); - - const cpuUsage = MetricsStats.mean(results, MetricsStats.cpu)!; - if (cpuUsage > 0.9) { - console.error( - `CPU usage too high to be accurate: ${(cpuUsage * 100).toFixed(2)} %.`, - 'Consider simplifying the scenario or changing the CPU throttling factor.', - ); - return false; - } - return true; - }, -}); - -result.writeToFile(latestResultFile); diff --git a/dev-packages/overhead-metrics/configs/dev/env.ts b/dev-packages/overhead-metrics/configs/dev/env.ts deleted file mode 100644 index c2168763ea6e..000000000000 --- a/dev-packages/overhead-metrics/configs/dev/env.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const outDir = 'out/results-dev'; -export const latestResultFile = 'out/latest-result.json'; diff --git a/dev-packages/overhead-metrics/configs/dev/process.ts b/dev-packages/overhead-metrics/configs/dev/process.ts deleted file mode 100644 index 096244b5c750..000000000000 --- a/dev-packages/overhead-metrics/configs/dev/process.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ResultsAnalyzer } from '../../src/results/analyzer.js'; -import { Result } from '../../src/results/result.js'; -import { ResultsSet } from '../../src/results/results-set.js'; -import { printAnalysis } from '../../src/util/console.js'; -import { latestResultFile, outDir } from './env.js'; - -const resultsSet = new ResultsSet(outDir); -const latestResult = Result.readFromFile(latestResultFile); - -const analysis = await ResultsAnalyzer.analyze(latestResult, resultsSet); -printAnalysis(analysis); - -await resultsSet.add(latestResultFile, true); diff --git a/dev-packages/overhead-metrics/package.json b/dev-packages/overhead-metrics/package.json deleted file mode 100644 index 878b29487f54..000000000000 --- a/dev-packages/overhead-metrics/package.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "private": true, - "version": "8.35.0", - "name": "@sentry-internal/overhead-metrics", - "main": "index.js", - "author": "Sentry", - "license": "MIT", - "type": "module", - "scripts": { - "build": "tsc", - "dev:collect": "ts-node-esm ./configs/dev/collect.ts", - "dev:process": "ts-node-esm ./configs/dev/process.ts", - "dev:run:replay": "npx chrome ./test-apps/booking-app/with-replay.html", - "ci:collect": "ts-node-esm ./configs/ci/collect.ts", - "ci:process": "ts-node-esm ./configs/ci/process.ts", - "fix": "eslint . --format stylish --fix", - "lint": "eslint . --format stylish" - }, - "dependencies": { - "@octokit/rest": "^19.0.5", - "@types/node": "^18.11.17", - "axios": "^1.6.7", - "extract-zip": "^2.0.1", - "filesize": "^10.0.6", - "fs-extra": "^11.1.0", - "p-timeout": "^6.0.0", - "playwright": "^1.44.1", - "playwright-core": "^1.44.1", - "simple-git": "^3.16.0", - "simple-statistics": "^7.8.0", - "typescript": "4.9.5" - }, - "devDependencies": { - "ts-node": "^10.9.1" - } -} diff --git a/dev-packages/overhead-metrics/src/collector.ts b/dev-packages/overhead-metrics/src/collector.ts deleted file mode 100644 index 0cdb80130f80..000000000000 --- a/dev-packages/overhead-metrics/src/collector.ts +++ /dev/null @@ -1,204 +0,0 @@ -import pTimeout from 'p-timeout'; -import * as playwright from 'playwright'; - -import type { CpuUsageSerialized } from './perf/cpu.js'; -import { CpuUsage, CpuUsageSampler } from './perf/cpu.js'; -import type { JsHeapUsageSerialized } from './perf/memory.js'; -import { JsHeapUsage, JsHeapUsageSampler } from './perf/memory.js'; -import type { NetworkUsageSerialized } from './perf/network.js'; -import { NetworkUsage, NetworkUsageCollector } from './perf/network.js'; -import { PerfMetricsSampler } from './perf/sampler.js'; -import { Result } from './results/result.js'; -import type { Scenario, TestCase } from './scenarios.js'; -import { consoleGroup } from './util/console.js'; -import { WebVitals, WebVitalsCollector } from './vitals/index.js'; - -const networkConditions = 'Fast 3G'; - -// Same as puppeteer-core PredefinedNetworkConditions -const PredefinedNetworkConditions = Object.freeze({ - 'Slow 3G': { - download: ((500 * 1000) / 8) * 0.8, - upload: ((500 * 1000) / 8) * 0.8, - latency: 400 * 5, - connectionType: 'cellular3g', - }, - 'Fast 3G': { - download: ((1.6 * 1000 * 1000) / 8) * 0.9, - upload: ((750 * 1000) / 8) * 0.9, - latency: 150 * 3.75, - connectionType: 'cellular3g', - }, -}); - -export class Metrics { - public constructor( - public readonly vitals: WebVitals, - public readonly cpu: CpuUsage, - public readonly memory: JsHeapUsage, - public readonly network: NetworkUsage, - ) {} - - /** - * - */ - public static fromJSON( - data: Partial<{ - vitals: Partial; - cpu: CpuUsageSerialized; - memory: JsHeapUsageSerialized; - network: NetworkUsageSerialized; - }>, - ): Metrics { - return new Metrics( - WebVitals.fromJSON(data.vitals || {}), - CpuUsage.fromJSON(data.cpu || {}), - JsHeapUsage.fromJSON(data.memory || {}), - NetworkUsage.fromJSON(data.network || {}), - ); - } -} - -export interface MetricsCollectorOptions { - headless: boolean; - cpuThrottling: number; -} - -export class MetricsCollector { - private _options: MetricsCollectorOptions; - - public constructor(options?: Partial) { - this._options = { - headless: false, - cpuThrottling: 4, - ...options, - }; - } - - /** - * - */ - public async execute(testCase: TestCase): Promise { - console.log(`Executing test case ${testCase.name}`); - return consoleGroup(async () => { - const scenarioResults: Metrics[][] = []; - for (let s = 0; s < testCase.scenarios.length; s++) { - scenarioResults.push(await this._collect(testCase, s.toString(), testCase.scenarios[s])); - } - return new Result(testCase.name, this._options.cpuThrottling, networkConditions, scenarioResults); - }); - } - - /** - * - */ - private async _collect(testCase: TestCase, name: string, scenario: Scenario): Promise { - const label = `Scenario ${name} data collection (total ${testCase.runs} runs)`; - for (let try_ = 1; try_ <= testCase.tries; try_++) { - console.time(label); - const results: Metrics[] = []; - for (let run = 1; run <= testCase.runs; run++) { - const innerLabel = `Scenario ${name} data collection, run ${run}/${testCase.runs}`; - console.time(innerLabel); - try { - results.push(await this._run(scenario)); - } catch (e) { - console.warn(`${innerLabel} failed with ${e}`); - break; - } finally { - console.timeEnd(innerLabel); - } - } - console.timeEnd(label); - if (results.length == testCase.runs && (await testCase.shouldAccept(results))) { - console.log(`Test case ${testCase.name}, scenario ${name} passed on try ${try_}/${testCase.tries}`); - return results; - } else if (try_ != testCase.tries) { - console.log(`Test case ${testCase.name} failed on try ${try_}/${testCase.tries}, retrying`); - } else { - throw `Test case ${testCase.name}, scenario ${name} failed after ${testCase.tries} tries.`; - } - } - // Unreachable code, if configured properly: - console.assert(testCase.tries >= 1); - return []; - } - - /** - * - */ - private async _run(scenario: Scenario): Promise { - const disposeCallbacks: (() => Promise)[] = []; - try { - return await pTimeout( - (async () => { - const browser = await playwright.chromium.launch({ - headless: this._options.headless, - }); - disposeCallbacks.push(() => browser.close()); - const page = await browser.newPage(); - disposeCallbacks.push(() => page.close()); - - const errorLogs: Array = []; - await page.on('console', message => { - if (message.type() === 'error') errorLogs.push(message.text()); - }); - await page.on('crash', _ => { - errorLogs.push('Page crashed'); - }); - await page.on('pageerror', error => { - errorLogs.push(`${error.name}: ${error.message}`); - }); - - const cdp = await page.context().newCDPSession(page); - - // Simulate throttling. - await cdp.send('Network.emulateNetworkConditions', { - offline: false, - latency: PredefinedNetworkConditions[networkConditions].latency, - uploadThroughput: PredefinedNetworkConditions[networkConditions].upload, - downloadThroughput: PredefinedNetworkConditions[networkConditions].download, - }); - await cdp.send('Emulation.setCPUThrottlingRate', { rate: this._options.cpuThrottling }); - - // Collect CPU and memory info 10 times per second. - const perfSampler = await PerfMetricsSampler.create(cdp, 100); - disposeCallbacks.push(async () => perfSampler.stop()); - const cpuSampler = new CpuUsageSampler(perfSampler); - const memSampler = new JsHeapUsageSampler(perfSampler); - - const networkCollector = await NetworkUsageCollector.create(page); - const vitalsCollector = await WebVitalsCollector.create(page); - - await scenario.run(browser, page); - - // NOTE: FID needs some interaction to actually show a value - const vitals = await vitalsCollector.collect(); - - if (errorLogs.length > 0) { - throw `Error logs in browser console:\n\t\t${errorLogs.join('\n\t\t')}`; - } - - return new Metrics(vitals, cpuSampler.getData(), memSampler.getData(), networkCollector.getData()); - })(), - { milliseconds: 60 * 1000 }, - ); - } finally { - console.log('Disposing of browser and resources'); - disposeCallbacks.reverse(); - const errors = []; - for (const cb of disposeCallbacks) { - try { - await cb(); - } catch (e) { - errors.push(e instanceof Error ? `${e.name}: ${e.message}` : `${e}`); - } - } - if (errors.length > 0) { - console.warn(`All disposose callbacks have finished. Errors: ${errors}`); - } else { - console.warn('All disposose callbacks have finished.'); - } - } - } -} diff --git a/dev-packages/overhead-metrics/src/perf/cpu.ts b/dev-packages/overhead-metrics/src/perf/cpu.ts deleted file mode 100644 index 2ec17c1866fa..000000000000 --- a/dev-packages/overhead-metrics/src/perf/cpu.ts +++ /dev/null @@ -1,52 +0,0 @@ -import type { JsonObject } from '../util/json.js'; -import type { PerfMetrics, PerfMetricsSampler } from './sampler.js'; -import { TimeBasedMap } from './sampler.js'; - -export { CpuUsageSampler, CpuUsage }; - -export type CpuUsageSerialized = Partial<{ snapshots: JsonObject; average: number }>; - -class CpuUsage { - public constructor(public snapshots: TimeBasedMap, public average: number) {} - - public static fromJSON(data: CpuUsageSerialized): CpuUsage { - return new CpuUsage(TimeBasedMap.fromJSON(data.snapshots || {}), data.average as number); - } -} - -class MetricsDataPoint { - public constructor(public timestamp: number, public activeTime: number) {} -} - -class CpuUsageSampler { - private _snapshots: TimeBasedMap = new TimeBasedMap(); - private _average: number = 0; - private _initial?: MetricsDataPoint = undefined; - private _startTime!: number; - private _lastTimestamp!: number; - private _cumulativeActiveTime!: number; - - public constructor(sampler: PerfMetricsSampler) { - sampler.subscribe(this._collect.bind(this)); - } - - public getData(): CpuUsage { - return new CpuUsage(this._snapshots, this._average); - } - - private async _collect(metrics: PerfMetrics): Promise { - const data = new MetricsDataPoint(metrics.Timestamp, metrics.Duration); - if (this._initial == undefined) { - this._initial = data; - this._startTime = data.timestamp; - } else { - const frameDuration = data.timestamp - this._lastTimestamp; - const usage = frameDuration == 0 ? 0 : (data.activeTime - this._cumulativeActiveTime) / frameDuration; - - this._snapshots.set(data.timestamp, usage); - this._average = data.activeTime / (data.timestamp - this._startTime); - } - this._lastTimestamp = data.timestamp; - this._cumulativeActiveTime = data.activeTime; - } -} diff --git a/dev-packages/overhead-metrics/src/perf/memory.ts b/dev-packages/overhead-metrics/src/perf/memory.ts deleted file mode 100644 index 561d5d7ebd99..000000000000 --- a/dev-packages/overhead-metrics/src/perf/memory.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { JsonObject } from '../util/json.js'; -import type { PerfMetrics, PerfMetricsSampler } from './sampler.js'; -import { TimeBasedMap } from './sampler.js'; - -export { JsHeapUsageSampler, JsHeapUsage }; - -export type JsHeapUsageSerialized = Partial<{ snapshots: JsonObject }>; - -class JsHeapUsage { - public constructor(public snapshots: TimeBasedMap) {} - - public static fromJSON(data: JsHeapUsageSerialized): JsHeapUsage { - return new JsHeapUsage(TimeBasedMap.fromJSON(data.snapshots || {})); - } -} - -class JsHeapUsageSampler { - private _snapshots: TimeBasedMap = new TimeBasedMap(); - - public constructor(sampler: PerfMetricsSampler) { - sampler.subscribe(this._collect.bind(this)); - } - - public getData(): JsHeapUsage { - return new JsHeapUsage(this._snapshots); - } - - private async _collect(metrics: PerfMetrics): Promise { - this._snapshots.set(metrics.Timestamp, metrics.JSHeapUsedSize!); - } -} diff --git a/dev-packages/overhead-metrics/src/perf/network.ts b/dev-packages/overhead-metrics/src/perf/network.ts deleted file mode 100644 index 03b76d2fcc4d..000000000000 --- a/dev-packages/overhead-metrics/src/perf/network.ts +++ /dev/null @@ -1,88 +0,0 @@ -import type * as playwright from 'playwright'; - -export class NetworkEvent { - public constructor( - public url: string | undefined, - public requestSize: number | undefined, - public responseSize: number | undefined, - public requestTimeNs: bigint | undefined, - public responseTimeNs: bigint | undefined, - ) {} - - /** - * - */ - public static fromJSON(data: Partial): NetworkEvent { - return new NetworkEvent( - data.url as string, - data.requestSize as number, - data.responseSize as number, - data.requestTimeNs == undefined ? undefined : BigInt(data.requestTimeNs), - data.responseTimeNs == undefined ? undefined : BigInt(data.responseTimeNs), - ); - } -} - -export type NetworkUsageSerialized = Partial<{ events: Array }>; - -export class NetworkUsage { - public constructor(public events: Array) {} - - /** - * - */ - public static fromJSON(data: NetworkUsageSerialized): NetworkUsage { - return new NetworkUsage(data.events?.map(e => NetworkEvent.fromJSON(e)) || []); - } -} - -export class NetworkUsageCollector { - private _events = new Array(); - - /** - * - */ - public static async create(page: playwright.Page): Promise { - const self = new NetworkUsageCollector(); - await page.route(_ => true, self._captureRequest.bind(self)); - return self; - } - - /** - * - */ - public getData(): NetworkUsage { - return new NetworkUsage(this._events); - } - - /** - * - */ - private async _captureRequest(route: playwright.Route, request: playwright.Request): Promise { - const url = request.url(); - try { - const event = new NetworkEvent( - url, - request.postDataBuffer()?.length, - undefined, - process.hrtime.bigint(), - undefined, - ); - this._events.push(event); - // Note: playwright would error out on file:/// requests. They are used to access local test app resources. - if (url.startsWith('file:///')) { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - route.continue(); - } else { - const response = await route.fetch(); - const body = await response.body(); - // eslint-disable-next-line @typescript-eslint/no-floating-promises - route.fulfill({ response, body }); - event.responseTimeNs = process.hrtime.bigint(); - event.responseSize = body.length; - } - } catch (e) { - console.log(`Error when capturing request: ${request.method()} ${url} - ${e}`); - } - } -} diff --git a/dev-packages/overhead-metrics/src/perf/sampler.ts b/dev-packages/overhead-metrics/src/perf/sampler.ts deleted file mode 100644 index 1c5c631f231a..000000000000 --- a/dev-packages/overhead-metrics/src/perf/sampler.ts +++ /dev/null @@ -1,118 +0,0 @@ -import type * as playwright from 'playwright'; -import type { Protocol } from 'playwright-core/types/protocol'; - -import type { JsonObject } from '../util/json'; - -export type PerfMetricsConsumer = (metrics: PerfMetrics) => Promise; -export type TimestampSeconds = number; - -export class TimeBasedMap extends Map { - /** - * - */ - public static fromJSON(entries: JsonObject): TimeBasedMap { - const result = new TimeBasedMap(); - // eslint-disable-next-line guard-for-in - for (const key in entries) { - result.set(parseFloat(key), entries[key]); - } - return result; - } - - /** - * - */ - public toJSON(): JsonObject { - return Object.fromEntries(this.entries()); - } -} - -export class PerfMetrics { - public constructor(private _metrics: Protocol.Performance.Metric[]) {} - - /** - * - */ - public get Timestamp(): number { - return this._find('Timestamp'); - } - - /** - * - */ - public get Duration(): number { - return this._find('TaskDuration'); - } - - /** - * - */ - public get JSHeapUsedSize(): number { - return this._find('JSHeapUsedSize'); - } - - /** - * - */ - private _find(name: string): number { - return this._metrics.find(metric => metric.name == name)!.value; - } -} - -export class PerfMetricsSampler { - private _consumers: PerfMetricsConsumer[] = []; - private _timer!: NodeJS.Timer; - private _errorPrinted: boolean = false; - - private constructor(private _cdp: playwright.CDPSession) {} - - /** - * - */ - public static async create(cdp: playwright.CDPSession, interval: number): Promise { - const self = new PerfMetricsSampler(cdp); - await cdp.send('Performance.enable', { timeDomain: 'timeTicks' }); - - // collect first sample immediately - self._collectSample(); - - // and set up automatic collection in the given interval - self._timer = setInterval(self._collectSample.bind(self), interval); - - return self; - } - - /** - * - */ - public subscribe(consumer: PerfMetricsConsumer): void { - this._consumers.push(consumer); - } - - /** - * - */ - public stop(): void { - clearInterval(this._timer); - } - - /** - * - */ - private _collectSample(): void { - this._cdp.send('Performance.getMetrics').then( - response => { - const metrics = new PerfMetrics(response.metrics); - this._consumers.forEach(cb => cb(metrics).catch(console.error)); - }, - e => { - // This happens if the browser closed unexpectedly. No reason to try again. - if (!this._errorPrinted) { - this._errorPrinted = true; - console.log(e); - this.stop(); - } - }, - ); - } -} diff --git a/dev-packages/overhead-metrics/src/results/analyzer.ts b/dev-packages/overhead-metrics/src/results/analyzer.ts deleted file mode 100644 index f27356f4d507..000000000000 --- a/dev-packages/overhead-metrics/src/results/analyzer.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { filesize } from 'filesize'; - -import type { GitHash } from '../util/git.js'; -import { JsonStringify } from '../util/json.js'; -import type { AnalyticsFunction, NumberProvider } from './metrics-stats.js'; -import { MetricsStats } from './metrics-stats.js'; -import type { Result } from './result.js'; -import type { ResultsSet } from './results-set.js'; - -// Compares latest result to previous/baseline results and produces the needed info. - -export class ResultsAnalyzer { - private constructor(private _result: Result) {} - - /** - * - */ - public static async analyze(currentResult: Result, baselineResults?: ResultsSet): Promise { - const items = new ResultsAnalyzer(currentResult)._collect(); - - const baseline = baselineResults?.find( - other => - other.cpuThrottling == currentResult.cpuThrottling && - other.name == currentResult.name && - other.networkConditions == currentResult.networkConditions && - JsonStringify(other) != JsonStringify(currentResult), - ); - - let otherHash: GitHash | undefined; - if (baseline != undefined) { - otherHash = baseline[0]; - const baseItems = new ResultsAnalyzer(baseline[1])._collect(); - // update items with baseline results - for (const base of baseItems) { - for (const item of items) { - if (item.metric == base.metric) { - item.others = base.values; - } - } - } - } - - return { - items: items, - otherHash: otherHash, - }; - } - - /** - * - */ - private _collect(): AnalyzerItem[] { - const items = new Array(); - - const scenarioResults = this._result.scenarioResults; - - const pushIfDefined = function ( - metric: AnalyzerItemMetric, - unit: AnalyzerItemUnit, - source: NumberProvider, - fn: AnalyticsFunction, - ): void { - const values = scenarioResults.map(items => fn(items, source)); - // only push if at least one value is defined - if (values.findIndex(v => v != undefined) >= 0) { - items.push({ - metric: metric, - values: new AnalyzerItemNumberValues(unit, values), - }); - } - }; - - pushIfDefined(AnalyzerItemMetric.lcp, AnalyzerItemUnit.ms, MetricsStats.lcp, MetricsStats.mean); - pushIfDefined(AnalyzerItemMetric.cls, AnalyzerItemUnit.ms, MetricsStats.cls, MetricsStats.mean); - pushIfDefined(AnalyzerItemMetric.cpu, AnalyzerItemUnit.ratio, MetricsStats.cpu, MetricsStats.mean); - pushIfDefined(AnalyzerItemMetric.memoryAvg, AnalyzerItemUnit.bytes, MetricsStats.memoryMean, MetricsStats.mean); - pushIfDefined(AnalyzerItemMetric.memoryMax, AnalyzerItemUnit.bytes, MetricsStats.memoryMax, MetricsStats.max); - pushIfDefined(AnalyzerItemMetric.netTx, AnalyzerItemUnit.bytes, MetricsStats.netTx, MetricsStats.mean); - pushIfDefined(AnalyzerItemMetric.netRx, AnalyzerItemUnit.bytes, MetricsStats.netRx, MetricsStats.mean); - pushIfDefined(AnalyzerItemMetric.netCount, AnalyzerItemUnit.integer, MetricsStats.netCount, MetricsStats.mean); - pushIfDefined(AnalyzerItemMetric.netTime, AnalyzerItemUnit.ms, MetricsStats.netTime, MetricsStats.mean); - - return items; - } -} - -export enum AnalyzerItemUnit { - ms, - ratio, // 1.0 == 100 % - bytes, - integer, -} - -export interface AnalyzerItemValues { - value(index: number): string; - diff(aIndex: number, bIndex: number): string; - percent(aIndex: number, bIndex: number): string; -} - -const AnalyzerItemValueNotAvailable = 'n/a'; - -class AnalyzerItemNumberValues implements AnalyzerItemValues { - public constructor(private _unit: AnalyzerItemUnit, private _values: (number | undefined)[]) {} - - public value(index: number): string { - if (!this._has(index)) return AnalyzerItemValueNotAvailable; - return this._withUnit(this._get(index)); - } - - public diff(aIndex: number, bIndex: number): string { - if (!this._has(aIndex) || !this._has(bIndex)) return AnalyzerItemValueNotAvailable; - const diff = this._get(bIndex) - this._get(aIndex); - const str = this._withUnit(diff, true); - return diff > 0 ? `+${str}` : str; - } - - public percent(aIndex: number, bIndex: number): string { - if (!this._has(aIndex) || !this._has(bIndex) || this._get(aIndex) == 0.0) return AnalyzerItemValueNotAvailable; - const percent = (this._get(bIndex) / this._get(aIndex)) * 100 - 100; - const str = `${percent.toFixed(2)} %`; - return percent > 0 ? `+${str}` : str; - } - - private _has(index: number): boolean { - return index >= 0 && index < this._values.length && this._values[index] != undefined; - } - - private _get(index: number): number { - return this._values[index]!; - } - - private _withUnit(value: number, isDiff: boolean = false): string { - switch (this._unit) { - case AnalyzerItemUnit.bytes: - return filesize(value) as string; - case AnalyzerItemUnit.ratio: - return `${(value * 100).toFixed(2)} ${isDiff ? 'pp' : '%'}`; - case AnalyzerItemUnit.integer: - return `${value}`; - default: - return `${value.toFixed(2)} ${AnalyzerItemUnit[this._unit]}`; - } - } -} - -export enum AnalyzerItemMetric { - lcp, - cls, - cpu, - memoryAvg, - memoryMax, - netTx, - netRx, - netCount, - netTime, -} - -export interface AnalyzerItem { - metric: AnalyzerItemMetric; - - // Current (latest) result. - values: AnalyzerItemValues; - - // Previous or baseline results, depending on the context. - others?: AnalyzerItemValues; -} - -export interface Analysis { - items: AnalyzerItem[]; - - // Commit hash that the the previous or baseline (depending on the context) result was collected for. - otherHash?: GitHash; -} diff --git a/dev-packages/overhead-metrics/src/results/metrics-stats.ts b/dev-packages/overhead-metrics/src/results/metrics-stats.ts deleted file mode 100644 index 2ccab6632905..000000000000 --- a/dev-packages/overhead-metrics/src/results/metrics-stats.ts +++ /dev/null @@ -1,64 +0,0 @@ -import * as ss from 'simple-statistics'; - -import type { Metrics } from '../collector'; - -export type NumberProvider = (metrics: Metrics) => number | undefined; -export type AnalyticsFunction = (items: Metrics[], dataProvider: NumberProvider) => number | undefined; - -export class MetricsStats { - public static lcp: NumberProvider = metrics => metrics.vitals.lcp; - public static cls: NumberProvider = metrics => metrics.vitals.cls; - public static cpu: NumberProvider = metrics => metrics.cpu.average; - public static memoryMean: NumberProvider = metrics => ss.mean(Array.from(metrics.memory.snapshots.values())); - public static memoryMax: NumberProvider = metrics => ss.max(Array.from(metrics.memory.snapshots.values())); - public static netTx: NumberProvider = metrics => ss.sum(metrics.network.events.map(e => e.requestSize || 0)); - public static netRx: NumberProvider = metrics => ss.sum(metrics.network.events.map(e => e.responseSize || 0)); - public static netCount: NumberProvider = metrics => - ss.sum(metrics.network.events.map(e => (e.requestTimeNs && e.responseTimeNs ? 1 : 0))); - public static netTime: NumberProvider = metrics => - ss.sum( - metrics.network.events.map(e => - e.requestTimeNs && e.responseTimeNs ? Number(e.responseTimeNs - e.requestTimeNs) / 1e6 : 0, - ), - ); - - public static mean: AnalyticsFunction = (items: Metrics[], dataProvider: NumberProvider) => { - const numbers = MetricsStats._filteredValues(MetricsStats._collect(items, dataProvider)); - return numbers.length > 0 ? ss.mean(numbers) : undefined; - }; - - public static max: AnalyticsFunction = (items: Metrics[], dataProvider: NumberProvider) => { - const numbers = MetricsStats._filteredValues(MetricsStats._collect(items, dataProvider)); - return numbers.length > 0 ? ss.max(numbers) : undefined; - }; - - public static stddev: AnalyticsFunction = (items: Metrics[], dataProvider: NumberProvider) => { - const numbers = MetricsStats._filteredValues(MetricsStats._collect(items, dataProvider)); - return numbers.length > 0 ? ss.standardDeviation(numbers) : undefined; - }; - - /** - * - */ - private static _collect(items: Metrics[], dataProvider: NumberProvider): number[] { - return items.map(dataProvider).filter(v => v != undefined && !Number.isNaN(v)) as number[]; - } - - // See https://en.wikipedia.org/wiki/Interquartile_range#Outliers for details on filtering. - /** - * - */ - private static _filteredValues(numbers: number[]): number[] { - numbers.sort((a, b) => a - b); - - if (numbers.length < 1) { - return []; - } - - const q1 = ss.quantileSorted(numbers, 0.25); - const q3 = ss.quantileSorted(numbers, 0.75); - const iqr = q3 - q1; - - return numbers.filter(num => num >= q1 - 1.5 * iqr && num <= q3 + 1.5 * iqr); - } -} diff --git a/dev-packages/overhead-metrics/src/results/pr-comment.ts b/dev-packages/overhead-metrics/src/results/pr-comment.ts deleted file mode 100644 index cd81d54dce20..000000000000 --- a/dev-packages/overhead-metrics/src/results/pr-comment.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { Git } from '../util/git.js'; -import type { Analysis, AnalyzerItemValues } from './analyzer.js'; -import { AnalyzerItemMetric, ResultsAnalyzer } from './analyzer.js'; -import { Result } from './result.js'; -import type { ResultSetItem } from './results-set.js'; - -function trimIndent(str: string): string { - return str - .trim() - .split('\n') - .map(s => s.trim()) - .join('\n'); -} - -function printableMetricName(metric: AnalyzerItemMetric): string { - switch (metric) { - case AnalyzerItemMetric.lcp: - return 'LCP'; - case AnalyzerItemMetric.cls: - return 'CLS'; - case AnalyzerItemMetric.cpu: - return 'CPU'; - case AnalyzerItemMetric.memoryAvg: - return 'JS heap avg'; - case AnalyzerItemMetric.memoryMax: - return 'JS heap max'; - default: - return AnalyzerItemMetric[metric]; - } -} - -export class PrCommentBuilder { - private _buffer: string = ''; - - /** - * - */ - public get title(): string { - return 'Replay SDK metrics :rocket:'; - } - - /** - * - */ - public get body(): string { - const now = new Date(); - return trimIndent(` - ${this._buffer} -
-
- *) pp - percentage points - an absolute difference between two percentages.
- Last updated: -
- `); - } - - /** - * - */ - public async addCurrentResult(analysis: Analysis, otherName: string): Promise { - // Decides whether to print the "Other" for comparison depending on it being set in the input data. - const hasOther = analysis.otherHash != undefined; - const maybeOther = function (content: () => string): string { - return hasOther ? content() : ''; - }; - - const currentHash = await Git.hash; - - this._buffer += `

${this.title}

`; - if (!hasOther) { - this._buffer += `Latest data for: ${currentHash}`; - } - this._buffer += ` - - - - - ${maybeOther(() => '')} - - - - - - ${maybeOther(() => '')} - - - - - - - - `; - - const valueColumns = function (values: AnalyzerItemValues): string { - return ` - - - - - - - - `; - }; - - for (const item of analysis.items) { - if (hasOther) { - this._buffer += ` - - - - - `; - } else { - this._buffer += ` - - - ${valueColumns(item.values)} - `; - } - } - - this._buffer += ` -
  Plain+Sentry+Replay
RevisionValueValueDiffRatioValueDiffRatio
${values.value(0)}${values.value(1)}${values.diff(0, 1)}${values.percent(0, 1)}${values.value(2)}${values.diff(0, 2)}${values.percent(0, 2)}
${printableMetricName(item.metric)}This PR ${currentHash} - ${valueColumns(item.values)} -
${otherName} ${analysis.otherHash} - ${valueColumns(item.others!)} -
${printableMetricName(item.metric)}
`; - } - - /** - * - */ - public async addAdditionalResultsSet(name: string, resultFiles: ResultSetItem[]): Promise { - if (resultFiles.length == 0) return; - - this._buffer += ` -
-

${name}

- `; - - // Each `resultFile` will be printed as a single row - with metrics as table columns. - for (let i = 0; i < resultFiles.length; i++) { - const resultFile = resultFiles[i]; - // Load the file and "analyse" - collect stats we want to print. - const analysis = await ResultsAnalyzer.analyze(Result.readFromFile(resultFile.path)); - - if (i == 0) { - // Add table header - this._buffer += ''; - for (const item of analysis.items) { - this._buffer += ``; - } - this._buffer += ''; - } - - // Add table row - this._buffer += ``; - for (const item of analysis.items) { - // TODO maybe find a better way of showing this. After the change to multiple scenarios, this shows diff between "With Sentry" and "With Sentry + Replay" - this._buffer += ``; - } - this._buffer += ''; - } - - this._buffer += ` -
Revision${printableMetricName(item.metric)}
${resultFile.hash}${item.values.diff(0, 2)}
-
`; - } -} diff --git a/dev-packages/overhead-metrics/src/results/result.ts b/dev-packages/overhead-metrics/src/results/result.ts deleted file mode 100644 index 3794e0163c39..000000000000 --- a/dev-packages/overhead-metrics/src/results/result.ts +++ /dev/null @@ -1,41 +0,0 @@ -import * as fs from 'fs'; -import path from 'path'; - -import { Metrics } from '../collector.js'; -import type { JsonObject } from '../util/json.js'; -import { JsonStringify } from '../util/json.js'; - -export class Result { - public constructor( - public readonly name: string, - public readonly cpuThrottling: number, - public readonly networkConditions: string, - public readonly scenarioResults: Metrics[][], - ) {} - - /** - * - */ - public static readFromFile(filePath: string): Result { - const json = fs.readFileSync(filePath, { encoding: 'utf-8' }); - const data = JSON.parse(json) as JsonObject; - return new Result( - data.name as string, - data.cpuThrottling as number, - data.networkConditions as string, - ((data.scenarioResults as Partial[][]) || []).map(list => list.map(Metrics.fromJSON.bind(Metrics))), - ); - } - - /** - * - */ - public writeToFile(filePath: string): void { - const dir = path.dirname(filePath); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } - const json = JsonStringify(this); - fs.writeFileSync(filePath, json); - } -} diff --git a/dev-packages/overhead-metrics/src/results/results-set.ts b/dev-packages/overhead-metrics/src/results/results-set.ts deleted file mode 100644 index 9eb65ff3cb4f..000000000000 --- a/dev-packages/overhead-metrics/src/results/results-set.ts +++ /dev/null @@ -1,116 +0,0 @@ -import assert from 'assert'; -import * as fs from 'fs'; -import path from 'path'; - -import type { GitHash } from '../util/git.js'; -import { Git } from '../util/git.js'; -import { Result } from './result.js'; - -const delimiter = '-'; - -export class ResultSetItem { - public constructor(public path: string) {} - - /** - * - */ - public get name(): string { - return path.basename(this.path); - } - - /** - * - */ - public get number(): number { - return parseInt(this.parts[0]); - } - - /** - * - */ - public get hash(): GitHash { - return this.parts[1]; - } - - /** - * - */ - public get parts(): string[] { - return path.basename(this.path).split(delimiter); - } -} - -/// Wraps a directory containing multiple (N--result.json) files. -/// The files are numbered from the most recently added one, to the oldest one. - -export class ResultsSet { - public constructor(private _directory: string) { - if (!fs.existsSync(_directory)) { - fs.mkdirSync(_directory, { recursive: true }); - } - } - - /** - * - */ - public find(predicate: (value: Result) => boolean): [GitHash, Result] | undefined { - for (const item of this.items()) { - const result = Result.readFromFile(item.path); - if (predicate(result)) { - return [item.hash, result]; - } - } - return undefined; - } - - /** - * - */ - public items(): ResultSetItem[] { - return this._files() - .map(file => { - return new ResultSetItem(path.join(this._directory, file.name)); - }) - .filter(item => !isNaN(item.number)) - .sort((a, b) => a.number - b.number); - } - - /** - * - */ - public async add(newFile: string, onlyIfDifferent: boolean = false): Promise { - console.log(`Preparing to add ${newFile} to ${this._directory}`); - assert(fs.existsSync(newFile)); - - // Get the list of file sorted by the prefix number in the descending order (starting with the oldest files). - const files = this.items().sort((a, b) => b.number - a.number); - - if (onlyIfDifferent && files.length > 0) { - const latestFile = files[files.length - 1]; - if (fs.readFileSync(latestFile.path, { encoding: 'utf-8' }) == fs.readFileSync(newFile, { encoding: 'utf-8' })) { - console.log(`Skipping - it's already stored as ${latestFile.name}`); - return; - } - } - - // Rename all existing files, increasing the prefix - for (const file of files) { - const parts = file.name.split(delimiter); - parts[0] = (file.number + 1).toString(); - const newPath = path.join(this._directory, parts.join(delimiter)); - console.log(`Renaming ${file.path} to ${newPath}`); - fs.renameSync(file.path, newPath); - } - - const newName = `1${delimiter}${await Git.hash}${delimiter}result.json`; - console.log(`Adding ${newFile} to ${this._directory} as ${newName}`); - fs.copyFileSync(newFile, path.join(this._directory, newName)); - } - - /** - * - */ - private _files(): fs.Dirent[] { - return fs.readdirSync(this._directory, { withFileTypes: true }).filter(v => v.isFile()); - } -} diff --git a/dev-packages/overhead-metrics/src/scenarios.ts b/dev-packages/overhead-metrics/src/scenarios.ts deleted file mode 100644 index f4d59cf06d53..000000000000 --- a/dev-packages/overhead-metrics/src/scenarios.ts +++ /dev/null @@ -1,80 +0,0 @@ -import assert from 'assert'; -import * as fs from 'fs'; -import path from 'path'; -import type * as playwright from 'playwright'; - -import type { Metrics } from './collector'; - -// A testing scenario we want to collect metrics for. -export interface Scenario { - run(browser: playwright.Browser, page: playwright.Page): Promise; -} - -// Two scenarios that are compared to each other. -export interface TestCase { - name: string; - scenarios: Scenario[]; - runs: number; - tries: number; - - // Test function that will be executed and given a scenarios result set with exactly `runs` number of items. - // Should returns true if this "try" should be accepted and collected. - // If false is returned, `Collector` will retry up to `tries` number of times. - shouldAccept(results: Metrics[]): Promise; -} - -// A simple scenario that just loads the given URL. - -export class LoadPageScenario implements Scenario { - public constructor(public url: string) {} - - /** - * - */ - public async run(_: playwright.Browser, page: playwright.Page): Promise { - await page.goto(this.url, { waitUntil: 'load', timeout: 60000 }); - } -} - -// Loads test-apps/jank/ as a page source & waits for a short time before quitting. - -export class JankTestScenario implements Scenario { - public constructor(private _indexFile: string) {} - - /** - * - */ - public async run(_: playwright.Browser, page: playwright.Page): Promise { - let url = path.resolve(`./test-apps/jank/${this._indexFile}`); - assert(fs.existsSync(url)); - url = `file:///${url.replace(/\\/g, '/')}`; - console.log('Navigating to ', url); - await page.goto(url, { waitUntil: 'load', timeout: 60000 }); - await new Promise(resolve => setTimeout(resolve, 12000)); - } -} - -export class BookingAppScenario implements Scenario { - public constructor(private _indexFile: string, private _count: number) {} - - /** - * - */ - public async run(_: playwright.Browser, page: playwright.Page): Promise { - let url = path.resolve(`./test-apps/booking-app/${this._indexFile}`); - assert(fs.existsSync(url)); - url = `file:///${url.replace(/\\/g, '/')}?count=${this._count}`; - console.log('Navigating to ', url); - await page.goto(url, { waitUntil: 'load', timeout: 60000 }); - - // Click "Update" - await page.locator('#search button').click(); - - for (let i = 1; i < 10; i++) { - await page.locator(`.result:nth-child(${i}) [data-select]`).click(); - } - - // Wait for flushing, which we set to 2000ms - to be safe, we add 1s on top - await new Promise(resolve => setTimeout(resolve, 3000)); - } -} diff --git a/dev-packages/overhead-metrics/src/util/console.ts b/dev-packages/overhead-metrics/src/util/console.ts deleted file mode 100644 index b3343e05ffae..000000000000 --- a/dev-packages/overhead-metrics/src/util/console.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { filesize } from 'filesize'; - -import type { Metrics } from '../collector.js'; -import type { Analysis } from '../results/analyzer.js'; -import { AnalyzerItemMetric } from '../results/analyzer.js'; -import { MetricsStats } from '../results/metrics-stats.js'; - -export async function consoleGroup(code: () => Promise): Promise { - console.group(); - return code().finally(console.groupEnd); -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type PrintableTable = { [k: string]: any }; - -export function printStats(items: Metrics[]): void { - console.table({ - lcp: `${MetricsStats.mean(items, MetricsStats.lcp)?.toFixed(2)} ms`, - cls: `${MetricsStats.mean(items, MetricsStats.cls)?.toFixed(2)} ms`, - cpu: `${((MetricsStats.mean(items, MetricsStats.cpu) || 0) * 100).toFixed(2)} %`, - memoryMean: filesize(MetricsStats.mean(items, MetricsStats.memoryMean)), - memoryMax: filesize(MetricsStats.max(items, MetricsStats.memoryMax)), - netTx: filesize(MetricsStats.mean(items, MetricsStats.netTx)), - netRx: filesize(MetricsStats.mean(items, MetricsStats.netRx)), - netCount: MetricsStats.mean(items, MetricsStats.netCount), - netTime: `${MetricsStats.mean(items, MetricsStats.netTime)?.toFixed(2)} ms`, - }); -} - -export function printAnalysis(analysis: Analysis): void { - const table: PrintableTable = {}; - for (const item of analysis.items) { - table[AnalyzerItemMetric[item.metric]] = { - value: item.values.value(0), - withSentry: item.values.diff(0, 1), - withReplay: item.values.diff(0, 2), - ...(item.others == undefined - ? {} - : { - previous: item.others.value(0), - previousWithSentry: item.others.diff(0, 1), - previousWithReplay: item.others.diff(0, 2), - }), - }; - } - console.table(table); -} diff --git a/dev-packages/overhead-metrics/src/util/git.ts b/dev-packages/overhead-metrics/src/util/git.ts deleted file mode 100644 index 6882c2f213b5..000000000000 --- a/dev-packages/overhead-metrics/src/util/git.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { simpleGit } from 'simple-git'; - -export type GitHash = string; -const git = simpleGit(); - -async function defaultBranch(): Promise { - const remoteInfo = (await git.remote(['show', 'origin'])) as string; - for (let line of remoteInfo.split('\n')) { - line = line.trim(); - if (line.startsWith('HEAD branch:')) { - return line.substring('HEAD branch:'.length).trim(); - } - } - throw "Couldn't find base branch name"; -} - -export const Git = { - get repository(): Promise { - return (async () => { - if (typeof process.env.GITHUB_REPOSITORY == 'string' && process.env.GITHUB_REPOSITORY.length > 0) { - return `github.com/${process.env.GITHUB_REPOSITORY}`; - } else { - let url = (await git.remote(['get-url', 'origin'])) as string; - url = url.trim(); - url = url.replace(/^git@/, ''); - url = url.replace(/\.git$/, ''); - return url.replace(':', '/'); - } - })(); - }, - - get branch(): Promise { - return (async () => { - if (typeof process.env.GITHUB_HEAD_REF == 'string' && process.env.GITHUB_HEAD_REF.length > 0) { - return process.env.GITHUB_HEAD_REF; - } else if (typeof process.env.GITHUB_REF == 'string' && process.env.GITHUB_REF.startsWith('refs/heads/')) { - return process.env.GITHUB_REF.substring('refs/heads/'.length); - } else { - const branches = (await git.branchLocal()).branches; - for (const name in branches) { - if (branches[name].current) return name; - } - throw "Couldn't find current branch name"; - } - })(); - }, - - get baseBranch(): Promise { - if (typeof process.env.GITHUB_BASE_REF == 'string' && process.env.GITHUB_BASE_REF.length > 0) { - return Promise.resolve(process.env.GITHUB_BASE_REF); - } else { - return defaultBranch(); - } - }, - - get branchIsBase(): Promise { - return (async () => { - const branch = await this.branch; - const baseBranch = await this.baseBranch; - - return branch === baseBranch; - })(); - }, - - get hash(): Promise { - return (async () => { - let gitHash = await git.revparse('HEAD'); - const diff = await git.diff(); - if (diff.trim().length > 0) { - gitHash += '+dirty'; - } - return gitHash; - })(); - }, -}; diff --git a/dev-packages/overhead-metrics/src/util/github.ts b/dev-packages/overhead-metrics/src/util/github.ts deleted file mode 100644 index 707d9529f8ae..000000000000 --- a/dev-packages/overhead-metrics/src/util/github.ts +++ /dev/null @@ -1,205 +0,0 @@ -import * as fs from 'fs'; -import path from 'path'; -import { Octokit } from '@octokit/rest'; -import axios from 'axios'; -import extract from 'extract-zip'; - -import type { PrCommentBuilder } from '../results/pr-comment.js'; -import { consoleGroup } from './console.js'; -import { Git } from './git.js'; - -const octokit = new Octokit({ - auth: process.env.GITHUB_TOKEN, - // log: console, -}); - -const [, owner, repo] = (await Git.repository).split('/') as [string, string, string]; -const defaultArgs = { owner, repo }; - -async function downloadArtifact(url: string, path: string): Promise { - const writer = fs.createWriteStream(path); - return axios({ - method: 'get', - url: url, - responseType: 'stream', - headers: { - Authorization: `Bearer ${process.env.GITHUB_TOKEN}`, - }, - }).then(response => { - return new Promise((resolve, reject) => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - response.data.pipe(writer); - let error: Error; - writer.on('error', err => { - error = err; - writer.close(); - reject(err); - }); - writer.on('close', () => { - if (!error) resolve(); - }); - }); - }); -} - -async function tryAddOrUpdateComment(commentBuilder: PrCommentBuilder): Promise { - /* Env var GITHUB_REF is only set if a branch or tag is available for the current CI event trigger type. - The ref given is fully-formed, meaning that - * for branches the format is refs/heads/, - * for pull requests it is refs/pull//merge, - * and for tags it is refs/tags/. - For example, refs/heads/feature-branch-1. - */ - let prNumber: number | undefined; - const githubRef = process.env.GITHUB_REF; - if (typeof githubRef == 'string' && githubRef.length > 0 && githubRef.startsWith('refs/pull/')) { - prNumber = parseInt(githubRef.split('/')[2] as string); - console.log(`Determined PR number ${prNumber} based on GITHUB_REF environment variable: '${githubRef}'`); - } else if (!(await Git.branchIsBase)) { - prNumber = ( - await octokit.rest.pulls.list({ - ...defaultArgs, - base: await Git.baseBranch, - head: await Git.branch, - }) - ).data[0]?.number; - if (prNumber != undefined) { - console.log(`Found PR number ${prNumber} based on base and head branches`); - } - } - - if (prNumber == undefined) return false; - - // Determine the PR comment author: - // Trying to fetch `octokit.users.getAuthenticated()` throws (in CI only): - // {"message":"Resource not accessible by integration","documentation_url":"https://docs.github.com/rest/reference/users#get-the-authenticated-user"} - // Let's make this conditional on some env variable that's unlikely to be set locally but will be set in GH Actions. - // Do not use "CI" because that's commonly set during local development and testing. - const author = - typeof process.env.GITHUB_ACTION == 'string' - ? 'github-actions[bot]' - : (await octokit.users.getAuthenticated()).data.login; - - // Try to find an existing comment by the author and title. - const comment = await (async () => { - for await (const comments of octokit.paginate.iterator(octokit.rest.issues.listComments, { - ...defaultArgs, - issue_number: prNumber, - })) { - const found = comments.data.find(comment => { - return ( - comment.user?.login == author && comment.body != undefined && comment.body.indexOf(commentBuilder.title) >= 0 - ); - }); - if (found) return found; - } - return undefined; - })(); - - if (comment != undefined) { - console.log(`Updating PR comment ${comment.html_url} body`); - await octokit.rest.issues.updateComment({ - ...defaultArgs, - comment_id: comment.id, - body: commentBuilder.body, - }); - } else { - console.log(`Adding a new comment to PR ${prNumber}`); - await octokit.rest.issues.createComment({ - ...defaultArgs, - issue_number: prNumber, - body: commentBuilder.body, - }); - } - - return true; -} - -export const GitHub = { - writeOutput(name: string, value: string): void { - if (typeof process.env.GITHUB_OUTPUT == 'string' && process.env.GITHUB_OUTPUT.length > 0) { - fs.appendFileSync(process.env.GITHUB_OUTPUT, `${name}=${value}\n`); - } - console.log(`Output ${name} = ${value}`); - }, - - downloadPreviousArtifact(branch: string, targetDir: string, artifactName: string): Promise { - console.log(`Trying to download previous artifact '${artifactName}' for branch '${branch}'`); - return consoleGroup(async () => { - fs.mkdirSync(targetDir, { recursive: true }); - - const workflow = await (async () => { - for await (const workflows of octokit.paginate.iterator(octokit.rest.actions.listRepoWorkflows, defaultArgs)) { - const found = workflows.data.find(w => w.name == process.env.GITHUB_WORKFLOW); - if (found) return found; - } - return undefined; - })(); - if (workflow == undefined) { - console.log( - `Skipping previous artifact '${artifactName}' download for branch '${branch}' - not running in CI?`, - "Environment variable GITHUB_WORKFLOW isn't set.", - ); - return; - } - - const workflowRuns = await octokit.actions.listWorkflowRuns({ - ...defaultArgs, - workflow_id: workflow.id, - branch: branch, - status: 'success', - }); - - const firstRun = workflowRuns.data.workflow_runs[0]; - - if (workflowRuns.data.total_count == 0 || !firstRun) { - console.warn(`Couldn't find any successful run for workflow '${workflow.name}'`); - return; - } - - const artifact = ( - await octokit.actions.listWorkflowRunArtifacts({ - ...defaultArgs, - run_id: firstRun.id, - }) - ).data.artifacts.find(it => it.name == artifactName); - - if (artifact == undefined) { - console.warn(`Couldn't find any artifact matching ${artifactName}`); - return; - } - - console.log(`Downloading artifact ${artifact.archive_download_url} and extracting to ${targetDir}`); - - const tempFilePath = path.resolve(targetDir, '../tmp-artifacts.zip'); - if (fs.existsSync(tempFilePath)) { - fs.unlinkSync(tempFilePath); - } - - try { - await downloadArtifact(artifact.archive_download_url, tempFilePath); - await extract(tempFilePath, { dir: path.resolve(targetDir) }); - } finally { - if (fs.existsSync(tempFilePath)) { - fs.unlinkSync(tempFilePath); - } - } - }); - }, - - async addOrUpdateComment(commentBuilder: PrCommentBuilder): Promise { - console.log('Adding/updating PR comment'); - return consoleGroup(async () => { - let successful = false; - try { - successful = await tryAddOrUpdateComment(commentBuilder); - } finally { - if (!successful) { - const file = 'out/comment.html'; - console.log(`Writing built comment to ${path.resolve(file)}`); - fs.writeFileSync(file, commentBuilder.body); - } - } - }); - }, -}; diff --git a/dev-packages/overhead-metrics/src/util/json.ts b/dev-packages/overhead-metrics/src/util/json.ts deleted file mode 100644 index 87a5676869aa..000000000000 --- a/dev-packages/overhead-metrics/src/util/json.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ - -export type JsonObject = { [k: string]: T }; - -export function JsonStringify(object: T): string { - return JSON.stringify( - object, - (_: unknown, value: any): unknown => { - if (typeof value != 'undefined' && typeof value.toJSON == 'function') { - return value.toJSON(); - } else if (typeof value == 'bigint') { - return value.toString(); - } else { - return value; - } - }, - 2, - ); -} diff --git a/dev-packages/overhead-metrics/src/vitals/cls.ts b/dev-packages/overhead-metrics/src/vitals/cls.ts deleted file mode 100644 index 3e1ab977fb86..000000000000 --- a/dev-packages/overhead-metrics/src/vitals/cls.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type * as playwright from 'playwright'; - -export { CLS }; - -// https://web.dev/cls/ -class CLS { - public constructor(private _page: playwright.Page) {} - - public async setup(): Promise { - await this._page.context().addInitScript(`{ - window.cumulativeLayoutShiftScore = undefined; - - const observer = new PerformanceObserver((list) => { - for (const entry of list.getEntries()) { - if (window.cumulativeLayoutShiftScore === undefined) { - window.cumulativeLayoutShiftScore = entry.value; - } else if (!entry.hadRecentInput) { - window.cumulativeLayoutShiftScore += entry.value; - } - } - }); - - observer.observe({type: 'layout-shift', buffered: true}); - - document.addEventListener('visibilitychange', () => { - if (document.visibilityState === 'hidden') { - observer.takeRecords(); - observer.disconnect(); - } - }); - }`); - } - - public async collect(): Promise { - const result = await this._page.evaluate('window.cumulativeLayoutShiftScore'); - return result as number; - } -} diff --git a/dev-packages/overhead-metrics/src/vitals/fid.ts b/dev-packages/overhead-metrics/src/vitals/fid.ts deleted file mode 100644 index feb2324aa034..000000000000 --- a/dev-packages/overhead-metrics/src/vitals/fid.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type * as playwright from 'playwright'; - -export { FID }; - -// https://web.dev/fid/ -class FID { - public constructor(private _page: playwright.Page) {} - - public async setup(): Promise { - await this._page.context().addInitScript(`{ - window.firstInputDelay = undefined; - - const observer = new PerformanceObserver((entryList) => { - for (const entry of entryList.getEntries()) { - window.firstInputDelay = entry.processingStart - entry.startTime; - } - }) - - observer.observe({type: 'first-input', buffered: true}); - - document.addEventListener('visibilitychange', () => { - if (document.visibilityState === 'hidden') { - observer.takeRecords(); - observer.disconnect(); - } - }); - }`); - } - - public async collect(): Promise { - const result = await this._page.evaluate('window.firstInputDelay'); - return result as number; - } -} diff --git a/dev-packages/overhead-metrics/src/vitals/index.ts b/dev-packages/overhead-metrics/src/vitals/index.ts deleted file mode 100644 index b573edb26bb6..000000000000 --- a/dev-packages/overhead-metrics/src/vitals/index.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type * as playwright from 'playwright'; - -import { CLS } from './cls.js'; -import { FID } from './fid.js'; -import { LCP } from './lcp.js'; - -export { WebVitals, WebVitalsCollector }; - -class WebVitals { - public constructor(public lcp: number | undefined, public cls: number | undefined, public fid: number | undefined) {} - - public static fromJSON(data: Partial): WebVitals { - return new WebVitals(data.lcp as number, data.cls as number, data.fid as number); - } -} - -class WebVitalsCollector { - private constructor(private _lcp: LCP, private _cls: CLS, private _fid: FID) {} - - public static async create(page: playwright.Page): Promise { - const result = new WebVitalsCollector(new LCP(page), new CLS(page), new FID(page)); - await result._lcp.setup(); - await result._cls.setup(); - await result._fid.setup(); - return result; - } - - public async collect(): Promise { - return new WebVitals(await this._lcp.collect(), await this._cls.collect(), await this._fid.collect()); - } -} diff --git a/dev-packages/overhead-metrics/src/vitals/lcp.ts b/dev-packages/overhead-metrics/src/vitals/lcp.ts deleted file mode 100644 index a471bf60ba4b..000000000000 --- a/dev-packages/overhead-metrics/src/vitals/lcp.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type * as playwright from 'playwright'; - -export { LCP }; - -// https://web.dev/lcp/ -class LCP { - public constructor(private _page: playwright.Page) {} - - public async setup(): Promise { - await this._page.context().addInitScript(`{ - window.largestContentfulPaint = undefined; - - const observer = new PerformanceObserver((list) => { - const entries = list.getEntries(); - const lastEntry = entries[entries.length - 1]; - window.largestContentfulPaint = lastEntry.renderTime || lastEntry.loadTime; - }); - - observer.observe({ type: 'largest-contentful-paint', buffered: true }); - - document.addEventListener('visibilitychange', () => { - if (document.visibilityState === 'hidden') { - observer.takeRecords(); - observer.disconnect(); - } - }); - }`); - } - - public async collect(): Promise { - const result = await this._page.evaluate('window.largestContentfulPaint'); - return result as number; - } -} diff --git a/dev-packages/overhead-metrics/test-apps/booking-app/img/house-0.jpg b/dev-packages/overhead-metrics/test-apps/booking-app/img/house-0.jpg deleted file mode 100644 index ff0962fc24f1..000000000000 Binary files a/dev-packages/overhead-metrics/test-apps/booking-app/img/house-0.jpg and /dev/null differ diff --git a/dev-packages/overhead-metrics/test-apps/booking-app/img/house-1.jpg b/dev-packages/overhead-metrics/test-apps/booking-app/img/house-1.jpg deleted file mode 100644 index 138f0b58ab2e..000000000000 Binary files a/dev-packages/overhead-metrics/test-apps/booking-app/img/house-1.jpg and /dev/null differ diff --git a/dev-packages/overhead-metrics/test-apps/booking-app/img/house-2.jpg b/dev-packages/overhead-metrics/test-apps/booking-app/img/house-2.jpg deleted file mode 100644 index 31f6bc1d5184..000000000000 Binary files a/dev-packages/overhead-metrics/test-apps/booking-app/img/house-2.jpg and /dev/null differ diff --git a/dev-packages/overhead-metrics/test-apps/booking-app/index.html b/dev-packages/overhead-metrics/test-apps/booking-app/index.html deleted file mode 100644 index e3972c61e4c0..000000000000 --- a/dev-packages/overhead-metrics/test-apps/booking-app/index.html +++ /dev/null @@ -1,219 +0,0 @@ - - - - Demo Booking Engine - - - - - - - - -
-
-

This is a test app.

- -
- -
- -
-
-
-
-
-
-
- - - - diff --git a/dev-packages/overhead-metrics/test-apps/booking-app/main.js b/dev-packages/overhead-metrics/test-apps/booking-app/main.js deleted file mode 100644 index 1ad19f429506..000000000000 --- a/dev-packages/overhead-metrics/test-apps/booking-app/main.js +++ /dev/null @@ -1,180 +0,0 @@ -(function () { - const searchForm = document.querySelector('#search'); - - searchForm.addEventListener('submit', event => { - event.preventDefault(); - - updateOffers(); - }); - - const obs = new MutationObserver(function (mutations) { - console.log(mutations); - }); - - obs.observe(document.documentElement, { - attributes: true, - attributeOldValue: true, - characterData: true, - characterDataOldValue: true, - childList: true, - subtree: true, - }); -})(); - -function updateOffers() { - const list = document.querySelector('.result-list'); - - // Clear out existing children - for (let el of list.children) { - list.removeChild(el); - } - - // Add new children - // Allow to define children count via URL ?count=100 - const url = new URL(window.location.href); - const count = parseInt(url.searchParams.get('count') || 50); - for (let i = 0; i < count; i++) { - const el = document.createElement('div'); - el.classList.add('result'); - el.innerHTML = generateResult(); - - const id = crypto.randomUUID(); - el.setAttribute('id', id); - - addListeners(id, el); - - list.appendChild(el); - } -} - -function addListeners(id, el) { - el.querySelector('[data-long-text-open]').addEventListener('click', event => { - const parent = event.target.closest('.long-text'); - parent.setAttribute('data-show-long', ''); - }); - el.querySelector('[data-long-text-close]').addEventListener('click', event => { - const parent = event.target.closest('.long-text'); - parent.removeAttribute('data-show-long'); - }); - - // These are purposefully inefficient - el.querySelector('[data-select]').addEventListener('click', () => { - document.querySelectorAll('.result').forEach(result => { - if (result.getAttribute('id') === id) { - result.setAttribute('data-show-options', 'yes'); - } else { - result.setAttribute('data-show-options', 'no'); - } - }); - - // Do some more, extra expensive work - document.querySelectorAll('.select__price').forEach(el => { - el.setAttribute('js-is-checked', new Date().toISOString()); - el.setAttribute('js-is-checked-2', new Date().toISOString()); - el.setAttribute('js-is-checked-3', 'yes'); - el.setAttribute('js-is-checked-4', 'yes'); - el.setAttribute('js-is-checked-5', 'yes'); - el.setAttribute('js-is-checked-6', 'yes'); - }); - document.querySelectorAll('.tag').forEach(el => el.setAttribute('js-is-checked', 'yes')); - document.querySelectorAll('h3').forEach(el => el.setAttribute('js-is-checked', 'yes')); - }); -} - -const baseTitles = ['Cottage house', 'Cabin', 'Villa', 'House', 'Appartment', 'Cosy appartment']; -const baseBeds = ['2', '2+2', '4+2', '6+2', '6+4']; -const baseDescription = - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'; - -function generateResult() { - const title = `${getRandomItem(baseTitles)} ${Math.ceil(Math.random() * 20)}`; - const beds = getRandomItem(baseBeds); - const description = baseDescription - .split(' ') - .slice(Math.ceil(Math.random() * 10)) - .join(' '); - const price = 200 + Math.random() * 800; - - // Make short version of description - const descriptionShort = description.slice(0, 200); - const priceStr = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(price); - - const imgSrc = `./img/house-${Math.floor(Math.random() * 3)}.jpg`; - - const placeholders = { - title, - beds, - description, - descriptionShort, - priceStr, - imgSrc, - }; - - return replacePlaceholders(template, placeholders); -} - -function getRandomItem(list) { - return list[Math.floor(Math.random() * list.length)]; -} - -function replacePlaceholders(str, placeholders) { - let replacedStr = str; - Object.keys(placeholders).forEach(placeholder => { - replacedStr = replacedStr.replaceAll(`{{${placeholder}}}`, placeholders[placeholder]); - }); - - return replacedStr; -} - -const template = `
- {{title}} -
- -
-
-

{{title}}

- -
- {{beds}} -
-
- -
-
- {{descriptionShort}} -
- -
- {{description}} - -
-
- -
- -
- -
-
- -
- -
- -
-
-
`; diff --git a/dev-packages/overhead-metrics/test-apps/booking-app/with-replay.html b/dev-packages/overhead-metrics/test-apps/booking-app/with-replay.html deleted file mode 100644 index ae99a6171f0c..000000000000 --- a/dev-packages/overhead-metrics/test-apps/booking-app/with-replay.html +++ /dev/null @@ -1,236 +0,0 @@ - - - - Demo Booking Engine - - - - - - - - -
-
-

This is a test app.

- -
- -
- -
-
-
-
-
-
-
- - - - - - diff --git a/dev-packages/overhead-metrics/test-apps/booking-app/with-sentry.html b/dev-packages/overhead-metrics/test-apps/booking-app/with-sentry.html deleted file mode 100644 index 94c581f184ab..000000000000 --- a/dev-packages/overhead-metrics/test-apps/booking-app/with-sentry.html +++ /dev/null @@ -1,227 +0,0 @@ - - - - Demo Booking Engine - - - - - - - - -
-
-

This is a test app.

- -
- -
- -
-
-
-
-
-
-
- - - - - - diff --git a/dev-packages/overhead-metrics/test-apps/jank/README.md b/dev-packages/overhead-metrics/test-apps/jank/README.md deleted file mode 100644 index beb81a4eadd2..000000000000 --- a/dev-packages/overhead-metrics/test-apps/jank/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# Chrome DevTools Jank article sample code - -- Originally coming from - [devtools-samples](https://github.com/GoogleChrome/devtools-samples/tree/4818abc9dbcdb954d0eb9b70879f4ea18756451f/jank), - licensed under Apache 2.0. -- Linking article: diff --git a/dev-packages/overhead-metrics/test-apps/jank/app.js b/dev-packages/overhead-metrics/test-apps/jank/app.js deleted file mode 100644 index 23fc9ced5111..000000000000 --- a/dev-packages/overhead-metrics/test-apps/jank/app.js +++ /dev/null @@ -1,170 +0,0 @@ -/* Copyright 2016 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. */ - -document.addEventListener('DOMContentLoaded', function () { - 'use strict'; - - var app = {}, - proto = document.querySelector('.proto'), - movers, - bodySize = document.body.getBoundingClientRect(), - ballSize = proto.getBoundingClientRect(), - maxHeight = Math.floor(bodySize.height - ballSize.height), - maxWidth = 97, // 100vw - width of square (3vw) - incrementor = 10, - distance = 3, - frame, - minimum = 20, - subtract = document.querySelector('.subtract'), - add = document.querySelector('.add'); - - app.optimize = true; - app.count = minimum; - app.enableApp = true; - - app.init = function () { - if (movers) { - bodySize = document.body.getBoundingClientRect(); - for (var i = 0; i < movers.length; i++) { - document.body.removeChild(movers[i]); - } - document.body.appendChild(proto); - ballSize = proto.getBoundingClientRect(); - document.body.removeChild(proto); - maxHeight = Math.floor(bodySize.height - ballSize.height); - } - for (var i = 0; i < app.count; i++) { - var m = proto.cloneNode(); - var top = Math.floor(Math.random() * maxHeight); - if (top === maxHeight) { - m.classList.add('up'); - } else { - m.classList.add('down'); - } - m.style.left = i / (app.count / maxWidth) + 'vw'; - m.style.top = top + 'px'; - document.body.appendChild(m); - } - movers = document.querySelectorAll('.mover'); - }; - - app.update = function (timestamp) { - for (var i = 0; i < app.count; i++) { - var m = movers[i]; - if (!app.optimize) { - var pos = m.classList.contains('down') ? m.offsetTop + distance : m.offsetTop - distance; - if (pos < 0) pos = 0; - if (pos > maxHeight) pos = maxHeight; - m.style.top = pos + 'px'; - if (m.offsetTop === 0) { - m.classList.remove('up'); - m.classList.add('down'); - } - if (m.offsetTop === maxHeight) { - m.classList.remove('down'); - m.classList.add('up'); - } - } else { - var pos = parseInt(m.style.top.slice(0, m.style.top.indexOf('px'))); - m.classList.contains('down') ? (pos += distance) : (pos -= distance); - if (pos < 0) pos = 0; - if (pos > maxHeight) pos = maxHeight; - m.style.top = pos + 'px'; - if (pos === 0) { - m.classList.remove('up'); - m.classList.add('down'); - } - if (pos === maxHeight) { - m.classList.remove('down'); - m.classList.add('up'); - } - } - } - frame = window.requestAnimationFrame(app.update); - }; - - document.querySelector('.stop').addEventListener('click', function (e) { - if (app.enableApp) { - cancelAnimationFrame(frame); - e.target.textContent = 'Start'; - app.enableApp = false; - } else { - frame = window.requestAnimationFrame(app.update); - e.target.textContent = 'Stop'; - app.enableApp = true; - } - }); - - document.querySelector('.optimize').addEventListener('click', function (e) { - if (e.target.textContent === 'Optimize') { - app.optimize = true; - e.target.textContent = 'Un-Optimize'; - } else { - app.optimize = false; - e.target.textContent = 'Optimize'; - } - }); - - add.addEventListener('click', function (e) { - cancelAnimationFrame(frame); - app.count += incrementor; - subtract.disabled = false; - app.init(); - frame = requestAnimationFrame(app.update); - }); - - subtract.addEventListener('click', function () { - cancelAnimationFrame(frame); - app.count -= incrementor; - app.init(); - frame = requestAnimationFrame(app.update); - if (app.count === minimum) { - subtract.disabled = true; - } - }); - - function debounce(func, wait, immediate) { - var timeout; - return function () { - var context = this, - args = arguments; - var later = function () { - timeout = null; - if (!immediate) func.apply(context, args); - }; - var callNow = immediate && !timeout; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - if (callNow) func.apply(context, args); - }; - } - - var onResize = debounce(function () { - if (app.enableApp) { - cancelAnimationFrame(frame); - app.init(); - frame = requestAnimationFrame(app.update); - } - }, 500); - - window.addEventListener('resize', onResize); - - add.textContent = 'Add ' + incrementor; - subtract.textContent = 'Subtract ' + incrementor; - document.body.removeChild(proto); - proto.classList.remove('.proto'); - app.init(); - window.app = app; - frame = window.requestAnimationFrame(app.update); -}); diff --git a/dev-packages/overhead-metrics/test-apps/jank/favicon-96x96.png b/dev-packages/overhead-metrics/test-apps/jank/favicon-96x96.png deleted file mode 100644 index 7f01723cee0f..000000000000 Binary files a/dev-packages/overhead-metrics/test-apps/jank/favicon-96x96.png and /dev/null differ diff --git a/dev-packages/overhead-metrics/test-apps/jank/index.html b/dev-packages/overhead-metrics/test-apps/jank/index.html deleted file mode 100644 index 5c06fc377622..000000000000 --- a/dev-packages/overhead-metrics/test-apps/jank/index.html +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - Janky Animation - - - - - - - -
- - - - - - - -
- - diff --git a/dev-packages/overhead-metrics/test-apps/jank/logo-1024px.png b/dev-packages/overhead-metrics/test-apps/jank/logo-1024px.png deleted file mode 100644 index 84df3e22f6b0..000000000000 Binary files a/dev-packages/overhead-metrics/test-apps/jank/logo-1024px.png and /dev/null differ diff --git a/dev-packages/overhead-metrics/test-apps/jank/styles.css b/dev-packages/overhead-metrics/test-apps/jank/styles.css deleted file mode 100644 index 052c8c4e608c..000000000000 --- a/dev-packages/overhead-metrics/test-apps/jank/styles.css +++ /dev/null @@ -1,59 +0,0 @@ -/* Copyright 2016 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - * implied. See the License for the specific language governing permissions - * and limitations under the License. */ - -* { - margin: 0; - padding: 0; -} - -body { - height: 100vh; - width: 100vw; -} - -.controls { - position: fixed; - top: 2vw; - left: 2vw; - z-index: 1; -} - -.controls button { - display: block; - font-size: 1em; - padding: 1em; - margin: 1em; - background-color: beige; - color: black; -} - -.subtract:disabled { - opacity: 0.2; -} - -.mover { - height: 3vw; - position: absolute; - z-index: 0; -} - -.border { - border: 1px solid black; -} - -@media (max-width: 600px) { - .controls button { - min-width: 20vw; - } -} diff --git a/dev-packages/overhead-metrics/test-apps/jank/with-replay.html b/dev-packages/overhead-metrics/test-apps/jank/with-replay.html deleted file mode 100644 index 6c5f32cc7e8d..000000000000 --- a/dev-packages/overhead-metrics/test-apps/jank/with-replay.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - Janky Animation - - - - - - - - - - -
- - - - - - - -
- - diff --git a/dev-packages/overhead-metrics/test-apps/jank/with-sentry.html b/dev-packages/overhead-metrics/test-apps/jank/with-sentry.html deleted file mode 100644 index 8beacb69b440..000000000000 --- a/dev-packages/overhead-metrics/test-apps/jank/with-sentry.html +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - Janky Animation - - - - - - - - - - -
- - - - - - - -
- - diff --git a/dev-packages/overhead-metrics/tsconfig.json b/dev-packages/overhead-metrics/tsconfig.json deleted file mode 100644 index 193dd1f8acde..000000000000 --- a/dev-packages/overhead-metrics/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "target": "es2020", - "module": "esnext", - "outDir": "build", - "esModuleInterop": true - }, - "include": ["src/**/*.ts", "configs/**/*.ts"] -} diff --git a/dev-packages/rollup-utils/npmHelpers.mjs b/dev-packages/rollup-utils/npmHelpers.mjs index 1a855e5674b7..4e6483364ee4 100644 --- a/dev-packages/rollup-utils/npmHelpers.mjs +++ b/dev-packages/rollup-utils/npmHelpers.mjs @@ -36,6 +36,7 @@ export function makeBaseNPMConfig(options = {}) { packageSpecificConfig = {}, addPolyfills = true, sucrase = {}, + bundledBuiltins = [], } = options; const nodeResolvePlugin = makeNodeResolvePlugin(); @@ -113,7 +114,7 @@ export function makeBaseNPMConfig(options = {}) { // don't include imported modules from outside the package in the final output external: [ - ...builtinModules, + ...builtinModules.filter(m => !bundledBuiltins.includes(m)), ...Object.keys(packageDotJSON.dependencies || {}), ...Object.keys(packageDotJSON.peerDependencies || {}), ...Object.keys(packageDotJSON.optionalDependencies || {}), diff --git a/dev-packages/test-utils/src/event-proxy-server.ts b/dev-packages/test-utils/src/event-proxy-server.ts index 448dd6e34ef0..6508dd9e5a0c 100644 --- a/dev-packages/test-utils/src/event-proxy-server.ts +++ b/dev-packages/test-utils/src/event-proxy-server.ts @@ -171,6 +171,11 @@ export async function startProxyServer( export async function startEventProxyServer(options: EventProxyServerOptions): Promise { if (options.envelopeDumpPath) { await fs.promises.mkdir(path.dirname(path.resolve(options.envelopeDumpPath)), { recursive: true }); + try { + await fs.promises.unlink(path.resolve(options.envelopeDumpPath)); + } catch { + // noop + } } await startProxyServer(options, async (eventCallbackListeners, proxyRequest, proxyRequestBody, eventBuffer) => { diff --git a/package.json b/package.json index 365e1eb13922..bee335619d24 100644 --- a/package.json +++ b/package.json @@ -31,10 +31,10 @@ "lint:biome": "biome check .", "lint:prettier": "prettier \"**/*.md\" \"**/*.css\" --check", "postpublish": "lerna run --stream --concurrency 1 postpublish", - "test": "lerna run --ignore \"@sentry-internal/{browser-integration-tests,e2e-tests,integration-shims,node-integration-tests,overhead-metrics}\" test", - "test:unit": "lerna run --ignore \"@sentry-internal/{browser-integration-tests,e2e-tests,integration-shims,node-integration-tests,overhead-metrics}\" test:unit", + "test": "lerna run --ignore \"@sentry-internal/{browser-integration-tests,e2e-tests,integration-shims,node-integration-tests}\" test", + "test:unit": "lerna run --ignore \"@sentry-internal/{browser-integration-tests,e2e-tests,integration-shims,node-integration-tests}\" test:unit", "test:update-snapshots": "lerna run test:update-snapshots", - "test:pr": "nx affected -t test --exclude \"@sentry-internal/{browser-integration-tests,e2e-tests,integration-shims,node-integration-tests,overhead-metrics}\"", + "test:pr": "nx affected -t test --exclude \"@sentry-internal/{browser-integration-tests,e2e-tests,integration-shims,node-integration-tests}\"", "test:pr:browser": "UNIT_TEST_ENV=browser ts-node ./scripts/ci-unit-tests.ts --affected", "test:pr:node": "UNIT_TEST_ENV=node ts-node ./scripts/ci-unit-tests.ts --affected", "test:ci:browser": "UNIT_TEST_ENV=browser ts-node ./scripts/ci-unit-tests.ts", @@ -88,7 +88,6 @@ "dev-packages/bundle-analyzer-scenarios", "dev-packages/e2e-tests", "dev-packages/node-integration-tests", - "dev-packages/overhead-metrics", "dev-packages/test-utils", "dev-packages/size-limit-gh-action", "dev-packages/clear-cache-gh-action", @@ -106,8 +105,8 @@ "@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-typescript": "^11.1.6", "@rollup/pluginutils": "^5.1.0", - "@size-limit/file": "~11.1.0", - "@size-limit/webpack": "~11.1.0", + "@size-limit/file": "~11.1.6", + "@size-limit/webpack": "~11.1.6", "@strictsoftware/typedoc-plugin-monorepo": "^0.3.1", "@types/jest": "^27.4.1", "@types/jsdom": "^21.1.6", @@ -125,10 +124,10 @@ "npm-run-all2": "^6.2.0", "prettier": "^3.1.1", "rimraf": "^3.0.2", - "rollup": "^4.13.0", + "rollup": "^4.24.2", "rollup-plugin-cleanup": "^3.2.1", "rollup-plugin-license": "^3.3.1", - "size-limit": "~11.1.0", + "size-limit": "~11.1.6", "sucrase": "^3.35.0", "ts-jest": "^27.1.4", "ts-node": "10.9.1", @@ -154,6 +153,12 @@ "printWidth": 120, "proseWrap": "always", "singleQuote": true, - "trailingComma": "all" + "trailingComma": "all", + "overrides": [{ + "files": "CHANGELOG.md", + "options": { + "proseWrap": "preserve" + } + }] } } diff --git a/packages/astro/package.json b/packages/astro/package.json index 8b53f185c7e7..480e883f801d 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -61,7 +61,7 @@ "@sentry/node": "8.35.0", "@sentry/types": "8.35.0", "@sentry/utils": "8.35.0", - "@sentry/vite-plugin": "^2.22.3" + "@sentry/vite-plugin": "^2.22.6" }, "devDependencies": { "astro": "^3.5.0", diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts index dacb42643b99..b544b71087fc 100644 --- a/packages/astro/src/index.server.ts +++ b/packages/astro/src/index.server.ts @@ -89,6 +89,7 @@ export { parameterize, postgresIntegration, prismaIntegration, + processThreadBreadcrumbIntegration, redisIntegration, requestDataIntegration, rewriteFramesIntegration, diff --git a/packages/aws-serverless/package.json b/packages/aws-serverless/package.json index 76973f30944e..7c9e716df177 100644 --- a/packages/aws-serverless/package.json +++ b/packages/aws-serverless/package.json @@ -65,7 +65,7 @@ }, "dependencies": { "@opentelemetry/instrumentation-aws-lambda": "0.44.0", - "@opentelemetry/instrumentation-aws-sdk": "0.44.0", + "@opentelemetry/instrumentation-aws-sdk": "0.45.0", "@sentry/core": "8.35.0", "@sentry/node": "8.35.0", "@sentry/types": "8.35.0", diff --git a/packages/aws-serverless/src/index.ts b/packages/aws-serverless/src/index.ts index ee70e8956c6f..cc7f783c40fd 100644 --- a/packages/aws-serverless/src/index.ts +++ b/packages/aws-serverless/src/index.ts @@ -102,6 +102,7 @@ export { setupNestErrorHandler, postgresIntegration, prismaIntegration, + processThreadBreadcrumbIntegration, hapiIntegration, setupHapiErrorHandler, spotlightIntegration, diff --git a/packages/core/src/baseclient.ts b/packages/core/src/baseclient.ts index c7a26f45ab70..6071f644c37c 100644 --- a/packages/core/src/baseclient.ts +++ b/packages/core/src/baseclient.ts @@ -565,7 +565,7 @@ export abstract class BaseClient implements Client { if (this._isEnabled() && this._transport) { return this._transport.send(envelope).then(null, reason => { - DEBUG_BUILD && logger.error('Error while sending event:', reason); + DEBUG_BUILD && logger.error('Error while sending envelope:', reason); return reason; }); } diff --git a/packages/core/src/tracing/sentrySpan.ts b/packages/core/src/tracing/sentrySpan.ts index 7e1083142314..54f59386ff17 100644 --- a/packages/core/src/tracing/sentrySpan.ts +++ b/packages/core/src/tracing/sentrySpan.ts @@ -429,10 +429,7 @@ function sendSpanEnvelope(envelope: SpanEnvelope): void { return; } - const transport = client.getTransport(); - if (transport) { - transport.send(envelope).then(null, reason => { - DEBUG_BUILD && logger.error('Error while sending span:', reason); - }); - } + // sendEnvelope should not throw + // eslint-disable-next-line @typescript-eslint/no-floating-promises + client.sendEnvelope(envelope); } diff --git a/packages/ember/package.json b/packages/ember/package.json index d75df71f7861..c198fa9af1cc 100644 --- a/packages/ember/package.json +++ b/packages/ember/package.json @@ -80,7 +80,7 @@ "qunit": "~2.19.2", "qunit-dom": "~2.0.0", "sinon": "15.2.0", - "webpack": "~5.94.0" + "webpack": "~5.95.0" }, "engines": { "node": ">=14.18" diff --git a/packages/gatsby/package.json b/packages/gatsby/package.json index ad231e84fb1a..95de9536cce3 100644 --- a/packages/gatsby/package.json +++ b/packages/gatsby/package.json @@ -49,7 +49,7 @@ "@sentry/react": "8.35.0", "@sentry/types": "8.35.0", "@sentry/utils": "8.35.0", - "@sentry/webpack-plugin": "2.22.3" + "@sentry/webpack-plugin": "2.22.6" }, "peerDependencies": { "gatsby": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", diff --git a/packages/google-cloud-serverless/package.json b/packages/google-cloud-serverless/package.json index 084aa77101fd..90ee6a85725b 100644 --- a/packages/google-cloud-serverless/package.json +++ b/packages/google-cloud-serverless/package.json @@ -61,7 +61,7 @@ "@google-cloud/pubsub": "^2.5.0", "@types/node": "^14.18.0", "google-gax": "^2.9.0", - "nock": "^13.0.4" + "nock": "^13.5.5" }, "scripts": { "build": "run-p build:transpile build:types", diff --git a/packages/google-cloud-serverless/src/index.ts b/packages/google-cloud-serverless/src/index.ts index bda96f062966..80dba64cef97 100644 --- a/packages/google-cloud-serverless/src/index.ts +++ b/packages/google-cloud-serverless/src/index.ts @@ -114,6 +114,7 @@ export { zodErrorsIntegration, profiler, amqplibIntegration, + processThreadBreadcrumbIntegration, } from '@sentry/node'; export { diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index dbdd89f2b710..66032b6ecc52 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -76,6 +76,7 @@ "access": "public" }, "dependencies": { + "@opentelemetry/api": "^1.9.0", "@opentelemetry/instrumentation-http": "0.53.0", "@opentelemetry/semantic-conventions": "^1.27.0", "@rollup/plugin-commonjs": "26.0.1", @@ -87,7 +88,7 @@ "@sentry/types": "8.35.0", "@sentry/utils": "8.35.0", "@sentry/vercel-edge": "8.35.0", - "@sentry/webpack-plugin": "2.22.3", + "@sentry/webpack-plugin": "2.22.6", "chalk": "3.0.0", "resolve": "1.22.8", "rollup": "3.29.5", @@ -99,13 +100,7 @@ "next": "13.2.0" }, "peerDependencies": { - "next": "^13.2.0 || ^14.0 || ^15.0.0-rc.0", - "webpack": ">=5.0.0" - }, - "peerDependenciesMeta": { - "webpack": { - "optional": true - } + "next": "^13.2.0 || ^14.0 || ^15.0.0-rc.0" }, "scripts": { "build": "run-p build:transpile build:types", diff --git a/packages/nextjs/src/client/index.ts b/packages/nextjs/src/client/index.ts index c66f50a293f2..c50bbce37305 100644 --- a/packages/nextjs/src/client/index.ts +++ b/packages/nextjs/src/client/index.ts @@ -13,7 +13,7 @@ import { applyTunnelRouteOption } from './tunnelRoute'; export * from '@sentry/react'; -export { captureUnderscoreErrorException } from '../common/_error'; +export { captureUnderscoreErrorException } from '../common/pages-router-instrumentation/_error'; const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & { __rewriteFramesAssetPrefixPath__: string; diff --git a/packages/nextjs/src/client/routing/pagesRouterRoutingInstrumentation.ts b/packages/nextjs/src/client/routing/pagesRouterRoutingInstrumentation.ts index f6906a566050..2380b743cced 100644 --- a/packages/nextjs/src/client/routing/pagesRouterRoutingInstrumentation.ts +++ b/packages/nextjs/src/client/routing/pagesRouterRoutingInstrumentation.ts @@ -6,7 +6,7 @@ import { } from '@sentry/core'; import { WINDOW, startBrowserTracingNavigationSpan, startBrowserTracingPageLoadSpan } from '@sentry/react'; import type { Client, TransactionSource } from '@sentry/types'; -import { browserPerformanceTimeOrigin, logger, stripUrlQueryAndFragment } from '@sentry/utils'; +import { browserPerformanceTimeOrigin, logger, parseBaggageHeader, stripUrlQueryAndFragment } from '@sentry/utils'; import type { NEXT_DATA } from 'next/dist/shared/lib/utils'; import RouterImport from 'next/router'; @@ -106,7 +106,15 @@ function extractNextDataTagInformation(): NextDataTagInfo { */ export function pagesRouterInstrumentPageLoad(client: Client): void { const { route, params, sentryTrace, baggage } = extractNextDataTagInformation(); - const name = route || globalObject.location.pathname; + const parsedBaggage = parseBaggageHeader(baggage); + let name = route || globalObject.location.pathname; + + // /_error is the fallback page for all errors. If there is a transaction name for /_error, use that instead + if (parsedBaggage && parsedBaggage['sentry-transaction'] && name === '/_error') { + name = parsedBaggage['sentry-transaction']; + // Strip any HTTP method from the span name + name = name.replace(/^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS|TRACE|CONNECT)\s+/i, ''); + } startBrowserTracingPageLoadSpan( client, diff --git a/packages/nextjs/src/common/index.ts b/packages/nextjs/src/common/index.ts index 354113637a30..7740c35c016c 100644 --- a/packages/nextjs/src/common/index.ts +++ b/packages/nextjs/src/common/index.ts @@ -1,14 +1,14 @@ -export { wrapGetStaticPropsWithSentry } from './wrapGetStaticPropsWithSentry'; -export { wrapGetInitialPropsWithSentry } from './wrapGetInitialPropsWithSentry'; -export { wrapAppGetInitialPropsWithSentry } from './wrapAppGetInitialPropsWithSentry'; -export { wrapDocumentGetInitialPropsWithSentry } from './wrapDocumentGetInitialPropsWithSentry'; -export { wrapErrorGetInitialPropsWithSentry } from './wrapErrorGetInitialPropsWithSentry'; -export { wrapGetServerSidePropsWithSentry } from './wrapGetServerSidePropsWithSentry'; +export { wrapGetStaticPropsWithSentry } from './pages-router-instrumentation/wrapGetStaticPropsWithSentry'; +export { wrapGetInitialPropsWithSentry } from './pages-router-instrumentation/wrapGetInitialPropsWithSentry'; +export { wrapAppGetInitialPropsWithSentry } from './pages-router-instrumentation/wrapAppGetInitialPropsWithSentry'; +export { wrapDocumentGetInitialPropsWithSentry } from './pages-router-instrumentation/wrapDocumentGetInitialPropsWithSentry'; +export { wrapErrorGetInitialPropsWithSentry } from './pages-router-instrumentation/wrapErrorGetInitialPropsWithSentry'; +export { wrapGetServerSidePropsWithSentry } from './pages-router-instrumentation/wrapGetServerSidePropsWithSentry'; export { wrapServerComponentWithSentry } from './wrapServerComponentWithSentry'; export { wrapRouteHandlerWithSentry } from './wrapRouteHandlerWithSentry'; -export { wrapApiHandlerWithSentryVercelCrons } from './wrapApiHandlerWithSentryVercelCrons'; +export { wrapApiHandlerWithSentryVercelCrons } from './pages-router-instrumentation/wrapApiHandlerWithSentryVercelCrons'; export { wrapMiddlewareWithSentry } from './wrapMiddlewareWithSentry'; -export { wrapPageComponentWithSentry } from './wrapPageComponentWithSentry'; +export { wrapPageComponentWithSentry } from './pages-router-instrumentation/wrapPageComponentWithSentry'; export { wrapGenerationFunctionWithSentry } from './wrapGenerationFunctionWithSentry'; export { withServerActionInstrumentation } from './withServerActionInstrumentation'; // eslint-disable-next-line deprecation/deprecation diff --git a/packages/nextjs/src/common/_error.ts b/packages/nextjs/src/common/pages-router-instrumentation/_error.ts similarity index 97% rename from packages/nextjs/src/common/_error.ts rename to packages/nextjs/src/common/pages-router-instrumentation/_error.ts index 385df8244a17..3450aad8ef5e 100644 --- a/packages/nextjs/src/common/_error.ts +++ b/packages/nextjs/src/common/pages-router-instrumentation/_error.ts @@ -1,7 +1,7 @@ import { captureException, withScope } from '@sentry/core'; import { vercelWaitUntil } from '@sentry/utils'; import type { NextPageContext } from 'next'; -import { flushSafelyWithTimeout } from './utils/responseEnd'; +import { flushSafelyWithTimeout } from '../utils/responseEnd'; type ContextOrProps = { req?: NextPageContext['req']; diff --git a/packages/nextjs/src/common/wrapApiHandlerWithSentry.ts b/packages/nextjs/src/common/pages-router-instrumentation/wrapApiHandlerWithSentry.ts similarity index 89% rename from packages/nextjs/src/common/wrapApiHandlerWithSentry.ts rename to packages/nextjs/src/common/pages-router-instrumentation/wrapApiHandlerWithSentry.ts index cef85c320ae0..30fce67e482e 100644 --- a/packages/nextjs/src/common/wrapApiHandlerWithSentry.ts +++ b/packages/nextjs/src/common/pages-router-instrumentation/wrapApiHandlerWithSentry.ts @@ -1,4 +1,5 @@ import { + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, captureException, continueTrace, @@ -6,12 +7,13 @@ import { startSpanManual, withIsolationScope, } from '@sentry/core'; -import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core'; -import { isString, logger, objectify, vercelWaitUntil } from '@sentry/utils'; +import { isString, logger, objectify } from '@sentry/utils'; + +import { vercelWaitUntil } from '@sentry/utils'; import type { NextApiRequest } from 'next'; -import type { AugmentedNextApiResponse, NextApiHandler } from './types'; -import { flushSafelyWithTimeout } from './utils/responseEnd'; -import { escapeNextjsTracing } from './utils/tracingUtils'; +import type { AugmentedNextApiResponse, NextApiHandler } from '../types'; +import { flushSafelyWithTimeout } from '../utils/responseEnd'; +import { dropNextjsRootContext, escapeNextjsTracing } from '../utils/tracingUtils'; export type AugmentedNextApiRequest = NextApiRequest & { __withSentry_applied__?: boolean; @@ -32,6 +34,7 @@ export function wrapApiHandlerWithSentry(apiHandler: NextApiHandler, parameteriz thisArg, args: [AugmentedNextApiRequest | undefined, AugmentedNextApiResponse | undefined], ) => { + dropNextjsRootContext(); return escapeNextjsTracing(() => { const [req, res] = args; @@ -86,7 +89,6 @@ export function wrapApiHandlerWithSentry(apiHandler: NextApiHandler, parameteriz return target.apply(thisArg, argArray); }, }); - try { return await wrappingTarget.apply(thisArg, args); } catch (e) { @@ -110,7 +112,9 @@ export function wrapApiHandlerWithSentry(apiHandler: NextApiHandler, parameteriz setHttpStatus(span, 500); span.end(); - vercelWaitUntil(flushSafelyWithTimeout()); + // we need to await the flush here to ensure that the error is captured + // as the runtime freezes as soon as the error is thrown below + await flushSafelyWithTimeout(); // We rethrow here so that nextjs can do with the error whatever it would normally do. (Sometimes "whatever it // would normally do" is to allow the error to bubble up to the global handlers - another reason we need to mark diff --git a/packages/nextjs/src/common/wrapApiHandlerWithSentryVercelCrons.ts b/packages/nextjs/src/common/pages-router-instrumentation/wrapApiHandlerWithSentryVercelCrons.ts similarity index 96% rename from packages/nextjs/src/common/wrapApiHandlerWithSentryVercelCrons.ts rename to packages/nextjs/src/common/pages-router-instrumentation/wrapApiHandlerWithSentryVercelCrons.ts index 4974cd827e9a..5ca29b338cda 100644 --- a/packages/nextjs/src/common/wrapApiHandlerWithSentryVercelCrons.ts +++ b/packages/nextjs/src/common/pages-router-instrumentation/wrapApiHandlerWithSentryVercelCrons.ts @@ -1,7 +1,7 @@ import { captureCheckIn } from '@sentry/core'; import type { NextApiRequest } from 'next'; -import type { VercelCronsConfig } from './types'; +import type { VercelCronsConfig } from '../types'; type EdgeRequest = { nextUrl: URL; @@ -9,7 +9,7 @@ type EdgeRequest = { }; /** - * Wraps a function with Sentry crons instrumentation by automaticaly sending check-ins for the given Vercel crons config. + * Wraps a function with Sentry crons instrumentation by automatically sending check-ins for the given Vercel crons config. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any export function wrapApiHandlerWithSentryVercelCrons any>( diff --git a/packages/nextjs/src/common/wrapAppGetInitialPropsWithSentry.ts b/packages/nextjs/src/common/pages-router-instrumentation/wrapAppGetInitialPropsWithSentry.ts similarity index 97% rename from packages/nextjs/src/common/wrapAppGetInitialPropsWithSentry.ts rename to packages/nextjs/src/common/pages-router-instrumentation/wrapAppGetInitialPropsWithSentry.ts index 2c7b0adc7d7b..10f783b9e9e6 100644 --- a/packages/nextjs/src/common/wrapAppGetInitialPropsWithSentry.ts +++ b/packages/nextjs/src/common/pages-router-instrumentation/wrapAppGetInitialPropsWithSentry.ts @@ -1,7 +1,7 @@ import type App from 'next/app'; -import { isBuild } from './utils/isBuild'; -import { withErrorInstrumentation, withTracedServerSideDataFetcher } from './utils/wrapperUtils'; +import { isBuild } from '../utils/isBuild'; +import { withErrorInstrumentation, withTracedServerSideDataFetcher } from '../utils/wrapperUtils'; type AppGetInitialProps = (typeof App)['getInitialProps']; diff --git a/packages/nextjs/src/common/wrapDocumentGetInitialPropsWithSentry.ts b/packages/nextjs/src/common/pages-router-instrumentation/wrapDocumentGetInitialPropsWithSentry.ts similarity index 96% rename from packages/nextjs/src/common/wrapDocumentGetInitialPropsWithSentry.ts rename to packages/nextjs/src/common/pages-router-instrumentation/wrapDocumentGetInitialPropsWithSentry.ts index 192e70f093b1..d7f69c621132 100644 --- a/packages/nextjs/src/common/wrapDocumentGetInitialPropsWithSentry.ts +++ b/packages/nextjs/src/common/pages-router-instrumentation/wrapDocumentGetInitialPropsWithSentry.ts @@ -1,7 +1,7 @@ import type Document from 'next/document'; -import { isBuild } from './utils/isBuild'; -import { withErrorInstrumentation, withTracedServerSideDataFetcher } from './utils/wrapperUtils'; +import { isBuild } from '../utils/isBuild'; +import { withErrorInstrumentation, withTracedServerSideDataFetcher } from '../utils/wrapperUtils'; type DocumentGetInitialProps = typeof Document.getInitialProps; diff --git a/packages/nextjs/src/common/wrapErrorGetInitialPropsWithSentry.ts b/packages/nextjs/src/common/pages-router-instrumentation/wrapErrorGetInitialPropsWithSentry.ts similarity index 97% rename from packages/nextjs/src/common/wrapErrorGetInitialPropsWithSentry.ts rename to packages/nextjs/src/common/pages-router-instrumentation/wrapErrorGetInitialPropsWithSentry.ts index a2bd559342a4..731d3fe1e24a 100644 --- a/packages/nextjs/src/common/wrapErrorGetInitialPropsWithSentry.ts +++ b/packages/nextjs/src/common/pages-router-instrumentation/wrapErrorGetInitialPropsWithSentry.ts @@ -1,8 +1,8 @@ import type { NextPageContext } from 'next'; import type { ErrorProps } from 'next/error'; -import { isBuild } from './utils/isBuild'; -import { withErrorInstrumentation, withTracedServerSideDataFetcher } from './utils/wrapperUtils'; +import { isBuild } from '../utils/isBuild'; +import { withErrorInstrumentation, withTracedServerSideDataFetcher } from '../utils/wrapperUtils'; type ErrorGetInitialProps = (context: NextPageContext) => Promise; diff --git a/packages/nextjs/src/common/wrapGetInitialPropsWithSentry.ts b/packages/nextjs/src/common/pages-router-instrumentation/wrapGetInitialPropsWithSentry.ts similarity index 96% rename from packages/nextjs/src/common/wrapGetInitialPropsWithSentry.ts rename to packages/nextjs/src/common/pages-router-instrumentation/wrapGetInitialPropsWithSentry.ts index 2624aefb4d24..97246ec9d122 100644 --- a/packages/nextjs/src/common/wrapGetInitialPropsWithSentry.ts +++ b/packages/nextjs/src/common/pages-router-instrumentation/wrapGetInitialPropsWithSentry.ts @@ -1,7 +1,7 @@ import type { NextPage } from 'next'; -import { isBuild } from './utils/isBuild'; -import { withErrorInstrumentation, withTracedServerSideDataFetcher } from './utils/wrapperUtils'; +import { isBuild } from '../utils/isBuild'; +import { withErrorInstrumentation, withTracedServerSideDataFetcher } from '../utils/wrapperUtils'; type GetInitialProps = Required['getInitialProps']; diff --git a/packages/nextjs/src/common/wrapGetServerSidePropsWithSentry.ts b/packages/nextjs/src/common/pages-router-instrumentation/wrapGetServerSidePropsWithSentry.ts similarity index 96% rename from packages/nextjs/src/common/wrapGetServerSidePropsWithSentry.ts rename to packages/nextjs/src/common/pages-router-instrumentation/wrapGetServerSidePropsWithSentry.ts index 0037bad36300..7c4b4101d80e 100644 --- a/packages/nextjs/src/common/wrapGetServerSidePropsWithSentry.ts +++ b/packages/nextjs/src/common/pages-router-instrumentation/wrapGetServerSidePropsWithSentry.ts @@ -1,7 +1,7 @@ import type { GetServerSideProps } from 'next'; -import { isBuild } from './utils/isBuild'; -import { withErrorInstrumentation, withTracedServerSideDataFetcher } from './utils/wrapperUtils'; +import { isBuild } from '../utils/isBuild'; +import { withErrorInstrumentation, withTracedServerSideDataFetcher } from '../utils/wrapperUtils'; /** * Create a wrapped version of the user's exported `getServerSideProps` function diff --git a/packages/nextjs/src/common/wrapGetStaticPropsWithSentry.ts b/packages/nextjs/src/common/pages-router-instrumentation/wrapGetStaticPropsWithSentry.ts similarity index 82% rename from packages/nextjs/src/common/wrapGetStaticPropsWithSentry.ts rename to packages/nextjs/src/common/pages-router-instrumentation/wrapGetStaticPropsWithSentry.ts index aebbf42ac684..5d083eb97ca8 100644 --- a/packages/nextjs/src/common/wrapGetStaticPropsWithSentry.ts +++ b/packages/nextjs/src/common/pages-router-instrumentation/wrapGetStaticPropsWithSentry.ts @@ -1,7 +1,7 @@ import type { GetStaticProps } from 'next'; -import { isBuild } from './utils/isBuild'; -import { callDataFetcherTraced, withErrorInstrumentation } from './utils/wrapperUtils'; +import { isBuild } from '../utils/isBuild'; +import { callDataFetcherTraced, withErrorInstrumentation } from '../utils/wrapperUtils'; type Props = { [key: string]: unknown }; @@ -14,7 +14,7 @@ type Props = { [key: string]: unknown }; */ export function wrapGetStaticPropsWithSentry( origGetStaticPropsa: GetStaticProps, - parameterizedRoute: string, + _parameterizedRoute: string, ): GetStaticProps { return new Proxy(origGetStaticPropsa, { apply: async (wrappingTarget, thisArg, args: Parameters>) => { @@ -23,10 +23,7 @@ export function wrapGetStaticPropsWithSentry( } const errorWrappedGetStaticProps = withErrorInstrumentation(wrappingTarget); - return callDataFetcherTraced(errorWrappedGetStaticProps, args, { - parameterizedRoute, - dataFetchingMethodName: 'getStaticProps', - }); + return callDataFetcherTraced(errorWrappedGetStaticProps, args); }, }); } diff --git a/packages/nextjs/src/common/pages-router-instrumentation/wrapPageComponentWithSentry.ts b/packages/nextjs/src/common/pages-router-instrumentation/wrapPageComponentWithSentry.ts new file mode 100644 index 000000000000..8b6a45faa63b --- /dev/null +++ b/packages/nextjs/src/common/pages-router-instrumentation/wrapPageComponentWithSentry.ts @@ -0,0 +1,91 @@ +import { captureException, getCurrentScope, withIsolationScope } from '@sentry/core'; +import { extractTraceparentData } from '@sentry/utils'; + +interface FunctionComponent { + (...args: unknown[]): unknown; +} + +interface ClassComponent { + new (...args: unknown[]): { + props?: unknown; + render(...args: unknown[]): unknown; + }; +} + +function isReactClassComponent(target: unknown): target is ClassComponent { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + return typeof target === 'function' && target?.prototype?.isReactComponent; +} + +/** + * Wraps a page component with Sentry error instrumentation. + */ +export function wrapPageComponentWithSentry(pageComponent: FunctionComponent | ClassComponent): unknown { + if (isReactClassComponent(pageComponent)) { + return class SentryWrappedPageComponent extends pageComponent { + public render(...args: unknown[]): unknown { + return withIsolationScope(() => { + const scope = getCurrentScope(); + // We extract the sentry trace data that is put in the component props by datafetcher wrappers + const sentryTraceData = + typeof this.props === 'object' && + this.props !== null && + '_sentryTraceData' in this.props && + typeof this.props._sentryTraceData === 'string' + ? this.props._sentryTraceData + : undefined; + + if (sentryTraceData) { + const traceparentData = extractTraceparentData(sentryTraceData); + scope.setContext('trace', { + span_id: traceparentData?.parentSpanId, + trace_id: traceparentData?.traceId, + }); + } + + try { + return super.render(...args); + } catch (e) { + captureException(e, { + mechanism: { + handled: false, + }, + }); + throw e; + } + }); + } + }; + } else if (typeof pageComponent === 'function') { + return new Proxy(pageComponent, { + apply(target, thisArg, argArray: [{ _sentryTraceData?: string } | undefined]) { + return withIsolationScope(() => { + const scope = getCurrentScope(); + // We extract the sentry trace data that is put in the component props by datafetcher wrappers + const sentryTraceData = argArray?.[0]?._sentryTraceData; + + if (sentryTraceData) { + const traceparentData = extractTraceparentData(sentryTraceData); + scope.setContext('trace', { + span_id: traceparentData?.parentSpanId, + trace_id: traceparentData?.traceId, + }); + } + + try { + return target.apply(thisArg, argArray); + } catch (e) { + captureException(e, { + mechanism: { + handled: false, + }, + }); + throw e; + } + }); + }, + }); + } else { + return pageComponent; + } +} diff --git a/packages/nextjs/src/common/span-attributes-with-logic-attached.ts b/packages/nextjs/src/common/span-attributes-with-logic-attached.ts new file mode 100644 index 000000000000..a272ef525dff --- /dev/null +++ b/packages/nextjs/src/common/span-attributes-with-logic-attached.ts @@ -0,0 +1,8 @@ +/** + * If this attribute is attached to a transaction, the Next.js SDK will drop that transaction. + */ +export const TRANSACTION_ATTR_SHOULD_DROP_TRANSACTION = 'sentry.drop_transaction'; + +export const TRANSACTION_ATTR_SENTRY_TRACE_BACKFILL = 'sentry.sentry_trace_backfill'; + +export const TRANSACTION_ATTR_SENTRY_ROUTE_BACKFILL = 'sentry.route_backfill'; diff --git a/packages/nextjs/src/common/utils/edgeWrapperUtils.ts b/packages/nextjs/src/common/utils/edgeWrapperUtils.ts deleted file mode 100644 index 5eed59aca0a3..000000000000 --- a/packages/nextjs/src/common/utils/edgeWrapperUtils.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { - SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, - SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, - SPAN_STATUS_OK, - captureException, - continueTrace, - handleCallbackErrors, - setHttpStatus, - startSpan, - withIsolationScope, -} from '@sentry/core'; -import { vercelWaitUntil, winterCGRequestToRequestData } from '@sentry/utils'; - -import type { EdgeRouteHandler } from '../../edge/types'; -import { flushSafelyWithTimeout } from './responseEnd'; -import { commonObjectToIsolationScope, escapeNextjsTracing } from './tracingUtils'; - -/** - * Wraps a function on the edge runtime with error and performance monitoring. - */ -export function withEdgeWrapping( - handler: H, - options: { spanDescription: string; spanOp: string; mechanismFunctionName: string }, -): (...params: Parameters) => Promise> { - return async function (this: unknown, ...args) { - return escapeNextjsTracing(() => { - const req: unknown = args[0]; - return withIsolationScope(commonObjectToIsolationScope(req), isolationScope => { - let sentryTrace; - let baggage; - - if (req instanceof Request) { - sentryTrace = req.headers.get('sentry-trace') || ''; - baggage = req.headers.get('baggage'); - - isolationScope.setSDKProcessingMetadata({ - request: winterCGRequestToRequestData(req), - }); - } - - isolationScope.setTransactionName(options.spanDescription); - - return continueTrace( - { - sentryTrace, - baggage, - }, - () => { - return startSpan( - { - name: options.spanDescription, - op: options.spanOp, - forceTransaction: true, - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs.withEdgeWrapping', - }, - }, - async span => { - const handlerResult = await handleCallbackErrors( - () => handler.apply(this, args), - error => { - captureException(error, { - mechanism: { - type: 'instrument', - handled: false, - data: { - function: options.mechanismFunctionName, - }, - }, - }); - }, - ); - - if (handlerResult instanceof Response) { - setHttpStatus(span, handlerResult.status); - } else { - span.setStatus({ code: SPAN_STATUS_OK }); - } - - return handlerResult; - }, - ); - }, - ).finally(() => { - vercelWaitUntil(flushSafelyWithTimeout()); - }); - }); - }); - }; -} diff --git a/packages/nextjs/src/common/utils/tracingUtils.ts b/packages/nextjs/src/common/utils/tracingUtils.ts index b996b6af1877..ff57fcae3acc 100644 --- a/packages/nextjs/src/common/utils/tracingUtils.ts +++ b/packages/nextjs/src/common/utils/tracingUtils.ts @@ -1,7 +1,8 @@ -import { Scope, startNewTrace } from '@sentry/core'; +import { Scope, getActiveSpan, getRootSpan, spanToJSON, startNewTrace } from '@sentry/core'; import type { PropagationContext } from '@sentry/types'; import { GLOBAL_OBJ, logger } from '@sentry/utils'; import { DEBUG_BUILD } from '../debug-build'; +import { TRANSACTION_ATTR_SHOULD_DROP_TRANSACTION } from '../span-attributes-with-logic-attached'; const commonPropagationContextMap = new WeakMap(); @@ -92,3 +93,19 @@ export function escapeNextjsTracing(cb: () => T): T { }); } } + +/** + * Ideally this function never lands in the develop branch. + * + * Drops the entire span tree this function was called in, if it was a span tree created by Next.js. + */ +export function dropNextjsRootContext(): void { + const nextJsOwnedSpan = getActiveSpan(); + if (nextJsOwnedSpan) { + const rootSpan = getRootSpan(nextJsOwnedSpan); + const rootSpanAttributes = spanToJSON(rootSpan).data; + if (rootSpanAttributes?.['next.span_type']) { + getRootSpan(nextJsOwnedSpan)?.setAttribute(TRANSACTION_ATTR_SHOULD_DROP_TRANSACTION, true); + } + } +} diff --git a/packages/nextjs/src/common/utils/wrapperUtils.ts b/packages/nextjs/src/common/utils/wrapperUtils.ts index ff04aebbd3ed..159ee669ec09 100644 --- a/packages/nextjs/src/common/utils/wrapperUtils.ts +++ b/packages/nextjs/src/common/utils/wrapperUtils.ts @@ -1,44 +1,13 @@ import type { IncomingMessage, ServerResponse } from 'http'; import { - SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, - SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, - SPAN_STATUS_ERROR, - SPAN_STATUS_OK, captureException, - continueTrace, + getActiveSpan, + getCurrentScope, + getIsolationScope, + getRootSpan, getTraceData, - startInactiveSpan, - startSpan, - startSpanManual, - withActiveSpan, - withIsolationScope, } from '@sentry/core'; -import type { Span } from '@sentry/types'; -import { isString, vercelWaitUntil } from '@sentry/utils'; - -import { autoEndSpanOnResponseEnd, flushSafelyWithTimeout } from './responseEnd'; -import { commonObjectToIsolationScope, escapeNextjsTracing } from './tracingUtils'; - -declare module 'http' { - interface IncomingMessage { - _sentrySpan?: Span; - } -} - -/** - * Grabs a span off a Next.js datafetcher request object, if it was previously put there via - * `setSpanOnRequest`. - * - * @param req The Next.js datafetcher request object - * @returns the span on the request object if there is one, or `undefined` if the request object didn't have one. - */ -export function getSpanFromRequest(req: IncomingMessage): Span | undefined { - return req._sentrySpan; -} - -function setSpanOnRequest(span: Span, req: IncomingMessage): void { - req._sentrySpan = span; -} +import { TRANSACTION_ATTR_SENTRY_ROUTE_BACKFILL } from '../span-attributes-with-logic-attached'; /** * Wraps a function that potentially throws. If it does, the error is passed to `captureException` and rethrown. @@ -55,7 +24,6 @@ export function withErrorInstrumentation any>( } catch (e) { // TODO: Extract error logic from `withSentry` in here or create a new wrapper with said logic or something like that. captureException(e, { mechanism: { handled: false } }); - throw e; } }; @@ -93,78 +61,27 @@ export function withTracedServerSideDataFetcher Pr this: unknown, ...args: Parameters ): Promise<{ data: ReturnType; sentryTrace?: string; baggage?: string }> { - return escapeNextjsTracing(() => { - const isolationScope = commonObjectToIsolationScope(req); - return withIsolationScope(isolationScope, () => { - isolationScope.setTransactionName(`${options.dataFetchingMethodName} (${options.dataFetcherRouteName})`); - isolationScope.setSDKProcessingMetadata({ - request: req, - }); - - const sentryTrace = - req.headers && isString(req.headers['sentry-trace']) ? req.headers['sentry-trace'] : undefined; - const baggage = req.headers?.baggage; - - return continueTrace({ sentryTrace, baggage }, () => { - const requestSpan = getOrStartRequestSpan(req, res, options.requestedRouteName); - return withActiveSpan(requestSpan, () => { - return startSpanManual( - { - op: 'function.nextjs', - name: `${options.dataFetchingMethodName} (${options.dataFetcherRouteName})`, - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs', - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - }, - }, - async dataFetcherSpan => { - dataFetcherSpan.setStatus({ code: SPAN_STATUS_OK }); - const { 'sentry-trace': sentryTrace, baggage } = getTraceData(); - try { - return { - sentryTrace: sentryTrace, - baggage: baggage, - data: await origDataFetcher.apply(this, args), - }; - } catch (e) { - dataFetcherSpan.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); - requestSpan?.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); - throw e; - } finally { - dataFetcherSpan.end(); - } - }, - ); - }); - }); - }); - }).finally(() => { - vercelWaitUntil(flushSafelyWithTimeout()); + getCurrentScope().setTransactionName(`${options.dataFetchingMethodName} (${options.dataFetcherRouteName})`); + getIsolationScope().setSDKProcessingMetadata({ + request: req, }); - }; -} -function getOrStartRequestSpan(req: IncomingMessage, res: ServerResponse, name: string): Span { - const existingSpan = getSpanFromRequest(req); - if (existingSpan) { - return existingSpan; - } + const span = getActiveSpan(); - const requestSpan = startInactiveSpan({ - name, - forceTransaction: true, - op: 'http.server', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs', - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - }, - }); + // Only set the route backfill if the span is not for /_error + if (span && options.requestedRouteName !== '/_error') { + const root = getRootSpan(span); + root.setAttribute(TRANSACTION_ATTR_SENTRY_ROUTE_BACKFILL, options.requestedRouteName); + } - requestSpan.setStatus({ code: SPAN_STATUS_OK }); - setSpanOnRequest(requestSpan, req); - autoEndSpanOnResponseEnd(requestSpan, res); + const { 'sentry-trace': sentryTrace, baggage } = getTraceData(); - return requestSpan; + return { + sentryTrace: sentryTrace, + baggage: baggage, + data: await origDataFetcher.apply(this, args), + }; + }; } /** @@ -177,37 +94,11 @@ function getOrStartRequestSpan(req: IncomingMessage, res: ServerResponse, name: export async function callDataFetcherTraced Promise | any>( origFunction: F, origFunctionArgs: Parameters, - options: { - parameterizedRoute: string; - dataFetchingMethodName: string; - }, ): Promise> { - const { parameterizedRoute, dataFetchingMethodName } = options; - - return startSpan( - { - op: 'function.nextjs', - name: `${dataFetchingMethodName} (${parameterizedRoute})`, - onlyIfParent: true, - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs', - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - }, - }, - async dataFetcherSpan => { - dataFetcherSpan.setStatus({ code: SPAN_STATUS_OK }); - - try { - return await origFunction(...origFunctionArgs); - } catch (e) { - dataFetcherSpan.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); - captureException(e, { mechanism: { handled: false } }); - throw e; - } finally { - dataFetcherSpan.end(); - } - }, - ).finally(() => { - vercelWaitUntil(flushSafelyWithTimeout()); - }); + try { + return await origFunction(...origFunctionArgs); + } catch (e) { + captureException(e, { mechanism: { handled: false } }); + throw e; + } } diff --git a/packages/nextjs/src/common/withServerActionInstrumentation.ts b/packages/nextjs/src/common/withServerActionInstrumentation.ts index 0b8d3b6d7c60..b13d3ebef3dd 100644 --- a/packages/nextjs/src/common/withServerActionInstrumentation.ts +++ b/packages/nextjs/src/common/withServerActionInstrumentation.ts @@ -1,16 +1,19 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, SPAN_STATUS_ERROR, + captureException, + continueTrace, + getClient, getIsolationScope, + handleCallbackErrors, + startSpan, withIsolationScope, } from '@sentry/core'; -import { captureException, continueTrace, getClient, handleCallbackErrors, startSpan } from '@sentry/core'; import { logger, vercelWaitUntil } from '@sentry/utils'; import { DEBUG_BUILD } from './debug-build'; import { isNotFoundNavigationError, isRedirectNavigationError } from './nextNavigationErrorUtils'; import { flushSafelyWithTimeout } from './utils/responseEnd'; -import { escapeNextjsTracing } from './utils/tracingUtils'; interface Options { formData?: FormData; @@ -64,88 +67,86 @@ async function withServerActionInstrumentationImplementation> { - return escapeNextjsTracing(() => { - return withIsolationScope(async isolationScope => { - const sendDefaultPii = getClient()?.getOptions().sendDefaultPii; + return withIsolationScope(async isolationScope => { + const sendDefaultPii = getClient()?.getOptions().sendDefaultPii; - let sentryTraceHeader; - let baggageHeader; - const fullHeadersObject: Record = {}; - try { - const awaitedHeaders: Headers = await options.headers; - sentryTraceHeader = awaitedHeaders?.get('sentry-trace') ?? undefined; - baggageHeader = awaitedHeaders?.get('baggage'); - awaitedHeaders?.forEach((value, key) => { - fullHeadersObject[key] = value; - }); - } catch (e) { - DEBUG_BUILD && - logger.warn( - "Sentry wasn't able to extract the tracing headers for a server action. Will not trace this request.", - ); - } - - isolationScope.setTransactionName(`serverAction/${serverActionName}`); - isolationScope.setSDKProcessingMetadata({ - request: { - headers: fullHeadersObject, - }, + let sentryTraceHeader; + let baggageHeader; + const fullHeadersObject: Record = {}; + try { + const awaitedHeaders: Headers = await options.headers; + sentryTraceHeader = awaitedHeaders?.get('sentry-trace') ?? undefined; + baggageHeader = awaitedHeaders?.get('baggage'); + awaitedHeaders?.forEach((value, key) => { + fullHeadersObject[key] = value; }); + } catch (e) { + DEBUG_BUILD && + logger.warn( + "Sentry wasn't able to extract the tracing headers for a server action. Will not trace this request.", + ); + } - return continueTrace( - { - sentryTrace: sentryTraceHeader, - baggage: baggageHeader, - }, - async () => { - try { - return await startSpan( - { - op: 'function.server_action', - name: `serverAction/${serverActionName}`, - forceTransaction: true, - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - }, - }, - async span => { - const result = await handleCallbackErrors(callback, error => { - if (isNotFoundNavigationError(error)) { - // We don't want to report "not-found"s - span.setStatus({ code: SPAN_STATUS_ERROR, message: 'not_found' }); - } else if (isRedirectNavigationError(error)) { - // Don't do anything for redirects - } else { - span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); - captureException(error, { - mechanism: { - handled: false, - }, - }); - } - }); - - if (options.recordResponse !== undefined ? options.recordResponse : sendDefaultPii) { - getIsolationScope().setExtra('server_action_result', result); - } + isolationScope.setTransactionName(`serverAction/${serverActionName}`); + isolationScope.setSDKProcessingMetadata({ + request: { + headers: fullHeadersObject, + }, + }); - if (options.formData) { - options.formData.forEach((value, key) => { - getIsolationScope().setExtra( - `server_action_form_data.${key}`, - typeof value === 'string' ? value : '[non-string value]', - ); + return continueTrace( + { + sentryTrace: sentryTraceHeader, + baggage: baggageHeader, + }, + async () => { + try { + return await startSpan( + { + op: 'function.server_action', + name: `serverAction/${serverActionName}`, + forceTransaction: true, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + }, + }, + async span => { + const result = await handleCallbackErrors(callback, error => { + if (isNotFoundNavigationError(error)) { + // We don't want to report "not-found"s + span.setStatus({ code: SPAN_STATUS_ERROR, message: 'not_found' }); + } else if (isRedirectNavigationError(error)) { + // Don't do anything for redirects + } else { + span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); + captureException(error, { + mechanism: { + handled: false, + }, }); } + }); - return result; - }, - ); - } finally { - vercelWaitUntil(flushSafelyWithTimeout()); - } - }, - ); - }); + if (options.recordResponse !== undefined ? options.recordResponse : sendDefaultPii) { + getIsolationScope().setExtra('server_action_result', result); + } + + if (options.formData) { + options.formData.forEach((value, key) => { + getIsolationScope().setExtra( + `server_action_form_data.${key}`, + typeof value === 'string' ? value : '[non-string value]', + ); + }); + } + + return result; + }, + ); + } finally { + vercelWaitUntil(flushSafelyWithTimeout()); + } + }, + ); }); } diff --git a/packages/nextjs/src/common/wrapGenerationFunctionWithSentry.ts b/packages/nextjs/src/common/wrapGenerationFunctionWithSentry.ts index 5944b520f6ea..5c9b2506ecee 100644 --- a/packages/nextjs/src/common/wrapGenerationFunctionWithSentry.ts +++ b/packages/nextjs/src/common/wrapGenerationFunctionWithSentry.ts @@ -20,6 +20,7 @@ import { propagationContextFromHeaders, uuid4, winterCGHeadersToDict } from '@se import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core'; import type { GenerationFunctionContext } from '../common/types'; import { isNotFoundNavigationError, isRedirectNavigationError } from './nextNavigationErrorUtils'; +import { TRANSACTION_ATTR_SENTRY_TRACE_BACKFILL } from './span-attributes-with-logic-attached'; import { commonObjectToIsolationScope, commonObjectToPropagationContext } from './utils/tracingUtils'; /** @@ -49,9 +50,6 @@ export function wrapGenerationFunctionWithSentry a const rootSpan = getRootSpan(activeSpan); const { scope } = getCapturedScopesOnSpan(rootSpan); setCapturedScopesOnSpan(rootSpan, scope ?? new Scope(), isolationScope); - - // We mark the root span as an app router span so we can allow-list it in our span processor that would normally filter out all Next.js transactions/spans - rootSpan.setAttribute('sentry.rsc', true); } let data: Record | undefined = undefined; @@ -75,6 +73,15 @@ export function wrapGenerationFunctionWithSentry a }, }); + const activeSpan = getActiveSpan(); + if (activeSpan) { + const rootSpan = getRootSpan(activeSpan); + const sentryTrace = headersDict?.['sentry-trace']; + if (sentryTrace) { + rootSpan.setAttribute(TRANSACTION_ATTR_SENTRY_TRACE_BACKFILL, sentryTrace); + } + } + const propagationContext = commonObjectToPropagationContext( headers, headersDict?.['sentry-trace'] diff --git a/packages/nextjs/src/common/wrapMiddlewareWithSentry.ts b/packages/nextjs/src/common/wrapMiddlewareWithSentry.ts index 66cbbb046300..e8b57c7d2b8b 100644 --- a/packages/nextjs/src/common/wrapMiddlewareWithSentry.ts +++ b/packages/nextjs/src/common/wrapMiddlewareWithSentry.ts @@ -1,5 +1,19 @@ +import { + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + captureException, + getActiveSpan, + getCurrentScope, + getRootSpan, + handleCallbackErrors, + setCapturedScopesOnSpan, + startSpan, + withIsolationScope, +} from '@sentry/core'; +import type { TransactionSource } from '@sentry/types'; +import { vercelWaitUntil, winterCGRequestToRequestData } from '@sentry/utils'; import type { EdgeRouteHandler } from '../edge/types'; -import { withEdgeWrapping } from './utils/edgeWrapperUtils'; +import { flushSafelyWithTimeout } from './utils/responseEnd'; /** * Wraps Next.js middleware with Sentry error and performance instrumentation. @@ -11,12 +25,69 @@ export function wrapMiddlewareWithSentry( middleware: H, ): (...params: Parameters) => Promise> { return new Proxy(middleware, { - apply: (wrappingTarget, thisArg, args: Parameters) => { - return withEdgeWrapping(wrappingTarget, { - spanDescription: 'middleware', - spanOp: 'middleware.nextjs', - mechanismFunctionName: 'withSentryMiddleware', - }).apply(thisArg, args); + apply: async (wrappingTarget, thisArg, args: Parameters) => { + // TODO: We still should add central isolation scope creation for when our build-time instrumentation does not work anymore with turbopack. + return withIsolationScope(isolationScope => { + const req: unknown = args[0]; + const currentScope = getCurrentScope(); + + let spanName: string; + let spanSource: TransactionSource; + + if (req instanceof Request) { + isolationScope.setSDKProcessingMetadata({ + request: winterCGRequestToRequestData(req), + }); + spanName = `middleware ${req.method} ${new URL(req.url).pathname}`; + spanSource = 'url'; + } else { + spanName = 'middleware'; + spanSource = 'component'; + } + + currentScope.setTransactionName(spanName); + + const activeSpan = getActiveSpan(); + + if (activeSpan) { + // If there is an active span, it likely means that the automatic Next.js OTEL instrumentation worked and we can + // rely on that for parameterization. + spanName = 'middleware'; + spanSource = 'component'; + + const rootSpan = getRootSpan(activeSpan); + if (rootSpan) { + setCapturedScopesOnSpan(rootSpan, currentScope, isolationScope); + } + } + + return startSpan( + { + name: spanName, + op: 'http.server.middleware', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: spanSource, + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs.wrapMiddlewareWithSentry', + }, + }, + () => { + return handleCallbackErrors( + () => wrappingTarget.apply(thisArg, args), + error => { + captureException(error, { + mechanism: { + type: 'instrument', + handled: false, + }, + }); + }, + () => { + vercelWaitUntil(flushSafelyWithTimeout()); + }, + ); + }, + ); + }); }, }); } diff --git a/packages/nextjs/src/common/wrapPageComponentWithSentry.ts b/packages/nextjs/src/common/wrapPageComponentWithSentry.ts deleted file mode 100644 index 8cd4a250ac14..000000000000 --- a/packages/nextjs/src/common/wrapPageComponentWithSentry.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { captureException, getCurrentScope, withIsolationScope } from '@sentry/core'; -import { extractTraceparentData } from '@sentry/utils'; -import { escapeNextjsTracing } from './utils/tracingUtils'; - -interface FunctionComponent { - (...args: unknown[]): unknown; -} - -interface ClassComponent { - new (...args: unknown[]): { - props?: unknown; - render(...args: unknown[]): unknown; - }; -} - -function isReactClassComponent(target: unknown): target is ClassComponent { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - return typeof target === 'function' && target?.prototype?.isReactComponent; -} - -/** - * Wraps a page component with Sentry error instrumentation. - */ -export function wrapPageComponentWithSentry(pageComponent: FunctionComponent | ClassComponent): unknown { - if (isReactClassComponent(pageComponent)) { - return class SentryWrappedPageComponent extends pageComponent { - public render(...args: unknown[]): unknown { - return escapeNextjsTracing(() => { - return withIsolationScope(() => { - const scope = getCurrentScope(); - // We extract the sentry trace data that is put in the component props by datafetcher wrappers - const sentryTraceData = - typeof this.props === 'object' && - this.props !== null && - '_sentryTraceData' in this.props && - typeof this.props._sentryTraceData === 'string' - ? this.props._sentryTraceData - : undefined; - - if (sentryTraceData) { - const traceparentData = extractTraceparentData(sentryTraceData); - scope.setContext('trace', { - span_id: traceparentData?.parentSpanId, - trace_id: traceparentData?.traceId, - }); - } - - try { - return super.render(...args); - } catch (e) { - captureException(e, { - mechanism: { - handled: false, - }, - }); - throw e; - } - }); - }); - } - }; - } else if (typeof pageComponent === 'function') { - return new Proxy(pageComponent, { - apply(target, thisArg, argArray: [{ _sentryTraceData?: string } | undefined]) { - return escapeNextjsTracing(() => { - return withIsolationScope(() => { - const scope = getCurrentScope(); - // We extract the sentry trace data that is put in the component props by datafetcher wrappers - const sentryTraceData = argArray?.[0]?._sentryTraceData; - - if (sentryTraceData) { - const traceparentData = extractTraceparentData(sentryTraceData); - scope.setContext('trace', { - span_id: traceparentData?.parentSpanId, - trace_id: traceparentData?.traceId, - }); - } - - try { - return target.apply(thisArg, argArray); - } catch (e) { - captureException(e, { - mechanism: { - handled: false, - }, - }); - throw e; - } - }); - }); - }, - }); - } else { - return pageComponent; - } -} diff --git a/packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts b/packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts index 71061e913dac..215bb35ce9a5 100644 --- a/packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts +++ b/packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts @@ -1,24 +1,24 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_OP, - SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, - SPAN_STATUS_ERROR, + Scope, captureException, + getActiveSpan, + getCapturedScopesOnSpan, + getIsolationScope, + getRootSpan, handleCallbackErrors, + setCapturedScopesOnSpan, setHttpStatus, - startSpan, withIsolationScope, withScope, } from '@sentry/core'; -import { propagationContextFromHeaders, vercelWaitUntil, winterCGHeadersToDict } from '@sentry/utils'; -import { isNotFoundNavigationError, isRedirectNavigationError } from './nextNavigationErrorUtils'; + import type { RouteHandlerContext } from './types'; -import { flushSafelyWithTimeout } from './utils/responseEnd'; -import { - commonObjectToIsolationScope, - commonObjectToPropagationContext, - escapeNextjsTracing, -} from './utils/tracingUtils'; + +import { propagationContextFromHeaders, winterCGHeadersToDict } from '@sentry/utils'; +import { isNotFoundNavigationError, isRedirectNavigationError } from './nextNavigationErrorUtils'; +import { commonObjectToIsolationScope } from './utils/tracingUtils'; /** * Wraps a Next.js App Router Route handler with Sentry error and performance instrumentation. @@ -33,76 +33,84 @@ export function wrapRouteHandlerWithSentry any>( const { method, parameterizedRoute, headers } = context; return new Proxy(routeHandler, { - apply: (originalFunction, thisArg, args) => { - return escapeNextjsTracing(() => { - const isolationScope = commonObjectToIsolationScope(headers); - - const completeHeadersDict: Record = headers ? winterCGHeadersToDict(headers) : {}; + apply: async (originalFunction, thisArg, args) => { + const activeSpan = getActiveSpan(); + const rootSpan = activeSpan ? getRootSpan(activeSpan) : undefined; - isolationScope.setSDKProcessingMetadata({ - request: { - headers: completeHeadersDict, - }, - }); + let edgeRuntimeIsolationScopeOverride: Scope | undefined; + if (rootSpan && process.env.NEXT_RUNTIME === 'edge') { + const isolationScope = commonObjectToIsolationScope(headers); + const { scope } = getCapturedScopesOnSpan(rootSpan); + setCapturedScopesOnSpan(rootSpan, scope ?? new Scope(), isolationScope); - const incomingPropagationContext = propagationContextFromHeaders( - completeHeadersDict['sentry-trace'], - completeHeadersDict['baggage'], - ); + edgeRuntimeIsolationScopeOverride = isolationScope; - const propagationContext = commonObjectToPropagationContext(headers, incomingPropagationContext); + rootSpan.updateName(`${method} ${parameterizedRoute}`); + rootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route'); + rootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'http.server'); + } - return withIsolationScope(isolationScope, () => { + return withIsolationScope( + process.env.NEXT_RUNTIME === 'edge' ? edgeRuntimeIsolationScopeOverride : getIsolationScope(), + () => { return withScope(async scope => { scope.setTransactionName(`${method} ${parameterizedRoute}`); - scope.setPropagationContext(propagationContext); - try { - return startSpan( - { - name: `${method} ${parameterizedRoute}`, - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.server', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs', - }, - forceTransaction: true, + + if (process.env.NEXT_RUNTIME === 'edge') { + const completeHeadersDict: Record = headers ? winterCGHeadersToDict(headers) : {}; + const incomingPropagationContext = propagationContextFromHeaders( + completeHeadersDict['sentry-trace'], + completeHeadersDict['baggage'], + ); + scope.setPropagationContext(incomingPropagationContext); + scope.setSDKProcessingMetadata({ + request: { + method, + headers: completeHeadersDict, }, - async span => { - const response: Response = await handleCallbackErrors( - () => originalFunction.apply(thisArg, args), - error => { - // Next.js throws errors when calling `redirect()`. We don't wanna report these. - if (isRedirectNavigationError(error)) { - // Don't do anything - } else if (isNotFoundNavigationError(error) && span) { - span.setStatus({ code: SPAN_STATUS_ERROR, message: 'not_found' }); - } else { - captureException(error, { - mechanism: { - handled: false, - }, - }); - } - }, - ); + }); + } - try { - if (span && response.status) { - setHttpStatus(span, response.status); - } - } catch { - // best effort - response may be undefined? + const response: Response = await handleCallbackErrors( + () => originalFunction.apply(thisArg, args), + error => { + // Next.js throws errors when calling `redirect()`. We don't wanna report these. + if (isRedirectNavigationError(error)) { + // Don't do anything + } else if (isNotFoundNavigationError(error)) { + if (activeSpan) { + setHttpStatus(activeSpan, 404); } + if (rootSpan) { + setHttpStatus(rootSpan, 404); + } + } else { + captureException(error, { + mechanism: { + handled: false, + }, + }); + } + }, + ); - return response; - }, - ); - } finally { - vercelWaitUntil(flushSafelyWithTimeout()); + try { + if (response.status) { + if (activeSpan) { + setHttpStatus(activeSpan, response.status); + } + if (rootSpan) { + setHttpStatus(rootSpan, response.status); + } + } + } catch { + // best effort - response may be undefined? } + + return response; }); - }); - }); + }, + ); }, }); } diff --git a/packages/nextjs/src/common/wrapServerComponentWithSentry.ts b/packages/nextjs/src/common/wrapServerComponentWithSentry.ts index 079722dad76d..c4bbde29eb53 100644 --- a/packages/nextjs/src/common/wrapServerComponentWithSentry.ts +++ b/packages/nextjs/src/common/wrapServerComponentWithSentry.ts @@ -18,6 +18,7 @@ import { propagationContextFromHeaders, uuid4, vercelWaitUntil, winterCGHeadersT import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core'; import { isNotFoundNavigationError, isRedirectNavigationError } from '../common/nextNavigationErrorUtils'; import type { ServerComponentContext } from '../common/types'; +import { TRANSACTION_ATTR_SENTRY_TRACE_BACKFILL } from './span-attributes-with-logic-attached'; import { flushSafelyWithTimeout } from './utils/responseEnd'; import { commonObjectToIsolationScope, commonObjectToPropagationContext } from './utils/tracingUtils'; @@ -43,9 +44,6 @@ export function wrapServerComponentWithSentry any> const rootSpan = getRootSpan(activeSpan); const { scope } = getCapturedScopesOnSpan(rootSpan); setCapturedScopesOnSpan(rootSpan, scope ?? new Scope(), isolationScope); - - // We mark the root span as an app router span so we can allow-list it in our span processor that would normally filter out all Next.js transactions/spans - rootSpan.setAttribute('sentry.rsc', true); } const headersDict = context.headers ? winterCGHeadersToDict(context.headers) : undefined; @@ -74,6 +72,15 @@ export function wrapServerComponentWithSentry any> scope.setPropagationContext(propagationContext); } + const activeSpan = getActiveSpan(); + if (activeSpan) { + const rootSpan = getRootSpan(activeSpan); + const sentryTrace = headersDict?.['sentry-trace']; + if (sentryTrace) { + rootSpan.setAttribute(TRANSACTION_ATTR_SENTRY_TRACE_BACKFILL, sentryTrace); + } + } + return startSpanManual( { op: 'function.nextjs', diff --git a/packages/nextjs/src/config/loaders/valueInjectionLoader.ts b/packages/nextjs/src/config/loaders/valueInjectionLoader.ts index bf89ce90ac2c..c3d7b499fabb 100644 --- a/packages/nextjs/src/config/loaders/valueInjectionLoader.ts +++ b/packages/nextjs/src/config/loaders/valueInjectionLoader.ts @@ -1,9 +1,20 @@ +// Rollup doesn't like if we put the directive regex as a literal (?). No idea why. +/* eslint-disable @sentry-internal/sdk/no-regexp-constructor */ + import type { LoaderThis } from './types'; -type LoaderOptions = { +export type ValueInjectionLoaderOptions = { values: Record; }; +// We need to be careful not to inject anything before any `"use strict";`s or "use client"s or really any other directive. +// As an additional complication directives may come after any number of comments. +// This regex is shamelessly stolen from: https://github.com/getsentry/sentry-javascript-bundler-plugins/blob/7f984482c73e4284e8b12a08dfedf23b5a82f0af/packages/bundler-plugin-core/src/index.ts#L535-L539 +const SKIP_COMMENT_AND_DIRECTIVE_REGEX = + // Note: CodeQL complains that this regex potentially has n^2 runtime. This likely won't affect realistic files. + // biome-ignore lint/nursery/useRegexLiterals: No user input + new RegExp('^(?:\\s*|/\\*(?:.|\\r|\\n)*?\\*/|//.*[\\n\\r])*(?:"[^"]*";?|\'[^\']*\';?)?'); + /** * Set values on the global/window object at the start of a module. * @@ -11,16 +22,22 @@ type LoaderOptions = { * - `values`: An object where the keys correspond to the keys of the global values to set and the values * correspond to the values of the values on the global object. Values must be JSON serializable. */ -export default function valueInjectionLoader(this: LoaderThis, userCode: string): string { +export default function valueInjectionLoader(this: LoaderThis, userCode: string): string { // We know one or the other will be defined, depending on the version of webpack being used const { values } = 'getOptions' in this ? this.getOptions() : this.query; // We do not want to cache injected values across builds this.cacheable(false); - const injectedCode = Object.entries(values) - .map(([key, value]) => `globalThis["${key}"] = ${JSON.stringify(value)};`) - .join('\n'); + // Not putting any newlines in the generated code will decrease the likelihood of sourcemaps breaking + const injectedCode = + // eslint-disable-next-line prefer-template + ';' + + Object.entries(values) + .map(([key, value]) => `globalThis["${key}"] = ${JSON.stringify(value)};`) + .join(''); - return `${injectedCode}\n${userCode}`; + return userCode.replace(SKIP_COMMENT_AND_DIRECTIVE_REGEX, match => { + return match + injectedCode; + }); } diff --git a/packages/nextjs/src/config/loaders/wrappingLoader.ts b/packages/nextjs/src/config/loaders/wrappingLoader.ts index d3c1d62c9330..e298a459d16e 100644 --- a/packages/nextjs/src/config/loaders/wrappingLoader.ts +++ b/packages/nextjs/src/config/loaders/wrappingLoader.ts @@ -182,8 +182,10 @@ export default function wrappingLoader( const componentTypeMatch = path.posix .normalize(path.relative(appDir, this.resourcePath)) + // Replace all backslashes with forward slashes (windows) + .replace(/\\/g, '/') // eslint-disable-next-line @sentry-internal/sdk/no-regexp-constructor - .match(new RegExp(`/\\/?([^/]+)\\.(?:${pageExtensionRegex})$`)); + .match(new RegExp(`(?:^|/)?([^/]+)\\.(?:${pageExtensionRegex})$`)); if (componentTypeMatch && componentTypeMatch[1]) { let componentType: ServerComponentContext['componentType']; diff --git a/packages/nextjs/src/config/templates/routeHandlerWrapperTemplate.ts b/packages/nextjs/src/config/templates/routeHandlerWrapperTemplate.ts index 9f955f9f4109..d0b9fba5ce3b 100644 --- a/packages/nextjs/src/config/templates/routeHandlerWrapperTemplate.ts +++ b/packages/nextjs/src/config/templates/routeHandlerWrapperTemplate.ts @@ -38,27 +38,20 @@ function wrapHandler(handler: T, method: 'GET' | 'POST' | 'PUT' | 'PATCH' | ' return new Proxy(handler, { apply: (originalFunction, thisArg, args) => { - let sentryTraceHeader: string | undefined | null = undefined; - let baggageHeader: string | undefined | null = undefined; let headers: WebFetchHeaders | undefined = undefined; // We try-catch here just in case the API around `requestAsyncStorage` changes unexpectedly since it is not public API try { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access const requestAsyncStore = requestAsyncStorage?.getStore() as ReturnType; - sentryTraceHeader = requestAsyncStore?.headers.get('sentry-trace') ?? undefined; - baggageHeader = requestAsyncStore?.headers.get('baggage') ?? undefined; headers = requestAsyncStore?.headers; } catch (e) { /** empty */ } - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-explicit-any return Sentry.wrapRouteHandlerWithSentry(originalFunction as any, { method, parameterizedRoute: '__ROUTE__', - sentryTraceHeader, - baggageHeader, headers, }).apply(thisArg, args); }, diff --git a/packages/nextjs/src/config/types.ts b/packages/nextjs/src/config/types.ts index d2e78d87f4ae..d887369606b6 100644 --- a/packages/nextjs/src/config/types.ts +++ b/packages/nextjs/src/config/types.ts @@ -1,12 +1,5 @@ import type { GLOBAL_OBJ } from '@sentry/utils'; import type { SentryWebpackPluginOptions } from '@sentry/webpack-plugin'; -import type { DefinePlugin, WebpackPluginInstance } from 'webpack'; - -// Export this from here because importing something from Webpack (the library) in `webpack.ts` confuses the heck out of -// madge, which we use for circular dependency checking. We've manually excluded this file from the check (which is -// safe, since it only includes types), so we can import it here without causing madge to fail. See -// https://github.com/pahen/madge/issues/306. -export type { WebpackPluginInstance }; // The first argument to `withSentryConfig` (which is the user's next config). export type ExportedNextConfig = NextConfigObject | NextConfigFunction; @@ -17,6 +10,11 @@ type NextRewrite = { destination: string; }; +interface WebpackPluginInstance { + [index: string]: any; + apply: (compiler: any) => void; +} + export type NextConfigObject = { // Custom webpack options webpack?: WebpackConfigFunction | null; @@ -409,12 +407,15 @@ export type SentryBuildOptions = { autoInstrumentAppDirectory?: boolean; /** - * Exclude certain serverside API routes or pages from being instrumented with Sentry. This option takes an array of - * strings or regular expressions. This options also affects pages in the `app` directory. + * Exclude certain serverside API routes or pages from being instrumented with Sentry during build-time. This option + * takes an array of strings or regular expressions. This options also affects pages in the `app` directory. * * NOTE: Pages should be specified as routes (`/animals` or `/api/animals/[animalType]/habitat`), not filepaths * (`pages/animals/index.js` or `.\src\pages\api\animals\[animalType]\habitat.tsx`), and strings must be be a full, * exact match. + * + * Notice: If you build Next.js with turbopack, the Sentry SDK will no longer apply build-time instrumentation and + * purely rely on Next.js telemetry features, meaning that this option will effectively no-op. */ excludeServerRoutes?: Array; @@ -499,7 +500,7 @@ export type BuildContext = { config: any; webpack: { version: string; - DefinePlugin: typeof DefinePlugin; + DefinePlugin: new (values: Record) => WebpackPluginInstance; }; // eslint-disable-next-line @typescript-eslint/no-explicit-any defaultLoaders: any; // needed for type tests (test:types) diff --git a/packages/nextjs/src/edge/index.ts b/packages/nextjs/src/edge/index.ts index 7034873f665e..fff4236bf3be 100644 --- a/packages/nextjs/src/edge/index.ts +++ b/packages/nextjs/src/edge/index.ts @@ -1,11 +1,23 @@ -import { applySdkMetadata, registerSpanErrorInstrumentation } from '@sentry/core'; -import { GLOBAL_OBJ } from '@sentry/utils'; +import { + SEMANTIC_ATTRIBUTE_SENTRY_OP, + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + applySdkMetadata, + getRootSpan, + registerSpanErrorInstrumentation, + spanToJSON, +} from '@sentry/core'; + +import { GLOBAL_OBJ, stripUrlQueryAndFragment, vercelWaitUntil } from '@sentry/utils'; import type { VercelEdgeOptions } from '@sentry/vercel-edge'; import { getDefaultIntegrations, init as vercelEdgeInit } from '@sentry/vercel-edge'; import { isBuild } from '../common/utils/isBuild'; +import { flushSafelyWithTimeout } from '../common/utils/responseEnd'; import { distDirRewriteFramesIntegration } from './distDirRewriteFramesIntegration'; +export { captureUnderscoreErrorException } from '../common/pages-router-instrumentation/_error'; + export type EdgeOptions = VercelEdgeOptions; const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & { @@ -37,7 +49,44 @@ export function init(options: VercelEdgeOptions = {}): void { applySdkMetadata(opts, 'nextjs'); - vercelEdgeInit(opts); + const client = vercelEdgeInit(opts); + + client?.on('spanStart', span => { + const spanAttributes = spanToJSON(span).data; + + // Mark all spans generated by Next.js as 'auto' + if (spanAttributes?.['next.span_type'] !== undefined) { + span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, 'auto'); + } + + // Make sure middleware spans get the right op + if (spanAttributes?.['next.span_type'] === 'Middleware.execute') { + span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'http.server.middleware'); + span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'url'); + } + }); + + // Use the preprocessEvent hook instead of an event processor, so that the users event processors receive the most + // up-to-date value, but also so that the logic that detects changes to the transaction names to set the source to + // "custom", doesn't trigger. + client?.on('preprocessEvent', event => { + // The otel auto inference will clobber the transaction name because the span has an http.target + if ( + event.type === 'transaction' && + event.contexts?.trace?.data?.['next.span_type'] === 'Middleware.execute' && + event.contexts?.trace?.data?.['next.span_name'] + ) { + if (event.transaction) { + event.transaction = stripUrlQueryAndFragment(event.contexts.trace.data['next.span_name']); + } + } + }); + + client?.on('spanEnd', span => { + if (span === getRootSpan(span)) { + vercelWaitUntil(flushSafelyWithTimeout()); + } + }); } /** diff --git a/packages/nextjs/src/edge/wrapApiHandlerWithSentry.ts b/packages/nextjs/src/edge/wrapApiHandlerWithSentry.ts index e5191ea27dbe..5c8ce043ecb8 100644 --- a/packages/nextjs/src/edge/wrapApiHandlerWithSentry.ts +++ b/packages/nextjs/src/edge/wrapApiHandlerWithSentry.ts @@ -1,4 +1,18 @@ -import { withEdgeWrapping } from '../common/utils/edgeWrapperUtils'; +import { + SEMANTIC_ATTRIBUTE_SENTRY_OP, + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + captureException, + getActiveSpan, + getCurrentScope, + getRootSpan, + handleCallbackErrors, + setCapturedScopesOnSpan, + startSpan, + withIsolationScope, +} from '@sentry/core'; +import { vercelWaitUntil, winterCGRequestToRequestData } from '@sentry/utils'; +import { flushSafelyWithTimeout } from '../common/utils/responseEnd'; import type { EdgeRouteHandler } from './types'; /** @@ -9,18 +23,76 @@ export function wrapApiHandlerWithSentry( parameterizedRoute: string, ): (...params: Parameters) => Promise> { return new Proxy(handler, { - apply: (wrappingTarget, thisArg, args: Parameters) => { - const req = args[0]; + apply: async (wrappingTarget, thisArg, args: Parameters) => { + // TODO: We still should add central isolation scope creation for when our build-time instrumentation does not work anymore with turbopack. - const wrappedHandler = withEdgeWrapping(wrappingTarget, { - spanDescription: !(req instanceof Request) - ? `handler (${parameterizedRoute})` - : `${req.method} ${parameterizedRoute}`, - spanOp: 'http.server', - mechanismFunctionName: 'wrapApiHandlerWithSentry', - }); + return withIsolationScope(isolationScope => { + const req: unknown = args[0]; + const currentScope = getCurrentScope(); + + if (req instanceof Request) { + isolationScope.setSDKProcessingMetadata({ + request: winterCGRequestToRequestData(req), + }); + currentScope.setTransactionName(`${req.method} ${parameterizedRoute}`); + } else { + currentScope.setTransactionName(`handler (${parameterizedRoute})`); + } + + let spanName: string; + let op: string | undefined = 'http.server'; - return wrappedHandler.apply(thisArg, args); + // If there is an active span, it likely means that the automatic Next.js OTEL instrumentation worked and we can + // rely on that for parameterization. + const activeSpan = getActiveSpan(); + if (activeSpan) { + spanName = `handler (${parameterizedRoute})`; + op = undefined; + + const rootSpan = getRootSpan(activeSpan); + if (rootSpan) { + rootSpan.updateName( + req instanceof Request ? `${req.method} ${parameterizedRoute}` : `handler ${parameterizedRoute}`, + ); + rootSpan.setAttributes({ + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.server', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + }); + setCapturedScopesOnSpan(rootSpan, currentScope, isolationScope); + } + } else if (req instanceof Request) { + spanName = `${req.method} ${parameterizedRoute}`; + } else { + spanName = `handler ${parameterizedRoute}`; + } + + return startSpan( + { + name: spanName, + op: op, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs.wrapApiHandlerWithSentry', + }, + }, + () => { + return handleCallbackErrors( + () => wrappingTarget.apply(thisArg, args), + error => { + captureException(error, { + mechanism: { + type: 'instrument', + handled: false, + }, + }); + }, + () => { + vercelWaitUntil(flushSafelyWithTimeout()); + }, + ); + }, + ); + }); }, }); } diff --git a/packages/nextjs/src/server/index.ts b/packages/nextjs/src/server/index.ts index 6249fcd8bad4..7aade8cbd5c3 100644 --- a/packages/nextjs/src/server/index.ts +++ b/packages/nextjs/src/server/index.ts @@ -1,16 +1,22 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, applySdkMetadata, + getCapturedScopesOnSpan, getClient, + getCurrentScope, getGlobalScope, + getIsolationScope, getRootSpan, + setCapturedScopesOnSpan, spanToJSON, } from '@sentry/core'; -import { getDefaultIntegrations, init as nodeInit } from '@sentry/node'; import type { NodeClient, NodeOptions } from '@sentry/node'; -import { GLOBAL_OBJ, logger } from '@sentry/utils'; +import { getDefaultIntegrations, httpIntegration, init as nodeInit } from '@sentry/node'; +import { GLOBAL_OBJ, extractTraceparentData, logger, stripUrlQueryAndFragment } from '@sentry/utils'; +import { context } from '@opentelemetry/api'; import { ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_ROUTE, @@ -18,38 +24,28 @@ import { SEMATTRS_HTTP_METHOD, SEMATTRS_HTTP_TARGET, } from '@opentelemetry/semantic-conventions'; +import { getScopesFromContext } from '@sentry/opentelemetry'; import type { EventProcessor } from '@sentry/types'; import { DEBUG_BUILD } from '../common/debug-build'; import { devErrorSymbolicationEventProcessor } from '../common/devErrorSymbolicationEventProcessor'; import { getVercelEnv } from '../common/getVercelEnv'; +import { + TRANSACTION_ATTR_SENTRY_ROUTE_BACKFILL, + TRANSACTION_ATTR_SENTRY_TRACE_BACKFILL, + TRANSACTION_ATTR_SHOULD_DROP_TRANSACTION, +} from '../common/span-attributes-with-logic-attached'; import { isBuild } from '../common/utils/isBuild'; import { distDirRewriteFramesIntegration } from './distDirRewriteFramesIntegration'; export * from '@sentry/node'; -export { captureUnderscoreErrorException } from '../common/_error'; +export { captureUnderscoreErrorException } from '../common/pages-router-instrumentation/_error'; const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & { __rewriteFramesDistDir__?: string; __sentryRewritesTunnelPath__?: string; }; -// https://github.com/lforst/nextjs-fork/blob/9051bc44d969a6e0ab65a955a2fc0af522a83911/packages/next/src/server/lib/trace/constants.ts#L11 -const NEXTJS_SPAN_NAME_PREFIXES = [ - 'BaseServer.', - 'LoadComponents.', - 'NextServer.', - 'createServer.', - 'startServer.', - 'NextNodeServer.', - 'Render.', - 'AppRender.', - 'Router.', - 'Node.', - 'AppRouteRouteHandlers.', - 'ResolveMetadata.', -]; - /** * A passthrough error boundary for the server that doesn't depend on any react. Error boundaries don't catch SSR errors * so they should simply be a passthrough. @@ -98,7 +94,14 @@ export function init(options: NodeOptions): NodeClient | undefined { return; } - const customDefaultIntegrations = getDefaultIntegrations(options); + const customDefaultIntegrations = getDefaultIntegrations(options) + .filter(integration => integration.name !== 'Http') + .concat( + // We are using the HTTP integration without instrumenting incoming HTTP requests because Next.js does that by itself. + httpIntegration({ + disableIncomingRequestSpans: true, + }), + ); // Turn off Next.js' own fetch instrumentation // https://github.com/lforst/nextjs-fork/blob/1994fd186defda77ad971c36dc3163db263c993f/packages/next/src/server/lib/patch-fetch.ts#L245 @@ -133,25 +136,7 @@ export function init(options: NodeOptions): NodeClient | undefined { applySdkMetadata(opts, 'nextjs', ['nextjs', 'node']); const client = nodeInit(opts); - client?.on('beforeSampling', ({ spanAttributes, spanName, parentSampled, parentContext }, samplingDecision) => { - // We allowlist the "BaseServer.handleRequest" span, since that one is responsible for App Router requests, which are actually useful for us. - // HOWEVER, that span is not only responsible for App Router requests, which is why we additionally filter for certain transactions in an - // event processor further below. - if (spanAttributes['next.span_type'] === 'BaseServer.handleRequest') { - return; - } - - // If we encounter a span emitted by Next.js, we do not want to sample it - // The reason for this is that the data quality of the spans varies, it is different per version of Next, - // and we need to keep our manual instrumentation around for the edge runtime anyhow. - // BUT we only do this if we don't have a parent span with a sampling decision yet (or if the parent is remote) - if ( - (spanAttributes['next.span_type'] || NEXTJS_SPAN_NAME_PREFIXES.some(prefix => spanName.startsWith(prefix))) && - (parentSampled === undefined || parentContext?.isRemote) - ) { - samplingDecision.decision = false; - } - + client?.on('beforeSampling', ({ spanAttributes }, samplingDecision) => { // There are situations where the Next.js Node.js server forwards requests for the Edge Runtime server (e.g. in // middleware) and this causes spans for Sentry ingest requests to be created. These are not exempt from our tracing // because we didn't get the chance to do `suppressTracing`, since this happens outside of userland. @@ -176,7 +161,7 @@ export function init(options: NodeOptions): NodeClient | undefined { // What we do in this glorious piece of code, is hoist any information about parameterized routes from spans emitted // by Next.js via the `next.route` attribute, up to the transaction by setting the http.route attribute. - if (spanAttributes?.['next.route']) { + if (typeof spanAttributes?.['next.route'] === 'string') { const rootSpan = getRootSpan(span); const rootSpanAttributes = spanToJSON(rootSpan).data; @@ -186,21 +171,31 @@ export function init(options: NodeOptions): NodeClient | undefined { (rootSpanAttributes?.[ATTR_HTTP_REQUEST_METHOD] || rootSpanAttributes?.[SEMATTRS_HTTP_METHOD]) && !rootSpanAttributes?.[ATTR_HTTP_ROUTE] ) { - rootSpan.setAttribute(ATTR_HTTP_ROUTE, spanAttributes['next.route']); + const route = spanAttributes['next.route'].replace(/\/route$/, ''); + rootSpan.updateName(route); + rootSpan.setAttribute(ATTR_HTTP_ROUTE, route); } } // We want to skip span data inference for any spans generated by Next.js. Reason being that Next.js emits spans // with patterns (e.g. http.server spans) that will produce confusing data. if (spanAttributes?.['next.span_type'] !== undefined) { - span.setAttribute('sentry.skip_span_data_inference', true); span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, 'auto'); } - // We want to rename these spans because they look like "GET /path/to/route" and we already emit spans that look - // like this with our own http instrumentation. - if (spanAttributes?.['next.span_type'] === 'BaseServer.handleRequest') { - span.updateName('next server handler'); // This is all lowercase because the spans that Next.js emits by itself generally look like this. + // We want to fork the isolation scope for incoming requests + if (spanAttributes?.['next.span_type'] === 'BaseServer.handleRequest' && span === getRootSpan(span)) { + const scopes = getCapturedScopesOnSpan(span); + + const isolationScope = (scopes.isolationScope || getIsolationScope()).clone(); + const scope = scopes.scope || getCurrentScope(); + + const currentScopesPointer = getScopesFromContext(context.active()); + if (currentScopesPointer) { + currentScopesPointer.isolationScope = isolationScope; + } + + setCapturedScopesOnSpan(span, scope, isolationScope); } }); @@ -215,15 +210,6 @@ export function init(options: NodeOptions): NodeClient | undefined { return null; } - // We only want to use our HTTP integration/instrumentation for app router requests, which are marked with the `sentry.rsc` attribute. - if ( - (event.contexts?.trace?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] === 'auto.http.otel.http' || - event.contexts?.trace?.data?.['next.span_type'] === 'BaseServer.handleRequest') && - event.contexts?.trace?.data?.['sentry.rsc'] !== true - ) { - return null; - } - // Filter out transactions for requests to the tunnel route if ( globalWithInjectedValues.__sentryRewritesTunnelPath__ && @@ -247,6 +233,27 @@ export function init(options: NodeOptions): NodeClient | undefined { return null; } + // Filter transactions that we explicitly want to drop. + if (event.contexts?.trace?.data?.[TRANSACTION_ATTR_SHOULD_DROP_TRANSACTION]) { + return null; + } + + // Next.js 13 sometimes names the root transactions like this containing useless tracing. + if (event.transaction === 'NextServer.getRequestHandler') { + return null; + } + + // Next.js 13 is not correctly picking up tracing data for trace propagation so we use a back-fill strategy + if (typeof event.contexts?.trace?.data?.[TRANSACTION_ATTR_SENTRY_TRACE_BACKFILL] === 'string') { + const traceparentData = extractTraceparentData( + event.contexts.trace.data[TRANSACTION_ATTR_SENTRY_TRACE_BACKFILL], + ); + + if (traceparentData?.parentSampled === false) { + return null; + } + } + return event; } else { return event; @@ -291,27 +298,62 @@ export function init(options: NodeOptions): NodeClient | undefined { ), ); - getGlobalScope().addEventProcessor( - Object.assign( - (event => { - // Sometimes, the HTTP integration will not work, causing us not to properly set an op for spans generated by - // Next.js that are actually more or less correct server HTTP spans, so we are backfilling the op here. - if ( - event.type === 'transaction' && - event.transaction?.match(/^(RSC )?GET /) && - event.contexts?.trace?.data?.['sentry.rsc'] === true && - !event.contexts.trace.op - ) { - event.contexts.trace.data = event.contexts.trace.data || {}; - event.contexts.trace.data[SEMANTIC_ATTRIBUTE_SENTRY_OP] = 'http.server'; - event.contexts.trace.op = 'http.server'; - } + // Use the preprocessEvent hook instead of an event processor, so that the users event processors receive the most + // up-to-date value, but also so that the logic that detects changes to the transaction names to set the source to + // "custom", doesn't trigger. + client?.on('preprocessEvent', event => { + // Enhance route handler transactions + if ( + event.type === 'transaction' && + event.contexts?.trace?.data?.['next.span_type'] === 'BaseServer.handleRequest' + ) { + event.contexts.trace.data = event.contexts.trace.data || {}; + event.contexts.trace.data[SEMANTIC_ATTRIBUTE_SENTRY_OP] = 'http.server'; + event.contexts.trace.op = 'http.server'; - return event; - }) satisfies EventProcessor, - { id: 'NextjsTransactionEnhancer' }, - ), - ); + if (event.transaction) { + event.transaction = stripUrlQueryAndFragment(event.transaction); + } + + // eslint-disable-next-line deprecation/deprecation + const method = event.contexts.trace.data[SEMATTRS_HTTP_METHOD]; + // eslint-disable-next-line deprecation/deprecation + const target = event.contexts?.trace?.data?.[SEMATTRS_HTTP_TARGET]; + const route = event.contexts.trace.data[ATTR_HTTP_ROUTE]; + + if (typeof method === 'string' && typeof route === 'string') { + event.transaction = `${method} ${route.replace(/\/route$/, '')}`; + event.contexts.trace.data[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] = 'route'; + } + + // backfill transaction name for pages that would otherwise contain unparameterized routes + if (event.contexts.trace.data[TRANSACTION_ATTR_SENTRY_ROUTE_BACKFILL] && event.transaction !== 'GET /_app') { + event.transaction = `${method} ${event.contexts.trace.data[TRANSACTION_ATTR_SENTRY_ROUTE_BACKFILL]}`; + } + + // Next.js overrides transaction names for page loads that throw an error + // but we want to keep the original target name + if (event.transaction === 'GET /_error' && target) { + event.transaction = `${method ? `${method} ` : ''}${target}`; + } + } + + // Next.js 13 is not correctly picking up tracing data for trace propagation so we use a back-fill strategy + if ( + event.type === 'transaction' && + typeof event.contexts?.trace?.data?.[TRANSACTION_ATTR_SENTRY_TRACE_BACKFILL] === 'string' + ) { + const traceparentData = extractTraceparentData(event.contexts.trace.data[TRANSACTION_ATTR_SENTRY_TRACE_BACKFILL]); + + if (traceparentData?.traceId) { + event.contexts.trace.trace_id = traceparentData.traceId; + } + + if (traceparentData?.parentSpanId) { + event.contexts.trace.parent_span_id = traceparentData.parentSpanId; + } + } + }); if (process.env.NODE_ENV === 'development') { getGlobalScope().addEventProcessor(devErrorSymbolicationEventProcessor); @@ -328,4 +370,4 @@ function sdkAlreadyInitialized(): boolean { export * from '../common'; -export { wrapApiHandlerWithSentry } from '../common/wrapApiHandlerWithSentry'; +export { wrapApiHandlerWithSentry } from '../common/pages-router-instrumentation/wrapApiHandlerWithSentry'; diff --git a/packages/nextjs/test/config/__snapshots__/valueInjectionLoader.test.ts.snap b/packages/nextjs/test/config/__snapshots__/valueInjectionLoader.test.ts.snap new file mode 100644 index 000000000000..ca901580da63 --- /dev/null +++ b/packages/nextjs/test/config/__snapshots__/valueInjectionLoader.test.ts.snap @@ -0,0 +1,83 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`valueInjectionLoader should correctly insert values for basic config 1`] = ` +" + ;globalThis[\\"foo\\"] = \\"bar\\";import * as Sentry from '@sentry/nextjs'; + Sentry.init(); + " +`; + +exports[`valueInjectionLoader should correctly insert values with a misplaced directive 1`] = ` +" + ;globalThis[\\"foo\\"] = \\"bar\\";console.log('This will render the directive useless'); + \\"use client\\"; + + + + import * as Sentry from '@sentry/nextjs'; + Sentry.init(); + " +`; + +exports[`valueInjectionLoader should correctly insert values with directive 1`] = ` +" + \\"use client\\";globalThis[\\"foo\\"] = \\"bar\\"; + import * as Sentry from '@sentry/nextjs'; + Sentry.init(); + " +`; + +exports[`valueInjectionLoader should correctly insert values with directive and block comments 1`] = ` +" + /* test */ + \\"use client\\";;globalThis[\\"foo\\"] = \\"bar\\"; + import * as Sentry from '@sentry/nextjs'; + Sentry.init(); + " +`; + +exports[`valueInjectionLoader should correctly insert values with directive and inline comments 1`] = ` +" + // test + \\"use client\\";;globalThis[\\"foo\\"] = \\"bar\\"; + import * as Sentry from '@sentry/nextjs'; + Sentry.init(); + " +`; + +exports[`valueInjectionLoader should correctly insert values with directive and multiline block comments 1`] = ` +" + /* + test + */ + \\"use client\\";;globalThis[\\"foo\\"] = \\"bar\\"; + import * as Sentry from '@sentry/nextjs'; + Sentry.init(); + " +`; + +exports[`valueInjectionLoader should correctly insert values with directive and multiline block comments and a bunch of whitespace 1`] = ` +" + /* + test + */ + + + + + \\"use client\\";;globalThis[\\"foo\\"] = \\"bar\\"; + + + + import * as Sentry from '@sentry/nextjs'; + Sentry.init(); + " +`; + +exports[`valueInjectionLoader should correctly insert values with directive and semicolon 1`] = ` +" + \\"use client\\";;globalThis[\\"foo\\"] = \\"bar\\"; + import * as Sentry from '@sentry/nextjs'; + Sentry.init(); + " +`; diff --git a/packages/nextjs/test/config/mocks.ts b/packages/nextjs/test/config/mocks.ts index 5c27c743c9f9..7d2cc1a0f4ac 100644 --- a/packages/nextjs/test/config/mocks.ts +++ b/packages/nextjs/test/config/mocks.ts @@ -58,15 +58,11 @@ afterEach(() => { mkdtempSyncSpy.mockClear(); }); -// TODO (v8): This shouldn't be necessary once `hideSourceMaps` gets a default value, even for the updated error message // eslint-disable-next-line @typescript-eslint/unbound-method const realConsoleWarn = global.console.warn; global.console.warn = (...args: unknown[]) => { - // Suppress the warning message about the `hideSourceMaps` option. This is better than forcing a value for - // `hideSourceMaps` because that would mean we couldn't test it easily and would muddy the waters of other tests. Note - // that doing this here, as a side effect, only works because the tests which trigger this warning are the same tests - // which need other mocks from this file. - if (typeof args[0] === 'string' && args[0]?.includes('your original code may be visible in browser devtools')) { + // Suppress the v7 -> v8 migration warning which would get spammed for the unit tests otherwise + if (typeof args[0] === 'string' && args[0]?.includes('Learn more about setting up an instrumentation hook')) { return; } diff --git a/packages/nextjs/test/config/valueInjectionLoader.test.ts b/packages/nextjs/test/config/valueInjectionLoader.test.ts new file mode 100644 index 000000000000..2d810ad87c5a --- /dev/null +++ b/packages/nextjs/test/config/valueInjectionLoader.test.ts @@ -0,0 +1,146 @@ +import type { LoaderThis } from '../../src/config/loaders/types'; +import type { ValueInjectionLoaderOptions } from '../../src/config/loaders/valueInjectionLoader'; +import valueInjectionLoader from '../../src/config/loaders/valueInjectionLoader'; + +const defaultLoaderThis = { + addDependency: () => undefined, + async: () => undefined, + cacheable: () => undefined, + callback: () => undefined, +}; + +const loaderThis = { + ...defaultLoaderThis, + resourcePath: './client.config.ts', + getOptions() { + return { + values: { + foo: 'bar', + }, + }; + }, +} satisfies LoaderThis; + +describe('valueInjectionLoader', () => { + it('should correctly insert values for basic config', () => { + const userCode = ` + import * as Sentry from '@sentry/nextjs'; + Sentry.init(); + `; + + const result = valueInjectionLoader.call(loaderThis, userCode); + + expect(result).toMatchSnapshot(); + expect(result).toMatch(';globalThis["foo"] = "bar";'); + }); + + it('should correctly insert values with directive', () => { + const userCode = ` + "use client" + import * as Sentry from '@sentry/nextjs'; + Sentry.init(); + `; + + const result = valueInjectionLoader.call(loaderThis, userCode); + + expect(result).toMatchSnapshot(); + expect(result).toMatch(';globalThis["foo"] = "bar";'); + }); + + it('should correctly insert values with directive and semicolon', () => { + const userCode = ` + "use client"; + import * as Sentry from '@sentry/nextjs'; + Sentry.init(); + `; + + const result = valueInjectionLoader.call(loaderThis, userCode); + + expect(result).toMatchSnapshot(); + expect(result).toMatch(';globalThis["foo"] = "bar";'); + }); + + it('should correctly insert values with directive and inline comments', () => { + const userCode = ` + // test + "use client"; + import * as Sentry from '@sentry/nextjs'; + Sentry.init(); + `; + + const result = valueInjectionLoader.call(loaderThis, userCode); + + expect(result).toMatchSnapshot(); + expect(result).toMatch(';globalThis["foo"] = "bar";'); + }); + + it('should correctly insert values with directive and block comments', () => { + const userCode = ` + /* test */ + "use client"; + import * as Sentry from '@sentry/nextjs'; + Sentry.init(); + `; + + const result = valueInjectionLoader.call(loaderThis, userCode); + + expect(result).toMatchSnapshot(); + expect(result).toMatch(';globalThis["foo"] = "bar";'); + }); + + it('should correctly insert values with directive and multiline block comments', () => { + const userCode = ` + /* + test + */ + "use client"; + import * as Sentry from '@sentry/nextjs'; + Sentry.init(); + `; + + const result = valueInjectionLoader.call(loaderThis, userCode); + + expect(result).toMatchSnapshot(); + expect(result).toMatch(';globalThis["foo"] = "bar";'); + }); + + it('should correctly insert values with directive and multiline block comments and a bunch of whitespace', () => { + const userCode = ` + /* + test + */ + + + + + "use client"; + + + + import * as Sentry from '@sentry/nextjs'; + Sentry.init(); + `; + + const result = valueInjectionLoader.call(loaderThis, userCode); + + expect(result).toMatchSnapshot(); + expect(result).toMatch(';globalThis["foo"] = "bar";'); + }); + + it('should correctly insert values with a misplaced directive', () => { + const userCode = ` + console.log('This will render the directive useless'); + "use client"; + + + + import * as Sentry from '@sentry/nextjs'; + Sentry.init(); + `; + + const result = valueInjectionLoader.call(loaderThis, userCode); + + expect(result).toMatchSnapshot(); + expect(result).toMatch(';globalThis["foo"] = "bar";'); + }); +}); diff --git a/packages/nextjs/test/config/wrappers.test.ts b/packages/nextjs/test/config/wrappers.test.ts index 284737f4335d..e2928d59016e 100644 --- a/packages/nextjs/test/config/wrappers.test.ts +++ b/packages/nextjs/test/config/wrappers.test.ts @@ -1,13 +1,12 @@ import type { IncomingMessage, ServerResponse } from 'http'; import * as SentryCore from '@sentry/core'; -import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core'; import type { Client } from '@sentry/types'; import { wrapGetInitialPropsWithSentry, wrapGetServerSidePropsWithSentry } from '../../src/common'; const startSpanManualSpy = jest.spyOn(SentryCore, 'startSpanManual'); -describe('data-fetching function wrappers should create spans', () => { +describe('data-fetching function wrappers should not create manual spans', () => { const route = '/tricks/[trickName]'; let req: IncomingMessage; let res: ServerResponse; @@ -35,17 +34,7 @@ describe('data-fetching function wrappers should create spans', () => { const wrappedOriginal = wrapGetServerSidePropsWithSentry(origFunction, route); await wrappedOriginal({ req, res } as any); - expect(startSpanManualSpy).toHaveBeenCalledWith( - expect.objectContaining({ - name: 'getServerSideProps (/tricks/[trickName])', - op: 'function.nextjs', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs', - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - }, - }), - expect.any(Function), - ); + expect(startSpanManualSpy).not.toHaveBeenCalled(); }); test('wrapGetInitialPropsWithSentry', async () => { @@ -54,16 +43,44 @@ describe('data-fetching function wrappers should create spans', () => { const wrappedOriginal = wrapGetInitialPropsWithSentry(origFunction); await wrappedOriginal({ req, res, pathname: route } as any); - expect(startSpanManualSpy).toHaveBeenCalledWith( - expect.objectContaining({ - name: 'getInitialProps (/tricks/[trickName])', - op: 'function.nextjs', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs', - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - }, - }), - expect.any(Function), - ); + expect(startSpanManualSpy).not.toHaveBeenCalled(); + }); + + test('wrapped function sets route backfill attribute when called within an active span', async () => { + const mockSetAttribute = jest.fn(); + const mockGetActiveSpan = jest.spyOn(SentryCore, 'getActiveSpan').mockReturnValue({ + setAttribute: mockSetAttribute, + } as any); + const mockGetRootSpan = jest.spyOn(SentryCore, 'getRootSpan').mockReturnValue({ + setAttribute: mockSetAttribute, + } as any); + + const origFunction = jest.fn(async () => ({ props: {} })); + const wrappedOriginal = wrapGetServerSidePropsWithSentry(origFunction, route); + + await wrappedOriginal({ req, res } as any); + + expect(mockGetActiveSpan).toHaveBeenCalled(); + expect(mockGetRootSpan).toHaveBeenCalled(); + expect(mockSetAttribute).toHaveBeenCalledWith('sentry.route_backfill', '/tricks/[trickName]'); + }); + + test('wrapped function does not set route backfill attribute for /_error route', async () => { + const mockSetAttribute = jest.fn(); + const mockGetActiveSpan = jest.spyOn(SentryCore, 'getActiveSpan').mockReturnValue({ + setAttribute: mockSetAttribute, + } as any); + const mockGetRootSpan = jest.spyOn(SentryCore, 'getRootSpan').mockReturnValue({ + setAttribute: mockSetAttribute, + } as any); + + const origFunction = jest.fn(async () => ({ props: {} })); + const wrappedOriginal = wrapGetServerSidePropsWithSentry(origFunction, '/_error'); + + await wrappedOriginal({ req, res } as any); + + expect(mockGetActiveSpan).toHaveBeenCalled(); + expect(mockGetRootSpan).not.toHaveBeenCalled(); + expect(mockSetAttribute).not.toHaveBeenCalled(); }); }); diff --git a/packages/nextjs/test/edge/edgeWrapperUtils.test.ts b/packages/nextjs/test/edge/edgeWrapperUtils.test.ts deleted file mode 100644 index 029ee9d97fce..000000000000 --- a/packages/nextjs/test/edge/edgeWrapperUtils.test.ts +++ /dev/null @@ -1,109 +0,0 @@ -import * as coreSdk from '@sentry/core'; -import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core'; - -import { withEdgeWrapping } from '../../src/common/utils/edgeWrapperUtils'; - -const origRequest = global.Request; -const origResponse = global.Response; - -// @ts-expect-error Request does not exist on type Global -global.Request = class Request { - headers = { - get() { - return null; - }, - }; -}; - -// @ts-expect-error Response does not exist on type Global -global.Response = class Request {}; - -afterAll(() => { - global.Request = origRequest; - global.Response = origResponse; -}); - -beforeEach(() => { - jest.clearAllMocks(); -}); - -describe('withEdgeWrapping', () => { - it('should return a function that calls the passed function', async () => { - const origFunctionReturnValue = new Response(); - const origFunction = jest.fn(_req => origFunctionReturnValue); - - const wrappedFunction = withEdgeWrapping(origFunction, { - spanDescription: 'some label', - mechanismFunctionName: 'some name', - spanOp: 'some op', - }); - - const returnValue = await wrappedFunction(new Request('https://sentry.io/')); - - expect(returnValue).toBe(origFunctionReturnValue); - expect(origFunction).toHaveBeenCalledTimes(1); - }); - - it('should return a function that calls captureException on error', async () => { - const captureExceptionSpy = jest.spyOn(coreSdk, 'captureException'); - const error = new Error(); - const origFunction = jest.fn(_req => { - throw error; - }); - - const wrappedFunction = withEdgeWrapping(origFunction, { - spanDescription: 'some label', - mechanismFunctionName: 'some name', - spanOp: 'some op', - }); - - await expect(wrappedFunction(new Request('https://sentry.io/'))).rejects.toBe(error); - expect(captureExceptionSpy).toHaveBeenCalledTimes(1); - }); - - it('should return a function that calls trace', async () => { - const startSpanSpy = jest.spyOn(coreSdk, 'startSpan'); - - const request = new Request('https://sentry.io/'); - const origFunction = jest.fn(_req => new Response()); - - const wrappedFunction = withEdgeWrapping(origFunction, { - spanDescription: 'some label', - mechanismFunctionName: 'some name', - spanOp: 'some op', - }); - - await wrappedFunction(request); - - expect(startSpanSpy).toHaveBeenCalledTimes(1); - expect(startSpanSpy).toHaveBeenCalledWith( - expect.objectContaining({ - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [coreSdk.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs.withEdgeWrapping', - }, - name: 'some label', - op: 'some op', - }), - expect.any(Function), - ); - - expect(coreSdk.getIsolationScope().getScopeData().sdkProcessingMetadata).toEqual({ - request: { headers: {} }, - }); - }); - - it("should return a function that doesn't crash when req isn't passed", async () => { - const origFunctionReturnValue = new Response(); - const origFunction = jest.fn(() => origFunctionReturnValue); - - const wrappedFunction = withEdgeWrapping(origFunction, { - spanDescription: 'some label', - mechanismFunctionName: 'some name', - spanOp: 'some op', - }); - - await expect(wrappedFunction()).resolves.toBe(origFunctionReturnValue); - expect(origFunction).toHaveBeenCalledTimes(1); - }); -}); diff --git a/packages/nextjs/test/edge/withSentryAPI.test.ts b/packages/nextjs/test/edge/withSentryAPI.test.ts index 6e24eca21bfe..11449da0e1ef 100644 --- a/packages/nextjs/test/edge/withSentryAPI.test.ts +++ b/packages/nextjs/test/edge/withSentryAPI.test.ts @@ -1,6 +1,3 @@ -import * as coreSdk from '@sentry/core'; -import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core'; - import { wrapApiHandlerWithSentry } from '../../src/edge'; const origRequest = global.Request; @@ -31,53 +28,16 @@ afterAll(() => { global.Response = origResponse; }); -const startSpanSpy = jest.spyOn(coreSdk, 'startSpan'); - afterEach(() => { jest.clearAllMocks(); }); describe('wrapApiHandlerWithSentry', () => { - it('should return a function that calls trace', async () => { - const request = new Request('https://sentry.io/'); - const origFunction = jest.fn(_req => new Response()); - - const wrappedFunction = wrapApiHandlerWithSentry(origFunction, '/user/[userId]/post/[postId]'); - - await wrappedFunction(request); - - expect(startSpanSpy).toHaveBeenCalledTimes(1); - expect(startSpanSpy).toHaveBeenCalledWith( - expect.objectContaining({ - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs.withEdgeWrapping', - }, - name: 'POST /user/[userId]/post/[postId]', - op: 'http.server', - }), - expect.any(Function), - ); - }); - - it('should return a function that calls trace without throwing when no request is passed', async () => { + it('should return a function that does not throw when no request is passed', async () => { const origFunction = jest.fn(() => new Response()); const wrappedFunction = wrapApiHandlerWithSentry(origFunction, '/user/[userId]/post/[postId]'); await wrappedFunction(); - - expect(startSpanSpy).toHaveBeenCalledTimes(1); - expect(startSpanSpy).toHaveBeenCalledWith( - expect.objectContaining({ - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [coreSdk.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs.withEdgeWrapping', - }, - name: 'handler (/user/[userId]/post/[postId])', - op: 'http.server', - }), - expect.any(Function), - ); }); }); diff --git a/packages/node/package.json b/packages/node/package.json index 7d0239317d90..a4d3bdd3e457 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -70,17 +70,17 @@ "@opentelemetry/core": "^1.25.1", "@opentelemetry/instrumentation": "^0.53.0", "@opentelemetry/instrumentation-amqplib": "^0.42.0", - "@opentelemetry/instrumentation-connect": "0.39.0", + "@opentelemetry/instrumentation-connect": "0.40.0", "@opentelemetry/instrumentation-dataloader": "0.12.0", - "@opentelemetry/instrumentation-express": "0.43.0", + "@opentelemetry/instrumentation-express": "0.44.0", "@opentelemetry/instrumentation-fastify": "0.40.0", - "@opentelemetry/instrumentation-fs": "0.15.0", + "@opentelemetry/instrumentation-fs": "0.16.0", "@opentelemetry/instrumentation-generic-pool": "0.39.0", "@opentelemetry/instrumentation-graphql": "0.43.0", "@opentelemetry/instrumentation-hapi": "0.41.0", "@opentelemetry/instrumentation-http": "0.53.0", "@opentelemetry/instrumentation-ioredis": "0.43.0", - "@opentelemetry/instrumentation-kafkajs": "0.3.0", + "@opentelemetry/instrumentation-kafkajs": "0.4.0", "@opentelemetry/instrumentation-koa": "0.43.0", "@opentelemetry/instrumentation-lru-memoizer": "0.40.0", "@opentelemetry/instrumentation-mongodb": "0.47.0", diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index f77ef88548f9..2e658f7abc36 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -31,6 +31,7 @@ export { spotlightIntegration } from './integrations/spotlight'; export { genericPoolIntegration } from './integrations/tracing/genericPool'; export { dataloaderIntegration } from './integrations/tracing/dataloader'; export { amqplibIntegration } from './integrations/tracing/amqplib'; +export { processThreadBreadcrumbIntegration } from './integrations/processThread'; export { SentryContextManager } from './otel/contextManager'; export { generateInstrumentOnce } from './otel/instrument'; diff --git a/packages/node/src/integrations/diagnostic_channel.d.ts b/packages/node/src/integrations/diagnostic_channel.d.ts new file mode 100644 index 000000000000..abf3649a617f --- /dev/null +++ b/packages/node/src/integrations/diagnostic_channel.d.ts @@ -0,0 +1,556 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/ban-types */ +/* eslint-disable @typescript-eslint/explicit-member-accessibility */ + +/** + * The `node:diagnostics_channel` module provides an API to create named channels + * to report arbitrary message data for diagnostics purposes. + * + * It can be accessed using: + * + * ```js + * import diagnostics_channel from 'node:diagnostics_channel'; + * ``` + * + * It is intended that a module writer wanting to report diagnostics messages + * will create one or many top-level channels to report messages through. + * Channels may also be acquired at runtime but it is not encouraged + * due to the additional overhead of doing so. Channels may be exported for + * convenience, but as long as the name is known it can be acquired anywhere. + * + * If you intend for your module to produce diagnostics data for others to + * consume it is recommended that you include documentation of what named + * channels are used along with the shape of the message data. Channel names + * should generally include the module name to avoid collisions with data from + * other modules. + * @since v15.1.0, v14.17.0 + * @see [source](https://github.com/nodejs/node/blob/v22.x/lib/diagnostics_channel.js) + */ +declare module 'diagnostics_channel' { + import type { AsyncLocalStorage } from 'node:async_hooks'; + /** + * Check if there are active subscribers to the named channel. This is helpful if + * the message you want to send might be expensive to prepare. + * + * This API is optional but helpful when trying to publish messages from very + * performance-sensitive code. + * + * ```js + * import diagnostics_channel from 'node:diagnostics_channel'; + * + * if (diagnostics_channel.hasSubscribers('my-channel')) { + * // There are subscribers, prepare and publish message + * } + * ``` + * @since v15.1.0, v14.17.0 + * @param name The channel name + * @return If there are active subscribers + */ + function hasSubscribers(name: string | symbol): boolean; + /** + * This is the primary entry-point for anyone wanting to publish to a named + * channel. It produces a channel object which is optimized to reduce overhead at + * publish time as much as possible. + * + * ```js + * import diagnostics_channel from 'node:diagnostics_channel'; + * + * const channel = diagnostics_channel.channel('my-channel'); + * ``` + * @since v15.1.0, v14.17.0 + * @param name The channel name + * @return The named channel object + */ + function channel(name: string | symbol): Channel; + type ChannelListener = (message: unknown, name: string | symbol) => void; + /** + * Register a message handler to subscribe to this channel. This message handler + * will be run synchronously whenever a message is published to the channel. Any + * errors thrown in the message handler will trigger an `'uncaughtException'`. + * + * ```js + * import diagnostics_channel from 'node:diagnostics_channel'; + * + * diagnostics_channel.subscribe('my-channel', (message, name) => { + * // Received data + * }); + * ``` + * @since v18.7.0, v16.17.0 + * @param name The channel name + * @param onMessage The handler to receive channel messages + */ + function subscribe(name: string | symbol, onMessage: ChannelListener): void; + /** + * Remove a message handler previously registered to this channel with {@link subscribe}. + * + * ```js + * import diagnostics_channel from 'node:diagnostics_channel'; + * + * function onMessage(message, name) { + * // Received data + * } + * + * diagnostics_channel.subscribe('my-channel', onMessage); + * + * diagnostics_channel.unsubscribe('my-channel', onMessage); + * ``` + * @since v18.7.0, v16.17.0 + * @param name The channel name + * @param onMessage The previous subscribed handler to remove + * @return `true` if the handler was found, `false` otherwise. + */ + function unsubscribe(name: string | symbol, onMessage: ChannelListener): boolean; + /** + * Creates a `TracingChannel` wrapper for the given `TracingChannel Channels`. If a name is given, the corresponding tracing + * channels will be created in the form of `tracing:${name}:${eventType}` where `eventType` corresponds to the types of `TracingChannel Channels`. + * + * ```js + * import diagnostics_channel from 'node:diagnostics_channel'; + * + * const channelsByName = diagnostics_channel.tracingChannel('my-channel'); + * + * // or... + * + * const channelsByCollection = diagnostics_channel.tracingChannel({ + * start: diagnostics_channel.channel('tracing:my-channel:start'), + * end: diagnostics_channel.channel('tracing:my-channel:end'), + * asyncStart: diagnostics_channel.channel('tracing:my-channel:asyncStart'), + * asyncEnd: diagnostics_channel.channel('tracing:my-channel:asyncEnd'), + * error: diagnostics_channel.channel('tracing:my-channel:error'), + * }); + * ``` + * @since v19.9.0 + * @experimental + * @param nameOrChannels Channel name or object containing all the `TracingChannel Channels` + * @return Collection of channels to trace with + */ + function tracingChannel< + StoreType = unknown, + ContextType extends object = StoreType extends object ? StoreType : object, + >(nameOrChannels: string | TracingChannelCollection): TracingChannel; + /** + * The class `Channel` represents an individual named channel within the data + * pipeline. It is used to track subscribers and to publish messages when there + * are subscribers present. It exists as a separate object to avoid channel + * lookups at publish time, enabling very fast publish speeds and allowing + * for heavy use while incurring very minimal cost. Channels are created with {@link channel}, constructing a channel directly + * with `new Channel(name)` is not supported. + * @since v15.1.0, v14.17.0 + */ + class Channel { + readonly name: string | symbol; + /** + * Check if there are active subscribers to this channel. This is helpful if + * the message you want to send might be expensive to prepare. + * + * This API is optional but helpful when trying to publish messages from very + * performance-sensitive code. + * + * ```js + * import diagnostics_channel from 'node:diagnostics_channel'; + * + * const channel = diagnostics_channel.channel('my-channel'); + * + * if (channel.hasSubscribers) { + * // There are subscribers, prepare and publish message + * } + * ``` + * @since v15.1.0, v14.17.0 + */ + readonly hasSubscribers: boolean; + private constructor(name: string | symbol); + /** + * Publish a message to any subscribers to the channel. This will trigger + * message handlers synchronously so they will execute within the same context. + * + * ```js + * import diagnostics_channel from 'node:diagnostics_channel'; + * + * const channel = diagnostics_channel.channel('my-channel'); + * + * channel.publish({ + * some: 'message', + * }); + * ``` + * @since v15.1.0, v14.17.0 + * @param message The message to send to the channel subscribers + */ + publish(message: unknown): void; + /** + * Register a message handler to subscribe to this channel. This message handler + * will be run synchronously whenever a message is published to the channel. Any + * errors thrown in the message handler will trigger an `'uncaughtException'`. + * + * ```js + * import diagnostics_channel from 'node:diagnostics_channel'; + * + * const channel = diagnostics_channel.channel('my-channel'); + * + * channel.subscribe((message, name) => { + * // Received data + * }); + * ``` + * @since v15.1.0, v14.17.0 + * @deprecated Since v18.7.0,v16.17.0 - Use {@link subscribe(name, onMessage)} + * @param onMessage The handler to receive channel messages + */ + subscribe(onMessage: ChannelListener): void; + /** + * Remove a message handler previously registered to this channel with `channel.subscribe(onMessage)`. + * + * ```js + * import diagnostics_channel from 'node:diagnostics_channel'; + * + * const channel = diagnostics_channel.channel('my-channel'); + * + * function onMessage(message, name) { + * // Received data + * } + * + * channel.subscribe(onMessage); + * + * channel.unsubscribe(onMessage); + * ``` + * @since v15.1.0, v14.17.0 + * @deprecated Since v18.7.0,v16.17.0 - Use {@link unsubscribe(name, onMessage)} + * @param onMessage The previous subscribed handler to remove + * @return `true` if the handler was found, `false` otherwise. + */ + unsubscribe(onMessage: ChannelListener): void; + /** + * When `channel.runStores(context, ...)` is called, the given context data + * will be applied to any store bound to the channel. If the store has already been + * bound the previous `transform` function will be replaced with the new one. + * The `transform` function may be omitted to set the given context data as the + * context directly. + * + * ```js + * import diagnostics_channel from 'node:diagnostics_channel'; + * import { AsyncLocalStorage } from 'node:async_hooks'; + * + * const store = new AsyncLocalStorage(); + * + * const channel = diagnostics_channel.channel('my-channel'); + * + * channel.bindStore(store, (data) => { + * return { data }; + * }); + * ``` + * @since v19.9.0 + * @experimental + * @param store The store to which to bind the context data + * @param transform Transform context data before setting the store context + */ + bindStore(store: AsyncLocalStorage, transform?: (context: ContextType) => StoreType): void; + /** + * Remove a message handler previously registered to this channel with `channel.bindStore(store)`. + * + * ```js + * import diagnostics_channel from 'node:diagnostics_channel'; + * import { AsyncLocalStorage } from 'node:async_hooks'; + * + * const store = new AsyncLocalStorage(); + * + * const channel = diagnostics_channel.channel('my-channel'); + * + * channel.bindStore(store); + * channel.unbindStore(store); + * ``` + * @since v19.9.0 + * @experimental + * @param store The store to unbind from the channel. + * @return `true` if the store was found, `false` otherwise. + */ + unbindStore(store: any): void; + /** + * Applies the given data to any AsyncLocalStorage instances bound to the channel + * for the duration of the given function, then publishes to the channel within + * the scope of that data is applied to the stores. + * + * If a transform function was given to `channel.bindStore(store)` it will be + * applied to transform the message data before it becomes the context value for + * the store. The prior storage context is accessible from within the transform + * function in cases where context linking is required. + * + * The context applied to the store should be accessible in any async code which + * continues from execution which began during the given function, however + * there are some situations in which `context loss` may occur. + * + * ```js + * import diagnostics_channel from 'node:diagnostics_channel'; + * import { AsyncLocalStorage } from 'node:async_hooks'; + * + * const store = new AsyncLocalStorage(); + * + * const channel = diagnostics_channel.channel('my-channel'); + * + * channel.bindStore(store, (message) => { + * const parent = store.getStore(); + * return new Span(message, parent); + * }); + * channel.runStores({ some: 'message' }, () => { + * store.getStore(); // Span({ some: 'message' }) + * }); + * ``` + * @since v19.9.0 + * @experimental + * @param context Message to send to subscribers and bind to stores + * @param fn Handler to run within the entered storage context + * @param thisArg The receiver to be used for the function call. + * @param args Optional arguments to pass to the function. + */ + runStores(): void; + } + interface TracingChannelSubscribers { + start: (message: ContextType) => void; + end: ( + message: ContextType & { + error?: unknown; + result?: unknown; + }, + ) => void; + asyncStart: ( + message: ContextType & { + error?: unknown; + result?: unknown; + }, + ) => void; + asyncEnd: ( + message: ContextType & { + error?: unknown; + result?: unknown; + }, + ) => void; + error: ( + message: ContextType & { + error: unknown; + }, + ) => void; + } + interface TracingChannelCollection { + start: Channel; + end: Channel; + asyncStart: Channel; + asyncEnd: Channel; + error: Channel; + } + /** + * The class `TracingChannel` is a collection of `TracingChannel Channels` which + * together express a single traceable action. It is used to formalize and + * simplify the process of producing events for tracing application flow. {@link tracingChannel} is used to construct a `TracingChannel`. As with `Channel` it is recommended to create and reuse a + * single `TracingChannel` at the top-level of the file rather than creating them + * dynamically. + * @since v19.9.0 + * @experimental + */ + class TracingChannel implements TracingChannelCollection { + start: Channel; + end: Channel; + asyncStart: Channel; + asyncEnd: Channel; + error: Channel; + /** + * Helper to subscribe a collection of functions to the corresponding channels. + * This is the same as calling `channel.subscribe(onMessage)` on each channel + * individually. + * + * ```js + * import diagnostics_channel from 'node:diagnostics_channel'; + * + * const channels = diagnostics_channel.tracingChannel('my-channel'); + * + * channels.subscribe({ + * start(message) { + * // Handle start message + * }, + * end(message) { + * // Handle end message + * }, + * asyncStart(message) { + * // Handle asyncStart message + * }, + * asyncEnd(message) { + * // Handle asyncEnd message + * }, + * error(message) { + * // Handle error message + * }, + * }); + * ``` + * @since v19.9.0 + * @experimental + * @param subscribers Set of `TracingChannel Channels` subscribers + */ + subscribe(subscribers: TracingChannelSubscribers): void; + /** + * Helper to unsubscribe a collection of functions from the corresponding channels. + * This is the same as calling `channel.unsubscribe(onMessage)` on each channel + * individually. + * + * ```js + * import diagnostics_channel from 'node:diagnostics_channel'; + * + * const channels = diagnostics_channel.tracingChannel('my-channel'); + * + * channels.unsubscribe({ + * start(message) { + * // Handle start message + * }, + * end(message) { + * // Handle end message + * }, + * asyncStart(message) { + * // Handle asyncStart message + * }, + * asyncEnd(message) { + * // Handle asyncEnd message + * }, + * error(message) { + * // Handle error message + * }, + * }); + * ``` + * @since v19.9.0 + * @experimental + * @param subscribers Set of `TracingChannel Channels` subscribers + * @return `true` if all handlers were successfully unsubscribed, and `false` otherwise. + */ + unsubscribe(subscribers: TracingChannelSubscribers): void; + /** + * Trace a synchronous function call. This will always produce a `start event` and `end event` around the execution and may produce an `error event` if the given function throws an error. + * This will run the given function using `channel.runStores(context, ...)` on the `start` channel which ensures all + * events should have any bound stores set to match this trace context. + * + * To ensure only correct trace graphs are formed, events will only be published if subscribers are present prior to starting the trace. Subscriptions + * which are added after the trace begins will not receive future events from that trace, only future traces will be seen. + * + * ```js + * import diagnostics_channel from 'node:diagnostics_channel'; + * + * const channels = diagnostics_channel.tracingChannel('my-channel'); + * + * channels.traceSync(() => { + * // Do something + * }, { + * some: 'thing', + * }); + * ``` + * @since v19.9.0 + * @experimental + * @param fn Function to wrap a trace around + * @param context Shared object to correlate events through + * @param thisArg The receiver to be used for the function call + * @param args Optional arguments to pass to the function + * @return The return value of the given function + */ + traceSync( + fn: (this: ThisArg, ...args: Args) => any, + context?: ContextType, + thisArg?: ThisArg, + ...args: Args + ): void; + /** + * Trace a promise-returning function call. This will always produce a `start event` and `end event` around the synchronous portion of the + * function execution, and will produce an `asyncStart event` and `asyncEnd event` when a promise continuation is reached. It may also + * produce an `error event` if the given function throws an error or the + * returned promise rejects. This will run the given function using `channel.runStores(context, ...)` on the `start` channel which ensures all + * events should have any bound stores set to match this trace context. + * + * To ensure only correct trace graphs are formed, events will only be published if subscribers are present prior to starting the trace. Subscriptions + * which are added after the trace begins will not receive future events from that trace, only future traces will be seen. + * + * ```js + * import diagnostics_channel from 'node:diagnostics_channel'; + * + * const channels = diagnostics_channel.tracingChannel('my-channel'); + * + * channels.tracePromise(async () => { + * // Do something + * }, { + * some: 'thing', + * }); + * ``` + * @since v19.9.0 + * @experimental + * @param fn Promise-returning function to wrap a trace around + * @param context Shared object to correlate trace events through + * @param thisArg The receiver to be used for the function call + * @param args Optional arguments to pass to the function + * @return Chained from promise returned by the given function + */ + tracePromise( + fn: (this: ThisArg, ...args: Args) => Promise, + context?: ContextType, + thisArg?: ThisArg, + ...args: Args + ): void; + /** + * Trace a callback-receiving function call. This will always produce a `start event` and `end event` around the synchronous portion of the + * function execution, and will produce a `asyncStart event` and `asyncEnd event` around the callback execution. It may also produce an `error event` if the given function throws an error or + * the returned + * promise rejects. This will run the given function using `channel.runStores(context, ...)` on the `start` channel which ensures all + * events should have any bound stores set to match this trace context. + * + * The `position` will be -1 by default to indicate the final argument should + * be used as the callback. + * + * ```js + * import diagnostics_channel from 'node:diagnostics_channel'; + * + * const channels = diagnostics_channel.tracingChannel('my-channel'); + * + * channels.traceCallback((arg1, callback) => { + * // Do something + * callback(null, 'result'); + * }, 1, { + * some: 'thing', + * }, thisArg, arg1, callback); + * ``` + * + * The callback will also be run with `channel.runStores(context, ...)` which + * enables context loss recovery in some cases. + * + * To ensure only correct trace graphs are formed, events will only be published if subscribers are present prior to starting the trace. Subscriptions + * which are added after the trace begins will not receive future events from that trace, only future traces will be seen. + * + * ```js + * import diagnostics_channel from 'node:diagnostics_channel'; + * import { AsyncLocalStorage } from 'node:async_hooks'; + * + * const channels = diagnostics_channel.tracingChannel('my-channel'); + * const myStore = new AsyncLocalStorage(); + * + * // The start channel sets the initial store data to something + * // and stores that store data value on the trace context object + * channels.start.bindStore(myStore, (data) => { + * const span = new Span(data); + * data.span = span; + * return span; + * }); + * + * // Then asyncStart can restore from that data it stored previously + * channels.asyncStart.bindStore(myStore, (data) => { + * return data.span; + * }); + * ``` + * @since v19.9.0 + * @experimental + * @param fn callback using function to wrap a trace around + * @param position Zero-indexed argument position of expected callback + * @param context Shared object to correlate trace events through + * @param thisArg The receiver to be used for the function call + * @param args Optional arguments to pass to the function + * @return The return value of the given function + */ + traceCallback any>( + fn: Fn, + position?: number, + context?: ContextType, + thisArg?: any, + ...args: Parameters + ): void; + } +} +declare module 'node:diagnostics_channel' { + export * from 'diagnostics_channel'; +} diff --git a/packages/node/src/integrations/processThread.ts b/packages/node/src/integrations/processThread.ts new file mode 100644 index 000000000000..870a0dc6df64 --- /dev/null +++ b/packages/node/src/integrations/processThread.ts @@ -0,0 +1,105 @@ +import type { ChildProcess } from 'node:child_process'; +import * as diagnosticsChannel from 'node:diagnostics_channel'; +import type { Worker } from 'node:worker_threads'; +import { addBreadcrumb, defineIntegration } from '@sentry/core'; +import type { IntegrationFn } from '@sentry/types'; + +interface Options { + /** + * Whether to include child process arguments in breadcrumbs data. + * + * @default false + */ + includeChildProcessArgs?: boolean; +} + +const INTEGRATION_NAME = 'ProcessAndThreadBreadcrumbs'; + +const _processThreadBreadcrumbIntegration = ((options: Options = {}) => { + return { + name: INTEGRATION_NAME, + setup(_client) { + // eslint-disable-next-line deprecation/deprecation + diagnosticsChannel.channel('child_process').subscribe((event: unknown) => { + if (event && typeof event === 'object' && 'process' in event) { + captureChildProcessEvents(event.process as ChildProcess, options); + } + }); + + // eslint-disable-next-line deprecation/deprecation + diagnosticsChannel.channel('worker_threads').subscribe((event: unknown) => { + if (event && typeof event === 'object' && 'worker' in event) { + captureWorkerThreadEvents(event.worker as Worker); + } + }); + }, + }; +}) satisfies IntegrationFn; + +/** + * Capture breadcrumbs for child processes and worker threads. + */ +export const processThreadBreadcrumbIntegration = defineIntegration(_processThreadBreadcrumbIntegration); + +function captureChildProcessEvents(child: ChildProcess, options: Options): void { + let hasExited = false; + let data: Record | undefined; + + child + .on('spawn', () => { + // This is Sentry getting macOS OS context + if (child.spawnfile === '/usr/bin/sw_vers') { + hasExited = true; + return; + } + + data = { spawnfile: child.spawnfile }; + if (options.includeChildProcessArgs) { + data.spawnargs = child.spawnargs; + } + }) + .on('exit', code => { + if (!hasExited) { + hasExited = true; + + // Only log for non-zero exit codes + if (code !== null && code !== 0) { + addBreadcrumb({ + category: 'child_process', + message: `Child process exited with code '${code}'`, + level: 'warning', + data, + }); + } + } + }) + .on('error', error => { + if (!hasExited) { + hasExited = true; + + addBreadcrumb({ + category: 'child_process', + message: `Child process errored with '${error.message}'`, + level: 'error', + data, + }); + } + }); +} + +function captureWorkerThreadEvents(worker: Worker): void { + let threadId: number | undefined; + + worker + .on('online', () => { + threadId = worker.threadId; + }) + .on('error', error => { + addBreadcrumb({ + category: 'worker_thread', + message: `Worker thread errored with '${error.message}'`, + level: 'error', + data: { threadId }, + }); + }); +} diff --git a/packages/node/src/sdk/index.ts b/packages/node/src/sdk/index.ts index 7276e809875a..87d61cc908bc 100644 --- a/packages/node/src/sdk/index.ts +++ b/packages/node/src/sdk/index.ts @@ -36,6 +36,7 @@ import { modulesIntegration } from '../integrations/modules'; import { nativeNodeFetchIntegration } from '../integrations/node-fetch'; import { onUncaughtExceptionIntegration } from '../integrations/onuncaughtexception'; import { onUnhandledRejectionIntegration } from '../integrations/onunhandledrejection'; +import { processThreadBreadcrumbIntegration } from '../integrations/processThread'; import { INTEGRATION_NAME as SPOTLIGHT_INTEGRATION_NAME, spotlightIntegration } from '../integrations/spotlight'; import { getAutoPerformanceIntegrations } from '../integrations/tracing'; import { makeNodeTransport } from '../transports'; @@ -71,6 +72,7 @@ export function getDefaultIntegrationsWithoutPerformance(): Integration[] { contextLinesIntegration(), localVariablesIntegration(), nodeContextIntegration(), + processThreadBreadcrumbIntegration(), ...getCjsOnlyIntegrations(), ]; } diff --git a/packages/node/tsconfig.test.json b/packages/node/tsconfig.test.json index 87f6afa06b86..b0c6b000999b 100644 --- a/packages/node/tsconfig.test.json +++ b/packages/node/tsconfig.test.json @@ -1,7 +1,7 @@ { "extends": "./tsconfig.json", - "include": ["test/**/*"], + "include": ["test/**/*", "./src/integrations/diagnostic_channel.d.ts"], "compilerOptions": { // should include all types from `./tsconfig.json` plus types for all test frameworks used diff --git a/packages/nuxt/README.md b/packages/nuxt/README.md index abf7f0d7c594..429fd7487ddc 100644 --- a/packages/nuxt/README.md +++ b/packages/nuxt/README.md @@ -75,7 +75,7 @@ Sentry.init({ ### 4. Server-side setup -Add an `sentry.client.config.ts` file to the root of your project: +Add a `sentry.server.config.ts` file to the root of your project: ```javascript import * as Sentry from '@sentry/nuxt'; @@ -137,16 +137,28 @@ When adding `sentry.server.config.ts`, you might get an error like this: for `@vercel/nft` to fix this. This will add the `hook.mjs` file to your build output ([Nitro issue here](https://github.com/unjs/nitro/issues/2703)). +For `npm`: + ```json "overrides": { "@vercel/nft": "^0.27.4" } ``` -or in `yarn`: +for `yarn`: ```json "resolutions": { "@vercel/nft": "^0.27.4" } ``` + +or for `pnpm`: + +```json +"pnpm": { + "overrides": { + "@vercel/nft": "^0.27.4" + } +} +``` diff --git a/packages/nuxt/package.json b/packages/nuxt/package.json index 9f2bff0bf98c..5936e2d25308 100644 --- a/packages/nuxt/package.json +++ b/packages/nuxt/package.json @@ -47,10 +47,10 @@ "@sentry/core": "8.35.0", "@sentry/node": "8.35.0", "@sentry/opentelemetry": "8.35.0", - "@sentry/rollup-plugin": "2.22.3", + "@sentry/rollup-plugin": "2.22.6", "@sentry/types": "8.35.0", "@sentry/utils": "8.35.0", - "@sentry/vite-plugin": "2.22.3", + "@sentry/vite-plugin": "2.22.6", "@sentry/vue": "8.35.0" }, "devDependencies": { diff --git a/packages/nuxt/src/common/types.ts b/packages/nuxt/src/common/types.ts index 6ba29752a308..5b714968d3ca 100644 --- a/packages/nuxt/src/common/types.ts +++ b/packages/nuxt/src/common/types.ts @@ -1,10 +1,21 @@ import type { init as initNode } from '@sentry/node'; import type { SentryRollupPluginOptions } from '@sentry/rollup-plugin'; import type { SentryVitePluginOptions } from '@sentry/vite-plugin'; -import type { init as initVue } from '@sentry/vue'; +import type { createSentryPiniaPlugin, init as initVue } from '@sentry/vue'; // Omitting 'app' as the Nuxt SDK will add the app instance in the client plugin (users do not have to provide this) -export type SentryNuxtClientOptions = Omit[0] & object, 'app'>; +export type SentryNuxtClientOptions = Omit[0] & object, 'app'> & { + /** + * Control if an existing Pinia store should be monitored. + * Set this to `true` to track with default options or provide your custom Pinia plugin options. + * + * This only works if "@pinia/nuxt" is added to the `modules` array. + * + * @default false + */ + trackPinia?: true | Parameters[0]; +}; + export type SentryNuxtServerOptions = Omit[0] & object, 'app'>; type SourceMapsOptions = { diff --git a/packages/nuxt/src/runtime/plugins/sentry.client.ts b/packages/nuxt/src/runtime/plugins/sentry.client.ts index b89a2fa87a8d..a8b15b937d53 100644 --- a/packages/nuxt/src/runtime/plugins/sentry.client.ts +++ b/packages/nuxt/src/runtime/plugins/sentry.client.ts @@ -1,5 +1,6 @@ import { getClient } from '@sentry/core'; -import { browserTracingIntegration, vueIntegration } from '@sentry/vue'; +import { consoleSandbox } from '@sentry/utils'; +import { browserTracingIntegration, createSentryPiniaPlugin, vueIntegration } from '@sentry/vue'; import { defineNuxtPlugin } from 'nuxt/app'; import { reportNuxtError } from '../utils'; @@ -34,11 +35,12 @@ export default defineNuxtPlugin({ name: 'sentry-client-integrations', dependsOn: ['sentry-client-config'], async setup(nuxtApp) { + const sentryClient = getClient(); + const clientOptions = sentryClient && sentryClient.getOptions(); + // This evaluates to true unless __SENTRY_TRACING__ is text-replaced with "false", in which case everything inside // will get tree-shaken away if (typeof __SENTRY_TRACING__ === 'undefined' || __SENTRY_TRACING__) { - const sentryClient = getClient(); - if (sentryClient && '$router' in nuxtApp) { sentryClient.addIntegration( browserTracingIntegration({ router: nuxtApp.$router as VueRouter, routeLabel: 'path' }), @@ -46,6 +48,23 @@ export default defineNuxtPlugin({ } } + if (clientOptions && 'trackPinia' in clientOptions && clientOptions.trackPinia) { + if ('$pinia' in nuxtApp) { + (nuxtApp.$pinia as { use: (plugin: unknown) => void }).use( + // `trackPinia` is an object with custom options or `true` (pass `undefined` to use default options) + createSentryPiniaPlugin(clientOptions.trackPinia === true ? undefined : clientOptions.trackPinia), + ); + } else { + clientOptions.debug && + consoleSandbox(() => { + // eslint-disable-next-line no-console + console.warn( + '[Sentry] You set `trackPinia`, but the Pinia module was not found. Make sure to add `"@pinia/nuxt"` to your modules array.', + ); + }); + } + } + nuxtApp.hook('app:created', vueApp => { const sentryClient = getClient(); diff --git a/packages/nuxt/src/vite/addServerConfig.ts b/packages/nuxt/src/vite/addServerConfig.ts index 4c12c6fd7dc2..fe86895a76f2 100644 --- a/packages/nuxt/src/vite/addServerConfig.ts +++ b/packages/nuxt/src/vite/addServerConfig.ts @@ -1,7 +1,7 @@ import * as fs from 'fs'; import { createResolver } from '@nuxt/kit'; import type { Nuxt } from '@nuxt/schema'; -import { consoleSandbox } from '@sentry/utils'; +import { consoleSandbox, flatten } from '@sentry/utils'; import type { Nitro } from 'nitropack'; import type { InputPluginOption } from 'rollup'; import type { SentryNuxtModuleOptions } from '../common/types'; @@ -94,7 +94,6 @@ export function addDynamicImportEntryFileWrapper(nitro: Nitro, serverConfigFile: } nitro.options.rollupConfig.plugins.push( - // @ts-expect-error - This is the correct type, but it shows an error because of two different definitions wrapEntryWithDynamicImport(createResolver(nitro.options.srcDir).resolve(`/${serverConfigFile}`)), ); } @@ -120,7 +119,7 @@ function wrapEntryWithDynamicImport(resolvedSentryConfigPath: string): InputPlug return { id: source, moduleSideEffects: true, external: true }; } - if (options.isEntry && !source.includes(`.mjs${SENTRY_WRAPPED_ENTRY}`)) { + if (options.isEntry && source.includes('.mjs') && !source.includes(`.mjs${SENTRY_WRAPPED_ENTRY}`)) { const resolution = await this.resolve(source, importer, options); // If it cannot be resolved or is external, just return it so that Rollup can display an error @@ -130,8 +129,9 @@ function wrapEntryWithDynamicImport(resolvedSentryConfigPath: string): InputPlug moduleInfo.moduleSideEffects = true; - // The key `.` in `exportedBindings` refer to the exports within the file - const exportedFunctions = moduleInfo.exportedBindings?.['.']; + // `exportedBindings` can look like this: `{ '.': [ 'handler' ], './firebase-gen-1.mjs': [ 'server' ] }` + // The key `.` refers to exports within the current file, while other keys show from where exports were imported first. + const exportedFunctions = flatten(Object.values(moduleInfo.exportedBindings || {})); // The enclosing `if` already checks for the suffix in `source`, but a check in `resolution.id` is needed as well to prevent multiple attachment of the suffix return resolution.id.includes(`.mjs${SENTRY_WRAPPED_ENTRY}`) diff --git a/packages/opentelemetry/src/spanExporter.ts b/packages/opentelemetry/src/spanExporter.ts index 18c935863b75..5ba28f8b1607 100644 --- a/packages/opentelemetry/src/spanExporter.ts +++ b/packages/opentelemetry/src/spanExporter.ts @@ -105,8 +105,9 @@ export class SentrySpanExporter { * We do this to avoid leaking memory. */ private _cleanupOldSpans(spans = this._finishedSpans): void { + const currentTimeSeconds = Date.now() / 1000; this._finishedSpans = spans.filter(span => { - const shouldDrop = shouldCleanupSpan(span, this._timeout); + const shouldDrop = shouldCleanupSpan(span, currentTimeSeconds, this._timeout); DEBUG_BUILD && shouldDrop && logger.log( @@ -174,8 +175,8 @@ function getCompletedRootNodes(nodes: SpanNode[]): SpanNodeCompleted[] { return nodes.filter(nodeIsCompletedRootNode); } -function shouldCleanupSpan(span: ReadableSpan, maxStartTimeOffsetSeconds: number): boolean { - const cutoff = Date.now() / 1000 - maxStartTimeOffsetSeconds; +function shouldCleanupSpan(span: ReadableSpan, currentTimeSeconds: number, maxStartTimeOffsetSeconds: number): boolean { + const cutoff = currentTimeSeconds - maxStartTimeOffsetSeconds; return spanTimeInputToSeconds(span.startTime) < cutoff; } @@ -345,7 +346,6 @@ function removeSentryAttributes(data: Record): Record { source: 'route', }, ], - [ - "should not do any data parsing when the 'sentry.skip_span_data_inference' attribute is set", - { - 'sentry.skip_span_data_inference': true, - - // All of these should be ignored - [SEMATTRS_HTTP_METHOD]: 'GET', - [SEMATTRS_DB_SYSTEM]: 'mysql', - [SEMATTRS_DB_STATEMENT]: 'SELECT * from users', - }, - 'test name', - undefined, - { - op: undefined, - description: 'test name', - source: 'custom', - data: { - 'sentry.skip_span_data_inference': undefined, - }, - }, - ], ])('%s', (_, attributes, name, kind, expected) => { const actual = parseSpanDescription({ attributes, kind, name } as unknown as Span); expect(actual).toEqual(expected); diff --git a/packages/profiling-node/src/integration.ts b/packages/profiling-node/src/integration.ts index 2eb0a59142ae..1af7b8d82dea 100644 --- a/packages/profiling-node/src/integration.ts +++ b/packages/profiling-node/src/integration.ts @@ -11,7 +11,7 @@ import { import type { NodeClient } from '@sentry/node'; import type { Event, IntegrationFn, Profile, ProfileChunk, ProfilingIntegration, Span } from '@sentry/types'; -import { LRUMap, logger, uuid4 } from '@sentry/utils'; +import { LRUMap, consoleSandbox, logger, uuid4 } from '@sentry/utils'; import { CpuProfilerBindings } from './cpu_profiler'; import { DEBUG_BUILD } from './debug-build'; @@ -426,13 +426,16 @@ class ContinuousProfiler { /** Exported only for tests. */ export const _nodeProfilingIntegration = ((): ProfilingIntegration => { - if (DEBUG_BUILD && ![16, 18, 20, 22].includes(NODE_MAJOR)) { - logger.warn( - `[Profiling] You are using a Node.js version that does not have prebuilt binaries (${NODE_VERSION}).`, - 'The @sentry/profiling-node package only has prebuilt support for the following LTS versions of Node.js: 16, 18, 20, 22.', - 'To use the @sentry/profiling-node package with this version of Node.js, you will need to compile the native addon from source.', - 'See: https://github.com/getsentry/sentry-javascript/tree/develop/packages/profiling-node#building-the-package-from-source', - ); + if (![16, 18, 20, 22].includes(NODE_MAJOR)) { + consoleSandbox(() => { + // eslint-disable-next-line no-console + console.warn( + `[Sentry Profiling] You are using a Node.js version that does not have prebuilt binaries (${NODE_VERSION}).`, + 'The @sentry/profiling-node package only has prebuilt support for the following LTS versions of Node.js: 16, 18, 20, 22.', + 'To use the @sentry/profiling-node package with this version of Node.js, you will need to compile the native addon from source.', + 'See: https://github.com/getsentry/sentry-javascript/tree/develop/packages/profiling-node#building-the-package-from-source', + ); + }); } return { diff --git a/packages/remix/package.json b/packages/remix/package.json index 088998855c6e..b389f2e663db 100644 --- a/packages/remix/package.json +++ b/packages/remix/package.json @@ -53,7 +53,7 @@ }, "dependencies": { "@remix-run/router": "1.x", - "@sentry/cli": "^2.35.0", + "@sentry/cli": "^2.37.0", "@sentry/core": "8.35.0", "@sentry/node": "8.35.0", "@sentry/opentelemetry": "8.35.0", diff --git a/packages/remix/playwright.config.ts b/packages/remix/playwright.config.ts index a1570f27f50d..142272a44740 100644 --- a/packages/remix/playwright.config.ts +++ b/packages/remix/playwright.config.ts @@ -8,7 +8,7 @@ const config: PlaywrightTestConfig = { }, // Run tests inside of a single file in parallel fullyParallel: true, - reporter: process.env.CI ? [['line'], ['junit', { outputFile: 'results.junit.xml' }]] : 'list', + reporter: process.env.CI ? [['list'], ['junit', { outputFile: 'results.junit.xml' }]] : 'list', // Use 3 workers on CI, else use defaults (based on available CPU cores) // Note that 3 is a random number selected to work well with our CI setup workers: process.env.CI ? 3 : undefined, diff --git a/packages/remix/test/integration/package.json b/packages/remix/test/integration/package.json index dba3c023903a..4604b8c8b067 100644 --- a/packages/remix/test/integration/package.json +++ b/packages/remix/test/integration/package.json @@ -19,7 +19,7 @@ "@remix-run/dev": "1.17.0", "@types/react": "^17.0.47", "@types/react-dom": "^17.0.17", - "nock": "^13.1.0", + "nock": "^13.5.5", "typescript": "4.9.5" }, "resolutions": { diff --git a/packages/replay-internal/src/integration.ts b/packages/replay-internal/src/integration.ts index 8f70e3099a97..c3c448a3c6f2 100644 --- a/packages/replay-internal/src/integration.ts +++ b/packages/replay-internal/src/integration.ts @@ -114,6 +114,7 @@ export class Replay implements Integration { beforeAddRecordingEvent, beforeErrorSampling, + onError, }: ReplayConfiguration = {}) { this.name = Replay.id; @@ -183,6 +184,7 @@ export class Replay implements Integration { networkResponseHeaders: _getMergedNetworkHeaders(networkResponseHeaders), beforeAddRecordingEvent, beforeErrorSampling, + onError, _experiments, }; diff --git a/packages/replay-internal/test/integration/sendReplayEvent.test.ts b/packages/replay-internal/test/integration/sendReplayEvent.test.ts index a58da34b521c..29f063fa4b94 100644 --- a/packages/replay-internal/test/integration/sendReplayEvent.test.ts +++ b/packages/replay-internal/test/integration/sendReplayEvent.test.ts @@ -28,6 +28,7 @@ describe('Integration | sendReplayEvent', () => { let mockTransportSend: MockTransportSend; let mockSendReplayRequest: MockInstance; let domHandler: DomHandler; + const onError: () => void = vi.fn(); const { record: mockRecord } = mockRrweb(); beforeAll(async () => { @@ -44,6 +45,7 @@ describe('Integration | sendReplayEvent', () => { _experiments: { captureExceptions: true, }, + onError, }, })); @@ -54,6 +56,7 @@ describe('Integration | sendReplayEvent', () => { }); beforeEach(() => { + vi.clearAllMocks(); vi.setSystemTime(new Date(BASE_TIMESTAMP)); mockRecord.takeFullSnapshot.mockClear(); mockTransportSend.mockClear(); @@ -357,8 +360,9 @@ describe('Integration | sendReplayEvent', () => { expect(replay).not.toHaveLastSentReplay(); }); - it('fails to upload data and hits retry max and stops', async () => { + it('fails to upload data, hits retry max, stops, and calls `onError` with the error', async () => { const TEST_EVENT = getTestEventIncremental({ timestamp: BASE_TIMESTAMP }); + const ERROR = new Error('Something bad happened'); const spyHandleException = vi.spyOn(SentryCore, 'captureException'); @@ -369,7 +373,7 @@ describe('Integration | sendReplayEvent', () => { // fail all requests mockSendReplayRequest.mockImplementation(async () => { - throw new Error('Something bad happened'); + throw ERROR; }); mockRecord._emitter(TEST_EVENT); @@ -406,6 +410,8 @@ describe('Integration | sendReplayEvent', () => { // Replay has stopped, no session should exist expect(replay.session).toBe(undefined); expect(replay.isEnabled()).toBe(false); + expect(onError).toHaveBeenCalledTimes(5); + expect(onError).toHaveBeenCalledWith(ERROR); // Events are ignored now, because we stopped mockRecord._emitter(TEST_EVENT); diff --git a/packages/solidstart/package.json b/packages/solidstart/package.json index bdafc4e2738d..dddfd55a9942 100644 --- a/packages/solidstart/package.json +++ b/packages/solidstart/package.json @@ -73,7 +73,7 @@ "@sentry/solid": "8.35.0", "@sentry/types": "8.35.0", "@sentry/utils": "8.35.0", - "@sentry/vite-plugin": "2.22.3" + "@sentry/vite-plugin": "2.22.6" }, "devDependencies": { "@solidjs/router": "^0.13.4", diff --git a/packages/sveltekit/package.json b/packages/sveltekit/package.json index 51c04c80ebd5..fd614c8d00ae 100644 --- a/packages/sveltekit/package.json +++ b/packages/sveltekit/package.json @@ -46,7 +46,7 @@ "@sentry/svelte": "8.35.0", "@sentry/types": "8.35.0", "@sentry/utils": "8.35.0", - "@sentry/vite-plugin": "2.22.3", + "@sentry/vite-plugin": "2.22.6", "magic-string": "0.30.7", "magicast": "0.2.8", "sorcery": "1.0.0" diff --git a/packages/vercel-edge/package.json b/packages/vercel-edge/package.json index 448ae6250f4a..c2e5bc6e1259 100644 --- a/packages/vercel-edge/package.json +++ b/packages/vercel-edge/package.json @@ -39,11 +39,17 @@ "access": "public" }, "dependencies": { + "@opentelemetry/api": "^1.9.0", "@sentry/core": "8.35.0", "@sentry/types": "8.35.0", "@sentry/utils": "8.35.0" }, "devDependencies": { + "@opentelemetry/semantic-conventions": "^1.27.0", + "@opentelemetry/core": "^1.25.1", + "@opentelemetry/resources": "^1.26.0", + "@opentelemetry/sdk-trace-base": "^1.26.0", + "@sentry/opentelemetry": "8.35.0", "@edge-runtime/types": "3.0.1" }, "scripts": { diff --git a/packages/vercel-edge/rollup.npm.config.mjs b/packages/vercel-edge/rollup.npm.config.mjs index 84a06f2fb64a..3cfd779d57f6 100644 --- a/packages/vercel-edge/rollup.npm.config.mjs +++ b/packages/vercel-edge/rollup.npm.config.mjs @@ -1,3 +1,60 @@ -import { makeBaseNPMConfig, makeNPMConfigVariants } from '@sentry-internal/rollup-utils'; +import replace from '@rollup/plugin-replace'; +import { makeBaseNPMConfig, makeNPMConfigVariants, plugins } from '@sentry-internal/rollup-utils'; -export default makeNPMConfigVariants(makeBaseNPMConfig()); +export default makeNPMConfigVariants( + makeBaseNPMConfig({ + entrypoints: ['src/index.ts'], + bundledBuiltins: ['perf_hooks'], + packageSpecificConfig: { + context: 'globalThis', + output: { + preserveModules: false, + }, + plugins: [ + plugins.makeCommonJSPlugin({ transformMixedEsModules: true }), // Needed because various modules in the OTEL toolchain use CJS (require-in-the-middle, shimmer, etc..) + plugins.makeJsonPlugin(), // Needed because `require-in-the-middle` imports json via require + replace({ + preventAssignment: true, + values: { + 'process.argv0': JSON.stringify(''), // needed because otel relies on process.argv0 for the default service name, but that api is not available in the edge runtime. + }, + }), + { + // This plugin is needed because otel imports `performance` from `perf_hooks` and also uses it via the `performance` global. + // Both of these APIs are not available in the edge runtime so we need to define a polyfill. + // Vercel does something similar in the `@vercel/otel` package: https://github.com/vercel/otel/blob/087601ae585cb116bb2b46c211d014520de76c71/packages/otel/build.ts#L62 + name: 'perf-hooks-performance-polyfill', + banner: ` + { + if (globalThis.performance === undefined) { + globalThis.performance = { + timeOrigin: 0, + now: () => Date.now() + }; + } + } + `, + resolveId: source => { + if (source === 'perf_hooks') { + return '\0perf_hooks_sentry_shim'; + } else { + return null; + } + }, + load: id => { + if (id === '\0perf_hooks_sentry_shim') { + return ` + export const performance = { + timeOrigin: 0, + now: () => Date.now() + } + `; + } else { + return null; + } + }, + }, + ], + }, + }), +); diff --git a/packages/vercel-edge/src/async.ts b/packages/vercel-edge/src/async.ts deleted file mode 100644 index dd7432c8e959..000000000000 --- a/packages/vercel-edge/src/async.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { getDefaultCurrentScope, getDefaultIsolationScope, setAsyncContextStrategy } from '@sentry/core'; -import type { Scope } from '@sentry/types'; -import { GLOBAL_OBJ, logger } from '@sentry/utils'; - -import { DEBUG_BUILD } from './debug-build'; - -interface AsyncLocalStorage { - getStore(): T | undefined; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - run(store: T, callback: (...args: TArgs) => R, ...args: TArgs): R; -} - -let asyncStorage: AsyncLocalStorage<{ scope: Scope; isolationScope: Scope }>; - -/** - * Sets the async context strategy to use AsyncLocalStorage which should be available in the edge runtime. - */ -export function setAsyncLocalStorageAsyncContextStrategy(): void { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any - const MaybeGlobalAsyncLocalStorage = (GLOBAL_OBJ as any).AsyncLocalStorage; - - if (!MaybeGlobalAsyncLocalStorage) { - DEBUG_BUILD && - logger.warn( - "Tried to register AsyncLocalStorage async context strategy in a runtime that doesn't support AsyncLocalStorage.", - ); - return; - } - - if (!asyncStorage) { - asyncStorage = new MaybeGlobalAsyncLocalStorage(); - } - - function getScopes(): { scope: Scope; isolationScope: Scope } { - const scopes = asyncStorage.getStore(); - - if (scopes) { - return scopes; - } - - // fallback behavior: - // if, for whatever reason, we can't find scopes on the context here, we have to fix this somehow - return { - scope: getDefaultCurrentScope(), - isolationScope: getDefaultIsolationScope(), - }; - } - - function withScope(callback: (scope: Scope) => T): T { - const scope = getScopes().scope.clone(); - const isolationScope = getScopes().isolationScope; - return asyncStorage.run({ scope, isolationScope }, () => { - return callback(scope); - }); - } - - function withSetScope(scope: Scope, callback: (scope: Scope) => T): T { - const isolationScope = getScopes().isolationScope.clone(); - return asyncStorage.run({ scope, isolationScope }, () => { - return callback(scope); - }); - } - - function withIsolationScope(callback: (isolationScope: Scope) => T): T { - const scope = getScopes().scope; - const isolationScope = getScopes().isolationScope.clone(); - return asyncStorage.run({ scope, isolationScope }, () => { - return callback(isolationScope); - }); - } - - function withSetIsolationScope(isolationScope: Scope, callback: (isolationScope: Scope) => T): T { - const scope = getScopes().scope; - return asyncStorage.run({ scope, isolationScope }, () => { - return callback(isolationScope); - }); - } - - setAsyncContextStrategy({ - withScope, - withSetScope, - withIsolationScope, - withSetIsolationScope, - getCurrentScope: () => getScopes().scope, - getIsolationScope: () => getScopes().isolationScope, - }); -} diff --git a/packages/vercel-edge/src/client.ts b/packages/vercel-edge/src/client.ts index b2c7416130bc..09987eacd030 100644 --- a/packages/vercel-edge/src/client.ts +++ b/packages/vercel-edge/src/client.ts @@ -2,6 +2,7 @@ import type { ServerRuntimeClientOptions } from '@sentry/core'; import { applySdkMetadata } from '@sentry/core'; import { ServerRuntimeClient } from '@sentry/core'; +import type { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; import type { VercelEdgeClientOptions } from './types'; declare const process: { @@ -15,6 +16,8 @@ declare const process: { * @see ServerRuntimeClient for usage documentation. */ export class VercelEdgeClient extends ServerRuntimeClient { + public traceProvider: BasicTracerProvider | undefined; + /** * Creates a new Vercel Edge Runtime SDK instance. * @param options Configuration options for this SDK. @@ -33,4 +36,21 @@ export class VercelEdgeClient extends ServerRuntimeClient { + const provider = this.traceProvider; + const spanProcessor = provider?.activeSpanProcessor; + + if (spanProcessor) { + await spanProcessor.forceFlush(); + } + + if (this.getOptions().sendClientReports) { + this._flushOutcomes(); + } + + return super.flush(timeout); + } } diff --git a/packages/vercel-edge/src/sdk.ts b/packages/vercel-edge/src/sdk.ts index 4e1bed208c34..4fa8415b2184 100644 --- a/packages/vercel-edge/src/sdk.ts +++ b/packages/vercel-edge/src/sdk.ts @@ -1,21 +1,48 @@ import { dedupeIntegration, functionToStringIntegration, + getCurrentScope, getIntegrationsToSetup, + hasTracingEnabled, inboundFiltersIntegration, - initAndBind, linkedErrorsIntegration, requestDataIntegration, } from '@sentry/core'; import type { Client, Integration, Options } from '@sentry/types'; -import { GLOBAL_OBJ, createStackParser, nodeStackLineParser, stackParserFromStackParserOptions } from '@sentry/utils'; +import { + GLOBAL_OBJ, + SDK_VERSION, + createStackParser, + logger, + nodeStackLineParser, + stackParserFromStackParserOptions, +} from '@sentry/utils'; -import { setAsyncLocalStorageAsyncContextStrategy } from './async'; +import { DiagLogLevel, diag } from '@opentelemetry/api'; +import { Resource } from '@opentelemetry/resources'; +import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; +import { + ATTR_SERVICE_NAME, + ATTR_SERVICE_VERSION, + SEMRESATTRS_SERVICE_NAMESPACE, +} from '@opentelemetry/semantic-conventions'; +import { + SentryPropagator, + SentrySampler, + SentrySpanProcessor, + enhanceDscWithOpenTelemetryRootSpanName, + openTelemetrySetupCheck, + setOpenTelemetryContextAsyncContextStrategy, + setupEventContextTrace, + wrapContextManagerClass, +} from '@sentry/opentelemetry'; import { VercelEdgeClient } from './client'; +import { DEBUG_BUILD } from './debug-build'; import { winterCGFetchIntegration } from './integrations/wintercg-fetch'; import { makeEdgeTransport } from './transports'; -import type { VercelEdgeClientOptions, VercelEdgeOptions } from './types'; +import type { VercelEdgeOptions } from './types'; import { getVercelEnv } from './utils/vercel'; +import { AsyncLocalStorageContextManager } from './vendored/async-local-storage-context-manager'; declare const process: { env: Record; @@ -37,7 +64,10 @@ export function getDefaultIntegrations(options: Options): Integration[] { /** Inits the Sentry NextJS SDK on the Edge Runtime. */ export function init(options: VercelEdgeOptions = {}): Client | undefined { - setAsyncLocalStorageAsyncContextStrategy(); + setOpenTelemetryContextAsyncContextStrategy(); + + const scope = getCurrentScope(); + scope.update(options.initialScope); if (options.defaultIntegrations === undefined) { options.defaultIntegrations = getDefaultIntegrations(options); @@ -71,14 +101,108 @@ export function init(options: VercelEdgeOptions = {}): Client | undefined { options.autoSessionTracking = true; } - const clientOptions: VercelEdgeClientOptions = { + const client = new VercelEdgeClient({ ...options, stackParser: stackParserFromStackParserOptions(options.stackParser || nodeStackParser), integrations: getIntegrationsToSetup(options), transport: options.transport || makeEdgeTransport, - }; + }); + // The client is on the current scope, from where it generally is inherited + getCurrentScope().setClient(client); + + client.init(); + + // If users opt-out of this, they _have_ to set up OpenTelemetry themselves + // There is no way to use this SDK without OpenTelemetry! + if (!options.skipOpenTelemetrySetup) { + setupOtel(client); + validateOpenTelemetrySetup(); + } + + enhanceDscWithOpenTelemetryRootSpanName(client); + setupEventContextTrace(client); + + return client; +} + +function validateOpenTelemetrySetup(): void { + if (!DEBUG_BUILD) { + return; + } + + const setup = openTelemetrySetupCheck(); + + const required: ReturnType = ['SentryContextManager', 'SentryPropagator']; + + if (hasTracingEnabled()) { + required.push('SentrySpanProcessor'); + } + + for (const k of required) { + if (!setup.includes(k)) { + logger.error( + `You have to set up the ${k}. Without this, the OpenTelemetry & Sentry integration will not work properly.`, + ); + } + } + + if (!setup.includes('SentrySampler')) { + logger.warn( + 'You have to set up the SentrySampler. Without this, the OpenTelemetry & Sentry integration may still work, but sample rates set for the Sentry SDK will not be respected. If you use a custom sampler, make sure to use `wrapSamplingDecision`.', + ); + } +} + +// exported for tests +// eslint-disable-next-line jsdoc/require-jsdoc +export function setupOtel(client: VercelEdgeClient): void { + if (client.getOptions().debug) { + setupOpenTelemetryLogger(); + } + + // Create and configure NodeTracerProvider + const provider = new BasicTracerProvider({ + sampler: new SentrySampler(client), + resource: new Resource({ + [ATTR_SERVICE_NAME]: 'edge', + // eslint-disable-next-line deprecation/deprecation + [SEMRESATTRS_SERVICE_NAMESPACE]: 'sentry', + [ATTR_SERVICE_VERSION]: SDK_VERSION, + }), + forceFlushTimeoutMillis: 500, + }); + + provider.addSpanProcessor( + new SentrySpanProcessor({ + timeout: client.getOptions().maxSpanWaitDuration, + }), + ); + + const SentryContextManager = wrapContextManagerClass(AsyncLocalStorageContextManager); + + // Initialize the provider + provider.register({ + propagator: new SentryPropagator(), + contextManager: new SentryContextManager(), + }); + + client.traceProvider = provider; +} + +/** + * Setup the OTEL logger to use our own logger. + */ +function setupOpenTelemetryLogger(): void { + const otelLogger = new Proxy(logger as typeof logger & { verbose: (typeof logger)['debug'] }, { + get(target, prop, receiver) { + const actualProp = prop === 'verbose' ? 'debug' : prop; + return Reflect.get(target, actualProp, receiver); + }, + }); - return initAndBind(VercelEdgeClient, clientOptions); + // Disable diag, to ensure this works even if called multiple times + diag.disable(); + diag.setLogger(otelLogger, DiagLogLevel.DEBUG); } /** diff --git a/packages/vercel-edge/src/types.ts b/packages/vercel-edge/src/types.ts index 7544820c75a3..26bc1b911875 100644 --- a/packages/vercel-edge/src/types.ts +++ b/packages/vercel-edge/src/types.ts @@ -33,6 +33,27 @@ export interface BaseVercelEdgeOptions { * */ clientClass?: typeof VercelEdgeClient; + /** + * If this is set to true, the SDK will not set up OpenTelemetry automatically. + * In this case, you _have_ to ensure to set it up correctly yourself, including: + * * The `SentrySpanProcessor` + * * The `SentryPropagator` + * * The `SentryContextManager` + * * The `SentrySampler` + */ + skipOpenTelemetrySetup?: boolean; + + /** + * The max. duration in seconds that the SDK will wait for parent spans to be finished before discarding a span. + * The SDK will automatically clean up spans that have no finished parent after this duration. + * This is necessary to prevent memory leaks in case of parent spans that are never finished or otherwise dropped/missing. + * However, if you have very long-running spans in your application, a shorter duration might cause spans to be discarded too early. + * In this case, you can increase this duration to a value that fits your expected data. + * + * Defaults to 300 seconds (5 minutes). + */ + maxSpanWaitDuration?: number; + /** Callback that is executed when a fatal global error occurs. */ onFatalError?(this: void, error: Error): void; } diff --git a/packages/vercel-edge/src/vendored/abstract-async-hooks-context-manager.ts b/packages/vercel-edge/src/vendored/abstract-async-hooks-context-manager.ts new file mode 100644 index 000000000000..883e9e43cf54 --- /dev/null +++ b/packages/vercel-edge/src/vendored/abstract-async-hooks-context-manager.ts @@ -0,0 +1,234 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * NOTICE from the Sentry authors: + * - Code vendored from: https://github.com/open-telemetry/opentelemetry-js/blob/6515ed8098333646a63a74a8c0150cc2daf520db/packages/opentelemetry-context-async-hooks/src/AbstractAsyncHooksContextManager.ts + * - Modifications: + * - Added lint rules + * - Modified bind() method not to rely on Node.js specific APIs + */ + +/* eslint-disable @typescript-eslint/explicit-member-accessibility */ +/* eslint-disable @typescript-eslint/member-ordering */ +/* eslint-disable jsdoc/require-jsdoc */ +/* eslint-disable @typescript-eslint/ban-types */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable prefer-rest-params */ +/* eslint-disable @typescript-eslint/no-dynamic-delete */ +/* eslint-disable @typescript-eslint/unbound-method */ +/* eslint-disable @typescript-eslint/no-this-alias */ + +import type { EventEmitter } from 'events'; +import type { Context, ContextManager } from '@opentelemetry/api'; + +type Func = (...args: unknown[]) => T; + +/** + * Store a map for each event of all original listeners and their "patched" + * version. So when a listener is removed by the user, the corresponding + * patched function will be also removed. + */ +interface PatchMap { + [name: string]: WeakMap, Func>; +} + +const ADD_LISTENER_METHODS = [ + 'addListener' as const, + 'on' as const, + 'once' as const, + 'prependListener' as const, + 'prependOnceListener' as const, +]; + +export abstract class AbstractAsyncHooksContextManager implements ContextManager { + abstract active(): Context; + + abstract with ReturnType>( + context: Context, + fn: F, + thisArg?: ThisParameterType, + ...args: A + ): ReturnType; + + abstract enable(): this; + + abstract disable(): this; + + /** + * Binds a the certain context or the active one to the target function and then returns the target + * @param context A context (span) to be bind to target + * @param target a function or event emitter. When target or one of its callbacks is called, + * the provided context will be used as the active context for the duration of the call. + */ + bind(context: Context, target: T): T { + if (typeof target === 'object' && target !== null && 'on' in target) { + return this._bindEventEmitter(context, target as unknown as EventEmitter) as T; + } + + if (typeof target === 'function') { + return this._bindFunction(context, target); + } + return target; + } + + private _bindFunction(context: Context, target: T): T { + const manager = this; + const contextWrapper = function (this: never, ...args: unknown[]) { + return manager.with(context, () => target.apply(this, args)); + }; + Object.defineProperty(contextWrapper, 'length', { + enumerable: false, + configurable: true, + writable: false, + value: target.length, + }); + /** + * It isn't possible to tell Typescript that contextWrapper is the same as T + * so we forced to cast as any here. + */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return contextWrapper as any; + } + + /** + * By default, EventEmitter call their callback with their context, which we do + * not want, instead we will bind a specific context to all callbacks that + * go through it. + * @param context the context we want to bind + * @param ee EventEmitter an instance of EventEmitter to patch + */ + private _bindEventEmitter(context: Context, ee: T): T { + const map = this._getPatchMap(ee); + if (map !== undefined) return ee; + this._createPatchMap(ee); + + // patch methods that add a listener to propagate context + ADD_LISTENER_METHODS.forEach(methodName => { + if (ee[methodName] === undefined) return; + ee[methodName] = this._patchAddListener(ee, ee[methodName], context); + }); + // patch methods that remove a listener + if (typeof ee.removeListener === 'function') { + ee.removeListener = this._patchRemoveListener(ee, ee.removeListener); + } + if (typeof ee.off === 'function') { + ee.off = this._patchRemoveListener(ee, ee.off); + } + // patch method that remove all listeners + if (typeof ee.removeAllListeners === 'function') { + ee.removeAllListeners = this._patchRemoveAllListeners(ee, ee.removeAllListeners); + } + return ee; + } + + /** + * Patch methods that remove a given listener so that we match the "patched" + * version of that listener (the one that propagate context). + * @param ee EventEmitter instance + * @param original reference to the patched method + */ + private _patchRemoveListener(ee: EventEmitter, original: Function) { + const contextManager = this; + return function (this: never, event: string, listener: Func) { + const events = contextManager._getPatchMap(ee)?.[event]; + if (events === undefined) { + return original.call(this, event, listener); + } + const patchedListener = events.get(listener); + return original.call(this, event, patchedListener || listener); + }; + } + + /** + * Patch methods that remove all listeners so we remove our + * internal references for a given event. + * @param ee EventEmitter instance + * @param original reference to the patched method + */ + private _patchRemoveAllListeners(ee: EventEmitter, original: Function) { + const contextManager = this; + return function (this: never, event: string) { + const map = contextManager._getPatchMap(ee); + if (map !== undefined) { + if (arguments.length === 0) { + contextManager._createPatchMap(ee); + } else if (map[event] !== undefined) { + delete map[event]; + } + } + return original.apply(this, arguments); + }; + } + + /** + * Patch methods on an event emitter instance that can add listeners so we + * can force them to propagate a given context. + * @param ee EventEmitter instance + * @param original reference to the patched method + * @param [context] context to propagate when calling listeners + */ + private _patchAddListener(ee: EventEmitter, original: Function, context: Context) { + const contextManager = this; + return function (this: never, event: string, listener: Func) { + /** + * This check is required to prevent double-wrapping the listener. + * The implementation for ee.once wraps the listener and calls ee.on. + * Without this check, we would wrap that wrapped listener. + * This causes an issue because ee.removeListener depends on the onceWrapper + * to properly remove the listener. If we wrap their wrapper, we break + * that detection. + */ + if (contextManager._wrapped) { + return original.call(this, event, listener); + } + let map = contextManager._getPatchMap(ee); + if (map === undefined) { + map = contextManager._createPatchMap(ee); + } + let listeners = map[event]; + if (listeners === undefined) { + listeners = new WeakMap(); + map[event] = listeners; + } + const patchedListener = contextManager.bind(context, listener); + // store a weak reference of the user listener to ours + listeners.set(listener, patchedListener); + + /** + * See comment at the start of this function for the explanation of this property. + */ + contextManager._wrapped = true; + try { + return original.call(this, event, patchedListener); + } finally { + contextManager._wrapped = false; + } + }; + } + + private _createPatchMap(ee: EventEmitter): PatchMap { + const map = Object.create(null); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (ee as any)[this._kOtListeners] = map; + return map; + } + private _getPatchMap(ee: EventEmitter): PatchMap | undefined { + return (ee as never)[this._kOtListeners]; + } + + private readonly _kOtListeners = Symbol('OtListeners'); + private _wrapped = false; +} diff --git a/packages/vercel-edge/src/vendored/async-local-storage-context-manager.ts b/packages/vercel-edge/src/vendored/async-local-storage-context-manager.ts new file mode 100644 index 000000000000..99520a3c0362 --- /dev/null +++ b/packages/vercel-edge/src/vendored/async-local-storage-context-manager.ts @@ -0,0 +1,89 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * NOTICE from the Sentry authors: + * - Code vendored from: https://github.com/open-telemetry/opentelemetry-js/blob/6515ed8098333646a63a74a8c0150cc2daf520db/packages/opentelemetry-context-async-hooks/src/AbstractAsyncHooksContextManager.ts + * - Modifications: + * - Added lint rules + * - Modified import path to AbstractAsyncHooksContextManager + * - Added Sentry logging + * - Modified constructor to access AsyncLocalStorage class from global object instead of the Node.js API + */ + +/* eslint-disable @typescript-eslint/explicit-member-accessibility */ +/* eslint-disable jsdoc/require-jsdoc */ + +import type { Context } from '@opentelemetry/api'; +import { ROOT_CONTEXT } from '@opentelemetry/api'; + +import { GLOBAL_OBJ, logger } from '@sentry/utils'; +import type { AsyncLocalStorage } from 'async_hooks'; +import { DEBUG_BUILD } from '../debug-build'; +import { AbstractAsyncHooksContextManager } from './abstract-async-hooks-context-manager'; + +export class AsyncLocalStorageContextManager extends AbstractAsyncHooksContextManager { + private _asyncLocalStorage: AsyncLocalStorage; + + constructor() { + super(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any + const MaybeGlobalAsyncLocalStorageConstructor = (GLOBAL_OBJ as any).AsyncLocalStorage; + + if (!MaybeGlobalAsyncLocalStorageConstructor) { + DEBUG_BUILD && + logger.warn( + "Tried to register AsyncLocalStorage async context strategy in a runtime that doesn't support AsyncLocalStorage.", + ); + + // @ts-expect-error Vendored type shenanigans + this._asyncLocalStorage = { + getStore() { + return undefined; + }, + run(_store, callback, ...args) { + return callback.apply(this, args); + }, + disable() { + // noop + }, + }; + } else { + this._asyncLocalStorage = new MaybeGlobalAsyncLocalStorageConstructor(); + } + } + + active(): Context { + return this._asyncLocalStorage.getStore() ?? ROOT_CONTEXT; + } + + with ReturnType>( + context: Context, + fn: F, + thisArg?: ThisParameterType, + ...args: A + ): ReturnType { + const cb = thisArg == null ? fn : fn.bind(thisArg); + return this._asyncLocalStorage.run(context, cb as never, ...args); + } + + enable(): this { + return this; + } + + disable(): this { + this._asyncLocalStorage.disable(); + return this; + } +} diff --git a/packages/vercel-edge/test/async.test.ts b/packages/vercel-edge/test/async.test.ts index a4423e0ca434..75c7d56803cd 100644 --- a/packages/vercel-edge/test/async.test.ts +++ b/packages/vercel-edge/test/async.test.ts @@ -1,19 +1,33 @@ import { Scope, getCurrentScope, getGlobalScope, getIsolationScope, withIsolationScope, withScope } from '@sentry/core'; +import { setOpenTelemetryContextAsyncContextStrategy } from '@sentry/opentelemetry'; import { GLOBAL_OBJ } from '@sentry/utils'; import { AsyncLocalStorage } from 'async_hooks'; -import { beforeEach, describe, expect, it } from 'vitest'; -import { setAsyncLocalStorageAsyncContextStrategy } from '../src/async'; +import { beforeAll, beforeEach, describe, expect, it } from 'vitest'; +import { VercelEdgeClient } from '../src'; +import { setupOtel } from '../src/sdk'; +import { makeEdgeTransport } from '../src/transports'; + +beforeAll(() => { + (GLOBAL_OBJ as any).AsyncLocalStorage = AsyncLocalStorage; + + const client = new VercelEdgeClient({ + stackParser: () => [], + integrations: [], + transport: makeEdgeTransport, + }); -describe('withScope()', () => { - beforeEach(() => { - getIsolationScope().clear(); - getCurrentScope().clear(); - getGlobalScope().clear(); + setupOtel(client); - (GLOBAL_OBJ as any).AsyncLocalStorage = AsyncLocalStorage; - setAsyncLocalStorageAsyncContextStrategy(); - }); + setOpenTelemetryContextAsyncContextStrategy(); +}); + +beforeEach(() => { + getIsolationScope().clear(); + getCurrentScope().clear(); + getGlobalScope().clear(); +}); +describe('withScope()', () => { it('will make the passed scope the active scope within the callback', () => new Promise(done => { withScope(scope => { @@ -84,15 +98,6 @@ describe('withScope()', () => { }); describe('withIsolationScope()', () => { - beforeEach(() => { - getIsolationScope().clear(); - getCurrentScope().clear(); - getGlobalScope().clear(); - (GLOBAL_OBJ as any).AsyncLocalStorage = AsyncLocalStorage; - - setAsyncLocalStorageAsyncContextStrategy(); - }); - it('will make the passed isolation scope the active isolation scope within the callback', () => new Promise(done => { withIsolationScope(scope => { diff --git a/packages/vercel-edge/test/sdk.test.ts b/packages/vercel-edge/test/sdk.test.ts deleted file mode 100644 index b1367716c73a..000000000000 --- a/packages/vercel-edge/test/sdk.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { describe, expect, it, vi } from 'vitest'; - -import * as SentryCore from '@sentry/core'; -import { init } from '../src/sdk'; - -describe('init', () => { - it('initializes and returns client', () => { - const initSpy = vi.spyOn(SentryCore, 'initAndBind'); - - expect(init({})).not.toBeUndefined(); - expect(initSpy).toHaveBeenCalledTimes(1); - }); -}); diff --git a/packages/vercel-edge/vite.config.ts b/packages/vercel-edge/vite.config.mts similarity index 100% rename from packages/vercel-edge/vite.config.ts rename to packages/vercel-edge/vite.config.mts diff --git a/scripts/normalize-e2e-test-dump-transaction-events.js b/scripts/normalize-e2e-test-dump-transaction-events.js index ba06a63fa020..771dcccd8f87 100644 --- a/scripts/normalize-e2e-test-dump-transaction-events.js +++ b/scripts/normalize-e2e-test-dump-transaction-events.js @@ -1,81 +1,95 @@ +// @ts-check /* eslint-disable no-console */ const fs = require('fs'); const path = require('path'); +const glob = require('glob'); + +glob.glob( + '../dev-packages/e2e-tests/test-applications/*/event-dumps/*.dump', + { + cwd: __dirname, + absolute: true, + }, + (err, dumpPaths) => { + if (err) { + throw err; + } -if (process.argv.length < 4) { - throw new Error('Please provide an input and output file path as an argument.'); -} - -const resolvedInputPath = path.resolve(process.argv[2]); -const resolvedOutputPath = path.resolve(process.argv[3]); - -const fileContents = fs.readFileSync(resolvedInputPath, 'utf8'); - -const transactionNodes = []; - -fileContents.split('\n').forEach(serializedEnvelope => { - let envelope; - try { - envelope = JSON.parse(serializedEnvelope); - } catch (e) { - return; - // noop - } + dumpPaths.forEach(dumpPath => { + const fileContents = fs.readFileSync(dumpPath, 'utf8'); - const envelopeItems = envelope[1]; - - envelopeItems.forEach(([envelopeItemHeader, transaction]) => { - if (envelopeItemHeader.type === 'transaction') { - const rootNode = { - runtime: transaction.contexts.runtime?.name, - op: transaction.contexts.trace.op, - name: transaction.transaction, - children: [], - }; - - const spanMap = new Map(); - spanMap.set(transaction.contexts.trace.span_id, rootNode); - - transaction.spans.forEach(span => { - const node = { - op: span.data['sentry.op'], - name: span.description, - parent_span_id: span.parent_span_id, - children: [], - }; - spanMap.set(span.span_id, node); - }); + const transactionNodes = []; - transaction.spans.forEach(span => { - const node = spanMap.get(span.span_id); - if (node && node.parent_span_id) { - const parentNode = spanMap.get(node.parent_span_id); - parentNode.children.push(node); + fileContents.split('\n').forEach(serializedEnvelope => { + let envelope; + try { + envelope = JSON.parse(serializedEnvelope); + } catch (e) { + return; + // noop } - }); - transactionNodes.push(rootNode); - } - }); -}); - -const output = transactionNodes - .sort((a, b) => { - const aSerialized = serializeNode(a); - const bSerialized = serializeNode(b); - if (aSerialized < bSerialized) { - return -1; - } else if (aSerialized > bSerialized) { - return 1; - } else { - return 0; - } - }) - .map(node => buildDeterministicStringFromNode(node)) - .join('\n\n-----------------------\n\n'); + const envelopeItems = envelope[1]; + + envelopeItems.forEach(([envelopeItemHeader, transaction]) => { + if (envelopeItemHeader.type === 'transaction') { + const rootNode = { + runtime: transaction.contexts.runtime?.name, + op: transaction.contexts.trace.op, + name: transaction.transaction, + children: [], + }; + + const spanMap = new Map(); + spanMap.set(transaction.contexts.trace.span_id, rootNode); + + transaction.spans.forEach(span => { + const node = { + op: span.data['sentry.op'], + name: span.description, + parent_span_id: span.parent_span_id, + children: [], + }; + spanMap.set(span.span_id, node); + }); + + transaction.spans.forEach(span => { + const node = spanMap.get(span.span_id); + if (node && node.parent_span_id) { + const parentNode = spanMap.get(node.parent_span_id); + parentNode.children.push(node); + } + }); + + transactionNodes.push(rootNode); + } + }); + }); -fs.writeFileSync(resolvedOutputPath, output, 'utf-8'); + const output = transactionNodes + .sort((a, b) => { + const aSerialized = serializeNode(a); + const bSerialized = serializeNode(b); + if (aSerialized < bSerialized) { + return -1; + } else if (aSerialized > bSerialized) { + return 1; + } else { + return 0; + } + }) + .map(node => buildDeterministicStringFromNode(node)) + .join('\n\n-----------------------\n\n'); + + fs.writeFileSync( + path.join(path.dirname(dumpPath), `normalized-transactions-${path.basename(dumpPath, '.dump')}.txt`), + output, + 'utf-8', + ); + }); + }, +); // ------- utility fns ---------- diff --git a/yarn.lock b/yarn.lock index 48991d296050..9d3139160e51 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6426,16 +6426,16 @@ path-to-regexp "3.2.0" tslib "2.6.3" -"@nestjs/platform-express@^10.3.3": - version "10.3.3" - resolved "https://registry.yarnpkg.com/@nestjs/platform-express/-/platform-express-10.3.3.tgz#c1484d30d1e7666c4c8d0d7cde31cfc0b9d166d7" - integrity sha512-GGKSEU48Os7nYFIsUM0nutuFUGn5AbeP8gzFBiBCAtiuJWrXZXpZ58pMBYxAbMf7IrcOZFInHEukjHGAQU0OZw== +"@nestjs/platform-express@^10.4.6": + version "10.4.6" + resolved "https://registry.yarnpkg.com/@nestjs/platform-express/-/platform-express-10.4.6.tgz#6c39c522fa66036b4256714fea203fbeb49fc4de" + integrity sha512-HcyCpAKccAasrLSGRTGWv5BKRs0rwTIFOSsk6laNyqfqvgvYcJQAedarnm4jmaemtmSJ0PFI9PmtEZADd2ahCg== dependencies: - body-parser "1.20.2" + body-parser "1.20.3" cors "2.8.5" - express "4.18.2" + express "4.21.1" multer "1.4.4-lts.1" - tslib "2.6.2" + tslib "2.7.0" "@netlify/functions@^2.8.0": version "2.8.1" @@ -6974,19 +6974,6 @@ before-after-hook "^2.2.0" universal-user-agent "^6.0.0" -"@octokit/core@^4.1.0": - version "4.2.0" - resolved "https://registry.npmjs.org/@octokit/core/-/core-4.2.0.tgz#8c253ba9605aca605bc46187c34fcccae6a96648" - integrity sha512-AgvDRUg3COpR82P7PBdGZF/NNqGmtMq2NiPqeSsDIeCfYFOZ9gddqWNQHnFdEUf+YwOj4aZYmJnlPp7OXmDIDg== - dependencies: - "@octokit/auth-token" "^3.0.0" - "@octokit/graphql" "^5.0.0" - "@octokit/request" "^6.0.0" - "@octokit/request-error" "^3.0.0" - "@octokit/types" "^9.0.0" - before-after-hook "^2.2.0" - universal-user-agent "^6.0.0" - "@octokit/core@^4.2.1": version "4.2.4" resolved "https://registry.yarnpkg.com/@octokit/core/-/core-4.2.4.tgz#d8769ec2b43ff37cc3ea89ec4681a20ba58ef907" @@ -7068,13 +7055,6 @@ dependencies: "@octokit/types" "^6.40.0" -"@octokit/plugin-paginate-rest@^6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-6.0.0.tgz#f34b5a7d9416019126042cd7d7b811e006c0d561" - integrity sha512-Sq5VU1PfT6/JyuXPyt04KZNVsFOSBaYOAq2QRZUwzVlI10KFvcbUo8lR258AAQL1Et60b0WuVik+zOWKLuDZxw== - dependencies: - "@octokit/types" "^9.0.0" - "@octokit/plugin-paginate-rest@^6.1.2": version "6.1.2" resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-6.1.2.tgz#f86456a7a1fe9e58fec6385a85cf1b34072341f8" @@ -7096,14 +7076,6 @@ "@octokit/types" "^6.39.0" deprecation "^2.3.1" -"@octokit/plugin-rest-endpoint-methods@^7.0.0": - version "7.0.1" - resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-7.0.1.tgz#f7ebe18144fd89460f98f35a587b056646e84502" - integrity sha512-pnCaLwZBudK5xCdrR823xHGNgqOzRnJ/mpC/76YPpNP7DybdsJtP7mdOwh+wYZxK5jqeQuhu59ogMI4NRlBUvA== - dependencies: - "@octokit/types" "^9.0.0" - deprecation "^2.3.1" - "@octokit/plugin-rest-endpoint-methods@^7.1.2": version "7.2.3" resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-7.2.3.tgz#37a84b171a6cb6658816c82c4082ac3512021797" @@ -7163,16 +7135,6 @@ "@octokit/plugin-request-log" "^1.0.4" "@octokit/plugin-rest-endpoint-methods" "^7.1.2" -"@octokit/rest@^19.0.5": - version "19.0.7" - resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-19.0.7.tgz#d2e21b4995ab96ae5bfae50b4969da7e04e0bb70" - integrity sha512-HRtSfjrWmWVNp2uAkEpQnuGMJsu/+dBr47dRc5QVgsCbnIc1+GFEaoKBWkYG+zjrsHpSqcAElMio+n10c0b5JA== - dependencies: - "@octokit/core" "^4.1.0" - "@octokit/plugin-paginate-rest" "^6.0.0" - "@octokit/plugin-request-log" "^1.0.4" - "@octokit/plugin-rest-endpoint-methods" "^7.0.0" - "@octokit/tsconfig@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@octokit/tsconfig/-/tsconfig-1.0.2.tgz#59b024d6f3c0ed82f00d08ead5b3750469125af7" @@ -7227,6 +7189,13 @@ dependencies: "@opentelemetry/api" "^1.0.0" +"@opentelemetry/api-logs@0.54.0": + version "0.54.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/api-logs/-/api-logs-0.54.0.tgz#a8e09ae22f6d318b6202765dbc2cc0b05e3377be" + integrity sha512-9HhEh5GqFrassUndqJsyW7a0PzfyWr2eV2xwzHLIS+wX3125+9HE9FMRAKmJRwxZhgZGwH3HNQQjoMGZqmOeVA== + dependencies: + "@opentelemetry/api" "^1.3.0" + "@opentelemetry/api@^0.12.0": version "0.12.0" resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-0.12.0.tgz#0359c3926e8f16fdcd8c78f196bd1e9fc4e66777" @@ -7234,7 +7203,7 @@ dependencies: "@opentelemetry/context-base" "^0.12.0" -"@opentelemetry/api@^1.0.0", "@opentelemetry/api@^1.8", "@opentelemetry/api@^1.9.0": +"@opentelemetry/api@^1.0.0", "@opentelemetry/api@^1.3.0", "@opentelemetry/api@^1.8", "@opentelemetry/api@^1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.9.0.tgz#d03eba68273dc0f7509e2a3d5cba21eae10379fe" integrity sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg== @@ -7299,23 +7268,23 @@ "@opentelemetry/semantic-conventions" "^1.27.0" "@types/aws-lambda" "8.10.143" -"@opentelemetry/instrumentation-aws-sdk@0.44.0": - version "0.44.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-aws-sdk/-/instrumentation-aws-sdk-0.44.0.tgz#f1a2d8c186d37fae42954921bbdcc3555aac331c" - integrity sha512-HIWFg4TDQsayceiikOnruMmyQ0SZYW6WiR+wknWwWVLHC3lHTCpAnqzp5V42ckArOdlwHZu2Jvq2GMSM4Myx3w== +"@opentelemetry/instrumentation-aws-sdk@0.45.0": + version "0.45.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-aws-sdk/-/instrumentation-aws-sdk-0.45.0.tgz#269371282ba95877937a11170843cf9d9436706f" + integrity sha512-3EGgC0LFZuFfXcOeslhXHhsiInVhhN046YQsYIPflsicAk7v0wN946sZKWuerEfmqx/kFXOsbOeI1SkkTRmqWQ== dependencies: "@opentelemetry/core" "^1.8.0" - "@opentelemetry/instrumentation" "^0.53.0" - "@opentelemetry/propagation-utils" "^0.30.11" + "@opentelemetry/instrumentation" "^0.54.0" + "@opentelemetry/propagation-utils" "^0.30.12" "@opentelemetry/semantic-conventions" "^1.27.0" -"@opentelemetry/instrumentation-connect@0.39.0": - version "0.39.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.39.0.tgz#32bdbaac464cba061c95df6c850ee81efdd86f8b" - integrity sha512-pGBiKevLq7NNglMgqzmeKczF4XQMTOUOTkK8afRHMZMnrK3fcETyTH7lVaSozwiOM3Ws+SuEmXZT7DYrrhxGlg== +"@opentelemetry/instrumentation-connect@0.40.0": + version "0.40.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.40.0.tgz#cb151b860ad8a711ebce4d7e025dcde95e4ba2c5" + integrity sha512-3aR/3YBQ160siitwwRLjwqrv2KBT16897+bo6yz8wIfel6nWOxTZBJudcbsK3p42pTC7qrbotJ9t/1wRLpv79Q== dependencies: "@opentelemetry/core" "^1.8.0" - "@opentelemetry/instrumentation" "^0.53.0" + "@opentelemetry/instrumentation" "^0.54.0" "@opentelemetry/semantic-conventions" "^1.27.0" "@types/connect" "3.4.36" @@ -7326,13 +7295,13 @@ dependencies: "@opentelemetry/instrumentation" "^0.53.0" -"@opentelemetry/instrumentation-express@0.43.0": - version "0.43.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-express/-/instrumentation-express-0.43.0.tgz#35ff5bcf40b816d9a9159d5f7814ed7e5d83f69b" - integrity sha512-bxTIlzn9qPXJgrhz8/Do5Q3jIlqfpoJrSUtVGqH+90eM1v2PkPHc+SdE+zSqe4q9Y1UQJosmZ4N4bm7Zj/++MA== +"@opentelemetry/instrumentation-express@0.44.0": + version "0.44.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-express/-/instrumentation-express-0.44.0.tgz#51dc11e3152ffbee1c4e389298aac30231c8270a" + integrity sha512-GWgibp6Q0wxyFaaU8ERIgMMYgzcHmGrw3ILUtGchLtLncHNOKk0SNoWGqiylXWWT4HTn5XdV8MGawUgpZh80cA== dependencies: "@opentelemetry/core" "^1.8.0" - "@opentelemetry/instrumentation" "^0.53.0" + "@opentelemetry/instrumentation" "^0.54.0" "@opentelemetry/semantic-conventions" "^1.27.0" "@opentelemetry/instrumentation-fastify@0.40.0": @@ -7344,13 +7313,13 @@ "@opentelemetry/instrumentation" "^0.53.0" "@opentelemetry/semantic-conventions" "^1.27.0" -"@opentelemetry/instrumentation-fs@0.15.0": - version "0.15.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.15.0.tgz#41658507860f39fee5209bca961cea8d24ca2a83" - integrity sha512-JWVKdNLpu1skqZQA//jKOcKdJC66TWKqa2FUFq70rKohvaSq47pmXlnabNO+B/BvLfmidfiaN35XakT5RyMl2Q== +"@opentelemetry/instrumentation-fs@0.16.0": + version "0.16.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.16.0.tgz#aa1cc3aa81011ad9843a0156b200f06f31ffa03e" + integrity sha512-hMDRUxV38ln1R3lNz6osj3YjlO32ykbHqVrzG7gEhGXFQfu7LJUx8t9tEwE4r2h3CD4D0Rw4YGDU4yF4mP3ilg== dependencies: "@opentelemetry/core" "^1.8.0" - "@opentelemetry/instrumentation" "^0.53.0" + "@opentelemetry/instrumentation" "^0.54.0" "@opentelemetry/instrumentation-generic-pool@0.39.0": version "0.39.0" @@ -7394,12 +7363,12 @@ "@opentelemetry/redis-common" "^0.36.2" "@opentelemetry/semantic-conventions" "^1.27.0" -"@opentelemetry/instrumentation-kafkajs@0.3.0": - version "0.3.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.3.0.tgz#6687bce4dac8b90ef8ccbf1b662d5d1e95a34414" - integrity sha512-UnkZueYK1ise8FXQeKlpBd7YYUtC7mM8J0wzUSccEfc/G8UqHQqAzIyYCUOUPUKp8GsjLnWOOK/3hJc4owb7Jg== +"@opentelemetry/instrumentation-kafkajs@0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.4.0.tgz#c1fe0de45a65a66581be0d7422f6828cc806b3bb" + integrity sha512-I9VwDG314g7SDL4t8kD/7+1ytaDBRbZQjhVaQaVIDR8K+mlsoBhLsWH79yHxhHQKvwCSZwqXF+TiTOhoQVUt7A== dependencies: - "@opentelemetry/instrumentation" "^0.53.0" + "@opentelemetry/instrumentation" "^0.54.0" "@opentelemetry/semantic-conventions" "^1.27.0" "@opentelemetry/instrumentation-koa@0.43.0": @@ -7525,10 +7494,22 @@ semver "^7.5.2" shimmer "^1.2.1" -"@opentelemetry/propagation-utils@^0.30.11": - version "0.30.11" - resolved "https://registry.yarnpkg.com/@opentelemetry/propagation-utils/-/propagation-utils-0.30.11.tgz#0a1c51cb4a2724fa41c41be07024bbb6f0aade46" - integrity sha512-rY4L/2LWNk5p/22zdunpqVmgz6uN419DsRTw5KFMa6u21tWhXS8devlMy4h8m8nnS20wM7r6yYweCNNKjgLYJw== +"@opentelemetry/instrumentation@^0.54.0": + version "0.54.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.54.0.tgz#3fa9df964d3b157ea7ef2270168d343331d6448e" + integrity sha512-B0Ydo9g9ehgNHwtpc97XivEzjz0XBKR6iQ83NTENIxEEf5NHE0otZQuZLgDdey1XNk+bP1cfRpIkSFWM5YlSyg== + dependencies: + "@opentelemetry/api-logs" "0.54.0" + "@types/shimmer" "^1.2.0" + import-in-the-middle "^1.8.1" + require-in-the-middle "^7.1.1" + semver "^7.5.2" + shimmer "^1.2.1" + +"@opentelemetry/propagation-utils@^0.30.12": + version "0.30.12" + resolved "https://registry.yarnpkg.com/@opentelemetry/propagation-utils/-/propagation-utils-0.30.12.tgz#58200cfd085e791bab5e3c4d36d77b2c60fc2b6b" + integrity sha512-bgab3q/4dYUutUpQCEaSDa+mLoQJG3vJKeSiGuhM4iZaSpkz8ov0fs1MGil5PfxCo6Hhw3bB3bFYhUtnsfT/Pg== "@opentelemetry/propagator-aws-xray@^1.3.1": version "1.25.1" @@ -7606,12 +7587,12 @@ resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.0.tgz#390eb4d42a29c66bdc30066af9035645e9bb7270" integrity sha512-M+kkXKRAIAiAP6qYyesfrC5TOmDpDVtsxuGfPcqd9B/iBrac+E14jYwrgm0yZBUIbIP2OnqC3j+UgkXLm1vxUQ== -"@opentelemetry/semantic-conventions@1.25.1", "@opentelemetry/semantic-conventions@^1.17.0": +"@opentelemetry/semantic-conventions@1.25.1": version "1.25.1" resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.1.tgz#0deecb386197c5e9c2c28f2f89f51fb8ae9f145e" integrity sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ== -"@opentelemetry/semantic-conventions@1.27.0", "@opentelemetry/semantic-conventions@^1.27.0": +"@opentelemetry/semantic-conventions@1.27.0", "@opentelemetry/semantic-conventions@^1.17.0", "@opentelemetry/semantic-conventions@^1.27.0": version "1.27.0" resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.27.0.tgz#1a857dcc95a5ab30122e04417148211e6f945e6c" integrity sha512-sAay1RrB+ONOem0OZanAR1ZI/k7yDpnOQSQmTMuGImUQb2y8EbSaCJ94FQluM74xoU03vlb2d2U90hZluL6nQg== @@ -8139,215 +8120,95 @@ estree-walker "^2.0.2" picomatch "^2.3.1" -"@rollup/rollup-android-arm-eabi@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.0.tgz#b98786c1304b4ff8db3a873180b778649b5dff2b" - integrity sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg== - -"@rollup/rollup-android-arm-eabi@4.22.4": - version "4.22.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz#8b613b9725e8f9479d142970b106b6ae878610d5" - integrity sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w== - -"@rollup/rollup-android-arm-eabi@4.9.1": - version "4.9.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.1.tgz#beaf518ee45a196448e294ad3f823d2d4576cf35" - integrity sha512-6vMdBZqtq1dVQ4CWdhFwhKZL6E4L1dV6jUjuBvsavvNJSppzi6dLBbuV+3+IyUREaj9ZFvQefnQm28v4OCXlig== - -"@rollup/rollup-android-arm64@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.0.tgz#8833679af11172b1bf1ab7cb3bad84df4caf0c9e" - integrity sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q== - -"@rollup/rollup-android-arm64@4.22.4": - version "4.22.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.4.tgz#654ca1049189132ff602bfcf8df14c18da1f15fb" - integrity sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA== - -"@rollup/rollup-android-arm64@4.9.1": - version "4.9.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.1.tgz#6f76cfa759c2d0fdb92122ffe28217181a1664eb" - integrity sha512-Jto9Fl3YQ9OLsTDWtLFPtaIMSL2kwGyGoVCmPC8Gxvym9TCZm4Sie+cVeblPO66YZsYH8MhBKDMGZ2NDxuk/XQ== - -"@rollup/rollup-darwin-arm64@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.0.tgz#ef02d73e0a95d406e0eb4fd61a53d5d17775659b" - integrity sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g== - -"@rollup/rollup-darwin-arm64@4.22.4": - version "4.22.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.4.tgz#6d241d099d1518ef0c2205d96b3fa52e0fe1954b" - integrity sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q== - -"@rollup/rollup-darwin-arm64@4.9.1": - version "4.9.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.1.tgz#9aaefe33a5481d66322d1c62f368171c03eabe2b" - integrity sha512-LtYcLNM+bhsaKAIGwVkh5IOWhaZhjTfNOkGzGqdHvhiCUVuJDalvDxEdSnhFzAn+g23wgsycmZk1vbnaibZwwA== - -"@rollup/rollup-darwin-x64@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.0.tgz#3ce5b9bcf92b3341a5c1c58a3e6bcce0ea9e7455" - integrity sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg== - -"@rollup/rollup-darwin-x64@4.22.4": - version "4.22.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.4.tgz#42bd19d292a57ee11734c980c4650de26b457791" - integrity sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw== - -"@rollup/rollup-darwin-x64@4.9.1": - version "4.9.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.1.tgz#707dcaadcdc6bd3fd6c69f55d9456cd4446306a3" - integrity sha512-KyP/byeXu9V+etKO6Lw3E4tW4QdcnzDG/ake031mg42lob5tN+5qfr+lkcT/SGZaH2PdW4Z1NX9GHEkZ8xV7og== - -"@rollup/rollup-linux-arm-gnueabihf@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.0.tgz#3d3d2c018bdd8e037c6bfedd52acfff1c97e4be4" - integrity sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ== - -"@rollup/rollup-linux-arm-gnueabihf@4.22.4": - version "4.22.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.4.tgz#f23555ee3d8fe941c5c5fd458cd22b65eb1c2232" - integrity sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ== - -"@rollup/rollup-linux-arm-gnueabihf@4.9.1": - version "4.9.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.1.tgz#7a4dbbd1dd98731d88a55aefcef0ec4c578fa9c7" - integrity sha512-Yqz/Doumf3QTKplwGNrCHe/B2p9xqDghBZSlAY0/hU6ikuDVQuOUIpDP/YcmoT+447tsZTmirmjgG3znvSCR0Q== - -"@rollup/rollup-linux-arm-musleabihf@4.22.4": - version "4.22.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.4.tgz#f3bbd1ae2420f5539d40ac1fde2b38da67779baa" - integrity sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg== - -"@rollup/rollup-linux-arm64-gnu@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.0.tgz#5fc8cc978ff396eaa136d7bfe05b5b9138064143" - integrity sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w== - -"@rollup/rollup-linux-arm64-gnu@4.22.4": - version "4.22.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.4.tgz#7abe900120113e08a1f90afb84c7c28774054d15" - integrity sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw== - -"@rollup/rollup-linux-arm64-gnu@4.9.1": - version "4.9.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.1.tgz#967ba8e6f68a5f21bd00cd97773dcdd6107e94ed" - integrity sha512-u3XkZVvxcvlAOlQJ3UsD1rFvLWqu4Ef/Ggl40WAVCuogf4S1nJPHh5RTgqYFpCOvuGJ7H5yGHabjFKEZGExk5Q== - -"@rollup/rollup-linux-arm64-musl@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.0.tgz#f2ae7d7bed416ffa26d6b948ac5772b520700eef" - integrity sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw== - -"@rollup/rollup-linux-arm64-musl@4.22.4": - version "4.22.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.4.tgz#9e655285c8175cd44f57d6a1e8e5dedfbba1d820" - integrity sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA== - -"@rollup/rollup-linux-arm64-musl@4.9.1": - version "4.9.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.1.tgz#d3a4e1c9f21eef3b9f4e4989f334a519a1341462" - integrity sha512-0XSYN/rfWShW+i+qjZ0phc6vZ7UWI8XWNz4E/l+6edFt+FxoEghrJHjX1EY/kcUGCnZzYYRCl31SNdfOi450Aw== - -"@rollup/rollup-linux-powerpc64le-gnu@4.22.4": - version "4.22.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.4.tgz#9a79ae6c9e9d8fe83d49e2712ecf4302db5bef5e" - integrity sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg== - -"@rollup/rollup-linux-riscv64-gnu@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.0.tgz#303d57a328ee9a50c85385936f31cf62306d30b6" - integrity sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA== - -"@rollup/rollup-linux-riscv64-gnu@4.22.4": - version "4.22.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.4.tgz#67ac70eca4ace8e2942fabca95164e8874ab8128" - integrity sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA== - -"@rollup/rollup-linux-riscv64-gnu@4.9.1": - version "4.9.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.1.tgz#415c0533bb752164effd05f5613858e8f6779bc9" - integrity sha512-LmYIO65oZVfFt9t6cpYkbC4d5lKHLYv5B4CSHRpnANq0VZUQXGcCPXHzbCXCz4RQnx7jvlYB1ISVNCE/omz5cw== - -"@rollup/rollup-linux-s390x-gnu@4.22.4": - version "4.22.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.4.tgz#9f883a7440f51a22ed7f99e1d070bd84ea5005fc" - integrity sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q== - -"@rollup/rollup-linux-x64-gnu@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.0.tgz#f672f6508f090fc73f08ba40ff76c20b57424778" - integrity sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA== - -"@rollup/rollup-linux-x64-gnu@4.22.4": - version "4.22.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz#70116ae6c577fe367f58559e2cffb5641a1dd9d0" - integrity sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg== - -"@rollup/rollup-linux-x64-gnu@4.9.1": - version "4.9.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.1.tgz#0983385dd753a2e0ecaddea7a81dd37fea5114f5" - integrity sha512-kr8rEPQ6ns/Lmr/hiw8sEVj9aa07gh1/tQF2Y5HrNCCEPiCBGnBUt9tVusrcBBiJfIt1yNaXN6r1CCmpbFEDpg== - -"@rollup/rollup-linux-x64-musl@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.0.tgz#d2f34b1b157f3e7f13925bca3288192a66755a89" - integrity sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw== - -"@rollup/rollup-linux-x64-musl@4.22.4": - version "4.22.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz#f473f88219feb07b0b98b53a7923be716d1d182f" - integrity sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g== - -"@rollup/rollup-linux-x64-musl@4.9.1": - version "4.9.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.1.tgz#eb7494ebc5199cbd2e5c38c2b8acbe2603f35e03" - integrity sha512-t4QSR7gN+OEZLG0MiCgPqMWZGwmeHhsM4AkegJ0Kiy6TnJ9vZ8dEIwHw1LcZKhbHxTY32hp9eVCMdR3/I8MGRw== - -"@rollup/rollup-win32-arm64-msvc@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.0.tgz#8ffecc980ae4d9899eb2f9c4ae471a8d58d2da6b" - integrity sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA== - -"@rollup/rollup-win32-arm64-msvc@4.22.4": - version "4.22.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.4.tgz#4349482d17f5d1c58604d1c8900540d676f420e0" - integrity sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw== - -"@rollup/rollup-win32-arm64-msvc@4.9.1": - version "4.9.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.1.tgz#5bebc66e3a7f82d4b9aa9ff448e7fc13a69656e9" - integrity sha512-7XI4ZCBN34cb+BH557FJPmh0kmNz2c25SCQeT9OiFWEgf8+dL6ZwJ8f9RnUIit+j01u07Yvrsuu1rZGxJCc51g== - -"@rollup/rollup-win32-ia32-msvc@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.0.tgz#a7505884f415662e088365b9218b2b03a88fc6f2" - integrity sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw== - -"@rollup/rollup-win32-ia32-msvc@4.22.4": - version "4.22.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.4.tgz#a6fc39a15db618040ec3c2a24c1e26cb5f4d7422" - integrity sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g== - -"@rollup/rollup-win32-ia32-msvc@4.9.1": - version "4.9.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.1.tgz#34156ebf8b4de3b20e6497260fe519a30263f8cf" - integrity sha512-yE5c2j1lSWOH5jp+Q0qNL3Mdhr8WuqCNVjc6BxbVfS5cAS6zRmdiw7ktb8GNpDCEUJphILY6KACoFoRtKoqNQg== - -"@rollup/rollup-win32-x64-msvc@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.0.tgz#6abd79db7ff8d01a58865ba20a63cfd23d9e2a10" - integrity sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw== - -"@rollup/rollup-win32-x64-msvc@4.22.4": - version "4.22.4" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz#3dd5d53e900df2a40841882c02e56f866c04d202" - integrity sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q== - -"@rollup/rollup-win32-x64-msvc@4.9.1": - version "4.9.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.1.tgz#d146db7a5949e10837b323ce933ed882ac878262" - integrity sha512-PyJsSsafjmIhVgaI1Zdj7m8BB8mMckFah/xbpplObyHfiXzKcI5UOUXRyOdHW7nz4DpMCuzLnF7v5IWHenCwYA== +"@rollup/rollup-android-arm-eabi@4.24.2": + version "4.24.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.2.tgz#07db37fcd9d401aae165f662c0069efd61d4ffcc" + integrity sha512-ufoveNTKDg9t/b7nqI3lwbCG/9IJMhADBNjjz/Jn6LxIZxD7T5L8l2uO/wD99945F1Oo8FvgbbZJRguyk/BdzA== + +"@rollup/rollup-android-arm64@4.24.2": + version "4.24.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.2.tgz#160975402adf85ecd58a0721ad60ae1779a68147" + integrity sha512-iZoYCiJz3Uek4NI0J06/ZxUgwAfNzqltK0MptPDO4OR0a88R4h0DSELMsflS6ibMCJ4PnLvq8f7O1d7WexUvIA== + +"@rollup/rollup-darwin-arm64@4.24.2": + version "4.24.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.2.tgz#2b126f0aa4349694fe2941bcbcc4b0982b7f1a49" + integrity sha512-/UhrIxobHYCBfhi5paTkUDQ0w+jckjRZDZ1kcBL132WeHZQ6+S5v9jQPVGLVrLbNUebdIRpIt00lQ+4Z7ys4Rg== + +"@rollup/rollup-darwin-x64@4.24.2": + version "4.24.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.2.tgz#3f4987eff6195532037c50b8db92736e326b5bb2" + integrity sha512-1F/jrfhxJtWILusgx63WeTvGTwE4vmsT9+e/z7cZLKU8sBMddwqw3UV5ERfOV+H1FuRK3YREZ46J4Gy0aP3qDA== + +"@rollup/rollup-freebsd-arm64@4.24.2": + version "4.24.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.24.2.tgz#15fe184ecfafc635879500f6985c954e57697c44" + integrity sha512-1YWOpFcGuC6iGAS4EI+o3BV2/6S0H+m9kFOIlyFtp4xIX5rjSnL3AwbTBxROX0c8yWtiWM7ZI6mEPTI7VkSpZw== + +"@rollup/rollup-freebsd-x64@4.24.2": + version "4.24.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.24.2.tgz#c72d37315d36b6e0763b7aabb6ae53c361b45e05" + integrity sha512-3qAqTewYrCdnOD9Gl9yvPoAoFAVmPJsBvleabvx4bnu1Kt6DrB2OALeRVag7BdWGWLhP1yooeMLEi6r2nYSOjg== + +"@rollup/rollup-linux-arm-gnueabihf@4.24.2": + version "4.24.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.2.tgz#f274f81abf845dcca5f1f40d434a09a79a3a73a0" + integrity sha512-ArdGtPHjLqWkqQuoVQ6a5UC5ebdX8INPuJuJNWRe0RGa/YNhVvxeWmCTFQ7LdmNCSUzVZzxAvUznKaYx645Rig== + +"@rollup/rollup-linux-arm-musleabihf@4.24.2": + version "4.24.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.2.tgz#9edaeb1a9fa7d4469917cb0614f665f1cf050625" + integrity sha512-B6UHHeNnnih8xH6wRKB0mOcJGvjZTww1FV59HqJoTJ5da9LCG6R4SEBt6uPqzlawv1LoEXSS0d4fBlHNWl6iYw== + +"@rollup/rollup-linux-arm64-gnu@4.24.2": + version "4.24.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.2.tgz#6eb6851f594336bfa00f074f58a00a61e9751493" + integrity sha512-kr3gqzczJjSAncwOS6i7fpb4dlqcvLidqrX5hpGBIM1wtt0QEVtf4wFaAwVv8QygFU8iWUMYEoJZWuWxyua4GQ== + +"@rollup/rollup-linux-arm64-musl@4.24.2": + version "4.24.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.2.tgz#9d8dc8e80df8f156d2888ecb8d6c96d653580731" + integrity sha512-TDdHLKCWgPuq9vQcmyLrhg/bgbOvIQ8rtWQK7MRxJ9nvaxKx38NvY7/Lo6cYuEnNHqf6rMqnivOIPIQt6H2AoA== + +"@rollup/rollup-linux-powerpc64le-gnu@4.24.2": + version "4.24.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.2.tgz#358e3e7dda2d60c46ff7c74f7075045736df5b50" + integrity sha512-xv9vS648T3X4AxFFZGWeB5Dou8ilsv4VVqJ0+loOIgDO20zIhYfDLkk5xoQiej2RiSQkld9ijF/fhLeonrz2mw== + +"@rollup/rollup-linux-riscv64-gnu@4.24.2": + version "4.24.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.2.tgz#b08461ace599c3f0b5f27051f1756b6cf1c78259" + integrity sha512-tbtXwnofRoTt223WUZYiUnbxhGAOVul/3StZ947U4A5NNjnQJV5irKMm76G0LGItWs6y+SCjUn/Q0WaMLkEskg== + +"@rollup/rollup-linux-s390x-gnu@4.24.2": + version "4.24.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.2.tgz#daab36c9b5c8ac4bfe5a9c4c39ad711464b7dfee" + integrity sha512-gc97UebApwdsSNT3q79glOSPdfwgwj5ELuiyuiMY3pEWMxeVqLGKfpDFoum4ujivzxn6veUPzkGuSYoh5deQ2Q== + +"@rollup/rollup-linux-x64-gnu@4.24.2": + version "4.24.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.2.tgz#4cc3a4f31920bdb028dbfd7ce0e972a17424a63c" + integrity sha512-jOG/0nXb3z+EM6SioY8RofqqmZ+9NKYvJ6QQaa9Mvd3RQxlH68/jcB/lpyVt4lCiqr04IyaC34NzhUqcXbB5FQ== + +"@rollup/rollup-linux-x64-musl@4.24.2": + version "4.24.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.2.tgz#59800e26c538517ee05f4645315d9e1aded93200" + integrity sha512-XAo7cJec80NWx9LlZFEJQxqKOMz/lX3geWs2iNT5CHIERLFfd90f3RYLLjiCBm1IMaQ4VOX/lTC9lWfzzQm14Q== + +"@rollup/rollup-win32-arm64-msvc@4.24.2": + version "4.24.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.2.tgz#c80e2c33c952b6b171fa6ad9a97dfbb2e4ebee44" + integrity sha512-A+JAs4+EhsTjnPQvo9XY/DC0ztaws3vfqzrMNMKlwQXuniBKOIIvAAI8M0fBYiTCxQnElYu7mLk7JrhlQ+HeOw== + +"@rollup/rollup-win32-ia32-msvc@4.24.2": + version "4.24.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.2.tgz#a1e9d275cb16f6d5feb9c20aee7e897b1e193359" + integrity sha512-ZhcrakbqA1SCiJRMKSU64AZcYzlZ/9M5LaYil9QWxx9vLnkQ9Vnkve17Qn4SjlipqIIBFKjBES6Zxhnvh0EAEw== + +"@rollup/rollup-win32-x64-msvc@4.24.2": + version "4.24.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.2.tgz#0610af0fb8fec52be779d5b163bbbd6930150467" + integrity sha512-2mLH46K1u3r6uwc95hU+OR9q/ggYMpnS7pSp83Ece1HUQgF9Nh/QwTK5rcgbFnV9j+08yBrU5sA/P0RK2MSBNA== "@schematics/angular@14.2.13": version "14.2.13" @@ -8425,64 +8286,64 @@ fflate "^0.4.4" mitt "^3.0.0" -"@sentry/babel-plugin-component-annotate@2.22.3": - version "2.22.3" - resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-2.22.3.tgz#de4970d51a54ef52b21f0d6ec49bd06bf37753c1" - integrity sha512-OlHA+i+vnQHRIdry4glpiS/xTOtgjmpXOt6IBOUqynx5Jd/iK1+fj+t8CckqOx9wRacO/hru2wfW/jFq0iViLg== +"@sentry/babel-plugin-component-annotate@2.22.6": + version "2.22.6" + resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-2.22.6.tgz#829d6caf2c95c1c46108336de4e1049e6521435e" + integrity sha512-V2g1Y1I5eSe7dtUVMBvAJr8BaLRr4CLrgNgtPaZyMT4Rnps82SrZ5zqmEkLXPumlXhLUWR6qzoMNN2u+RXVXfQ== -"@sentry/bundler-plugin-core@2.22.3": - version "2.22.3" - resolved "https://registry.yarnpkg.com/@sentry/bundler-plugin-core/-/bundler-plugin-core-2.22.3.tgz#f8c0a25321216ae9777749c1a4b9d982ae1ec2e1" - integrity sha512-DeoUl0WffcqZZRl5Wy9aHvX4WfZbbWt0QbJ7NJrcEViq+dRAI2FQTYECFLwdZi5Gtb3oyqZICO+P7k8wDnzsjQ== +"@sentry/bundler-plugin-core@2.22.6": + version "2.22.6" + resolved "https://registry.yarnpkg.com/@sentry/bundler-plugin-core/-/bundler-plugin-core-2.22.6.tgz#a1ea1fd43700a3ece9e7db016997e79a2782b87d" + integrity sha512-1esQdgSUCww9XAntO4pr7uAM5cfGhLsgTK9MEwAKNfvpMYJi9NUTYa3A7AZmdA8V6107Lo4OD7peIPrDRbaDCg== dependencies: "@babel/core" "^7.18.5" - "@sentry/babel-plugin-component-annotate" "2.22.3" - "@sentry/cli" "^2.33.1" + "@sentry/babel-plugin-component-annotate" "2.22.6" + "@sentry/cli" "^2.36.1" dotenv "^16.3.1" find-up "^5.0.0" glob "^9.3.2" magic-string "0.30.8" unplugin "1.0.1" -"@sentry/cli-darwin@2.35.0": - version "2.35.0" - resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.35.0.tgz#4bc9a07690f0de75d930ba47f4655f6465191768" - integrity sha512-dRtDaASkB1ncSbCLMIL8bxki4dPMimSdYz74XOUJ5IvDVVzEInEO7PqvyOj/cyafB+1FSNudaZ90ZRvsNN1Maw== - -"@sentry/cli-linux-arm64@2.35.0": - version "2.35.0" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.35.0.tgz#bad8a45b81d2b317f702991783a503f566b2294e" - integrity sha512-NpyVz2lQWWkMa9GZkt0m4cA/wsgYnWOE6Z+4ePUGjbOIG3Ws9DLaHjYxUUYI79kxfbVCp7wLo1S6kOkj+M1Dlw== - -"@sentry/cli-linux-arm@2.35.0": - version "2.35.0" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.35.0.tgz#dacfc219876f5dce3d8c65dab7128ea3e493f561" - integrity sha512-zNL+/HnepZ4/MkIS8wfoUQxSa+k6r0DSSdX1TpDH5436u+3LB5rfCTBfZ624DWHKMoXX+1dI+rWSi+zL8QFMsg== - -"@sentry/cli-linux-i686@2.35.0": - version "2.35.0" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.35.0.tgz#d0e6401b60b0a4b6c3578998995ba6cb31c1bf20" - integrity sha512-vIYwZVqx+kYZdPsenIm+UqjSCKe9Q2Aof6kzrzW0DPR1WyqIWbWG4NbiugiPTiuA1dLjUjYpGP8wyIqb8hxv4w== - -"@sentry/cli-linux-x64@2.35.0": - version "2.35.0" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.35.0.tgz#a1e8e7bff960ed8916b4cc9c0ef75a057e30f989" - integrity sha512-7Wy5QNt6wZ8EaxEbHqP0DEiyUcXRVItRt9jzhpa2nCaawL+fwDOQCjUkHGsdIC+y14UqA+er9CaPCSp8sA6Vaw== - -"@sentry/cli-win32-i686@2.35.0": - version "2.35.0" - resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.35.0.tgz#c1b090f7c740c5b22d1019ca48a84f58cd4b2670" - integrity sha512-XDcBUtO5A9elH+xgFNs6NBjkMBnz0sZLo5DU7LE77qKXULnlLeJ63eZD1ukQIRPvxEDsIEPOllRweLuAlUMDtw== - -"@sentry/cli-win32-x64@2.35.0": - version "2.35.0" - resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.35.0.tgz#f592af483da239be846e556f57f5c6fc7dc1dc54" - integrity sha512-86yHO+31qAXUeAdSCH7MNodn/cn/9xd2fTrxjtfNZWO0pX0jW91sCdomfBxhu5b977cyV9gNcqeBbc9XSIKIIA== - -"@sentry/cli@^2.33.1", "@sentry/cli@^2.35.0": - version "2.35.0" - resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.35.0.tgz#5514eb8f5808bc70707ffa186156f8ff7ca5971e" - integrity sha512-7sHRJViEgHTfEXf+HD1Fb2cwmnxlILmb2NNxghP2vvrgC2PhuwuJU7AX4zg7HjJgxH9HBmnn4AJskDujaJ/6cQ== +"@sentry/cli-darwin@2.37.0": + version "2.37.0" + resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.37.0.tgz#9c890c68abf30ceaad27826212a0963b125b8bbf" + integrity sha512-CsusyMvO0eCPSN7H+sKHXS1pf637PWbS4rZak/7giz/z31/6qiXmeMlcL3f9lLZKtFPJmXVFO9uprn1wbBVF8A== + +"@sentry/cli-linux-arm64@2.37.0": + version "2.37.0" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.37.0.tgz#2070155bade6d72d6b706807c6f365c65f9b82ea" + integrity sha512-2vzUWHLZ3Ct5gpcIlfd/2Qsha+y9M8LXvbZE26VxzYrIkRoLAWcnClBv8m4XsHLMURYvz3J9QSZHMZHSO7kAzw== + +"@sentry/cli-linux-arm@2.37.0": + version "2.37.0" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.37.0.tgz#a08c2133e8e2566074fd6fe4f68e9ffd0c85664a" + integrity sha512-Dz0qH4Yt+gGUgoVsqVt72oDj4VQynRF1QB1/Sr8g76Vbi+WxWZmUh0iFwivYVwWxdQGu/OQrE0tx946HToCRyA== + +"@sentry/cli-linux-i686@2.37.0": + version "2.37.0" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.37.0.tgz#53fff0e7f232b656b0ee3413b66006ee724a4abf" + integrity sha512-MHRLGs4t/CQE1pG+mZBQixyWL6xDZfNalCjO8GMcTTbZFm44S3XRHfYJZNVCgdtnUP7b6OHGcu1v3SWE10LcwQ== + +"@sentry/cli-linux-x64@2.37.0": + version "2.37.0" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.37.0.tgz#2fbaf51ef3884bd6561c987f01ac98f544457150" + integrity sha512-k76ClefKZaDNJZU/H3mGeR8uAzAGPzDRG/A7grzKfBeyhP3JW09L7Nz9IQcSjCK+xr399qLhM2HFCaPWQ6dlMw== + +"@sentry/cli-win32-i686@2.37.0": + version "2.37.0" + resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.37.0.tgz#fa195664da27ce8c40fdb6db1bf1d125cdf587d9" + integrity sha512-FFyi5RNYQQkEg4GkP2f3BJcgQn0F4fjFDMiWkjCkftNPXQG+HFUEtrGsWr6mnHPdFouwbYg3tEPUWNxAoypvTw== + +"@sentry/cli-win32-x64@2.37.0": + version "2.37.0" + resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.37.0.tgz#84fa4d070b8a4a115c46ab38f42d29580143fd26" + integrity sha512-nSMj4OcfQmyL+Tu/jWCJwhKCXFsCZW1MUk6wjjQlRt9SDLfgeapaMlK1ZvT1eZv5ZH6bj3qJfefwj4U8160uOA== + +"@sentry/cli@^2.36.1", "@sentry/cli@^2.37.0": + version "2.37.0" + resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.37.0.tgz#dd01e933cf1caed7d7b6abab5a96044fe1c9c7a1" + integrity sha512-fM3V4gZRJR/s8lafc3O07hhOYRnvkySdPkvL/0e0XW0r+xRwqIAgQ5ECbsZO16A5weUiXVSf03ztDL1FcmbJCQ== dependencies: https-proxy-agent "^5.0.0" node-fetch "^2.6.7" @@ -8490,36 +8351,36 @@ proxy-from-env "^1.1.0" which "^2.0.2" optionalDependencies: - "@sentry/cli-darwin" "2.35.0" - "@sentry/cli-linux-arm" "2.35.0" - "@sentry/cli-linux-arm64" "2.35.0" - "@sentry/cli-linux-i686" "2.35.0" - "@sentry/cli-linux-x64" "2.35.0" - "@sentry/cli-win32-i686" "2.35.0" - "@sentry/cli-win32-x64" "2.35.0" - -"@sentry/rollup-plugin@2.22.3": - version "2.22.3" - resolved "https://registry.yarnpkg.com/@sentry/rollup-plugin/-/rollup-plugin-2.22.3.tgz#18ab4b7903ee723bee4cf789b38bb3febb05faae" - integrity sha512-I1UsnYzZm5W7/Pyah2yxuMRxmzgf5iDKoptFfMaerpRO5oBhFO3tMnKSLAlYMvuXKRoYkInNv6ckkUcSOF6jig== - dependencies: - "@sentry/bundler-plugin-core" "2.22.3" + "@sentry/cli-darwin" "2.37.0" + "@sentry/cli-linux-arm" "2.37.0" + "@sentry/cli-linux-arm64" "2.37.0" + "@sentry/cli-linux-i686" "2.37.0" + "@sentry/cli-linux-x64" "2.37.0" + "@sentry/cli-win32-i686" "2.37.0" + "@sentry/cli-win32-x64" "2.37.0" + +"@sentry/rollup-plugin@2.22.6": + version "2.22.6" + resolved "https://registry.yarnpkg.com/@sentry/rollup-plugin/-/rollup-plugin-2.22.6.tgz#74e9ab69729ee024a497b21b66be3b1992e786d5" + integrity sha512-UmTT4kLytwDJkmfwFCOXIgS6pBi2+ZeM/zU/xJ2R4jE0+s1VvYP3DBGYsUhp4Uf/zDanCawpKJqYZMZtq9EyMA== + dependencies: + "@sentry/bundler-plugin-core" "2.22.6" unplugin "1.0.1" -"@sentry/vite-plugin@2.22.3", "@sentry/vite-plugin@^2.22.3": - version "2.22.3" - resolved "https://registry.yarnpkg.com/@sentry/vite-plugin/-/vite-plugin-2.22.3.tgz#b52802412b6f3d8e3e56742afc9624d9babae5b6" - integrity sha512-+5bsLFRKOZzBp68XigoNE1pJ3tJ4gt2jXluApu54ui0N/yjfqGQ7LQTD7nL4tmJvB5Agwi0e7M7+fcxe9gSgBA== +"@sentry/vite-plugin@2.22.6", "@sentry/vite-plugin@^2.22.6": + version "2.22.6" + resolved "https://registry.yarnpkg.com/@sentry/vite-plugin/-/vite-plugin-2.22.6.tgz#d08a1ede05f137636d5b3c61845d24c0114f0d76" + integrity sha512-zIieP1VLWQb3wUjFJlwOAoaaJygJhXeUoGd0e/Ha2RLb2eW2S+4gjf6y6NqyY71tZ74LYVZKg/4prB6FAZSMXQ== dependencies: - "@sentry/bundler-plugin-core" "2.22.3" + "@sentry/bundler-plugin-core" "2.22.6" unplugin "1.0.1" -"@sentry/webpack-plugin@2.22.3": - version "2.22.3" - resolved "https://registry.yarnpkg.com/@sentry/webpack-plugin/-/webpack-plugin-2.22.3.tgz#a9eeb4689c062eb6dc50671c09f06ec6875b9b02" - integrity sha512-Sq1S6bL3nuoTP5typkj+HPjQ13dqftIE8kACAq4tKkXOpWO9bf6HtqcruEQCxMekbWDTdljsrknQ17ZBx2q66Q== +"@sentry/webpack-plugin@2.22.6": + version "2.22.6" + resolved "https://registry.yarnpkg.com/@sentry/webpack-plugin/-/webpack-plugin-2.22.6.tgz#8c9d27d5cd89153a5b6e08cc9dcb3048b122ffbc" + integrity sha512-BiLhAzQYAz/9kCXKj2LeUKWf/9GBVn2dD0DeYK89s+sjDEaxjbcLBBiLlLrzT7eC9QVj2tUZRKOi6puCfc8ysw== dependencies: - "@sentry/bundler-plugin-core" "2.22.3" + "@sentry/bundler-plugin-core" "2.22.6" unplugin "1.0.1" uuid "^9.0.0" @@ -8616,18 +8477,18 @@ resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5" integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ== -"@size-limit/file@~11.1.0": - version "11.1.0" - resolved "https://registry.yarnpkg.com/@size-limit/file/-/file-11.1.0.tgz#9fe6497f5782cf4d887439b2ded1daf2ad2da620" - integrity sha512-C7Tr9dvw8Jx8If2xyGt0OprB+t03tAf259QJYi4WSl3IRMxH7l8k9qyITa/KCFcmH7tVBduNRSnHAL8Cf90EfQ== +"@size-limit/file@~11.1.6": + version "11.1.6" + resolved "https://registry.yarnpkg.com/@size-limit/file/-/file-11.1.6.tgz#de1244aef06081a93bd594ddc28ef14080ca5b01" + integrity sha512-ojzzJMrTfcSECRnaTjGy0wNIolTCRdyqZTSWG9sG5XEoXG6PNgHXDDS6gf6YNxnqb+rWfCfVe93u6aKi3wEocQ== -"@size-limit/webpack@~11.1.0": - version "11.1.0" - resolved "https://registry.yarnpkg.com/@size-limit/webpack/-/webpack-11.1.0.tgz#955fa23defae08697caa924c11df4e510dd03884" - integrity sha512-Tijbk9hgr2fPDz6M7ig7K4AbD8sacEljO+1c0bVVEr0JdbO7fv5dWsjS0lzKt+EM3GiuGPUVPZ4f381t/FzTSw== +"@size-limit/webpack@~11.1.6": + version "11.1.6" + resolved "https://registry.yarnpkg.com/@size-limit/webpack/-/webpack-11.1.6.tgz#a73f5b82a88d0896e45697863370e7a56e6cf2b9" + integrity sha512-PTZCgwJsgdzdEj2wPFuLm0cCge8N2WbswMcKWNwMJibxQxPAmiF+sZ2F6GYBS7G7K3Fb4ovCliuN+wnnRACPNg== dependencies: - nanoid "^5.0.6" - webpack "^5.90.3" + nanoid "^5.0.7" + webpack "^5.95.0" "@smithy/abort-controller@^2.2.0": version "2.2.0" @@ -9093,11 +8954,6 @@ "@smithy/types" "^2.12.0" tslib "^2.6.2" -"@socket.io/base64-arraybuffer@~1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@socket.io/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz#568d9beae00b0d835f4f8c53fd55714986492e61" - integrity sha512-dOlCBKnDw4iShaIsH/bxujKTM18+2TOAsYz+KSc11Am38H4q5Xw8Bbz97ZYdrVNM+um3p7w86Bvvmcn9q+5+eQ== - "@socket.io/component-emitter@~3.1.0": version "3.1.0" resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553" @@ -9707,16 +9563,21 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== -"@types/estree@1.0.5", "@types/estree@^1.0.1", "@types/estree@^1.0.5": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" - integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== +"@types/estree@1.0.6": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" + integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== "@types/estree@^0.0.51": version "0.0.51" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40" integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== +"@types/estree@^1.0.1", "@types/estree@^1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" + integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== + "@types/express-serve-static-core@*": version "4.17.43" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.43.tgz#10d8444be560cb789c4735aea5eac6e5af45df54" @@ -9826,17 +9687,7 @@ dependencies: "@types/unist" "*" -"@types/history-4@npm:@types/history@4.7.8": - version "4.7.8" - resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" - integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA== - -"@types/history-5@npm:@types/history@4.7.8": - version "4.7.8" - resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" - integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA== - -"@types/history@*": +"@types/history-4@npm:@types/history@4.7.8", "@types/history-5@npm:@types/history@4.7.8", "@types/history@*": version "4.7.8" resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA== @@ -10069,11 +9920,6 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.63.tgz#1788fa8da838dbb5f9ea994b834278205db6ca2b" integrity sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ== -"@types/node@^18.11.17": - version "18.14.2" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.14.2.tgz#c076ed1d7b6095078ad3cf21dfeea951842778b1" - integrity sha512-1uEQxww3DaghA0RxqHx0O0ppVlo43pJhepY51OxuQIKHpjbnYLA7vcdwioNPzIqmC2u3I/dmylcqjlh0e7AyUA== - "@types/normalize-package-data@^2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" @@ -10164,15 +10010,7 @@ "@types/history" "^3" "@types/react" "*" -"@types/react-router-4@npm:@types/react-router@5.1.14": - version "5.1.14" - resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.14.tgz#e0442f4eb4c446541ad7435d44a97f8fe6df40da" - integrity sha512-LAJpqYUaCTMT2anZheoidiIymt8MuX286zoVFPM3DVb23aQBH0mAkFvzpd4LKqiolV8bBtZWT5Qp7hClCNDENw== - dependencies: - "@types/history" "*" - "@types/react" "*" - -"@types/react-router-5@npm:@types/react-router@5.1.14": +"@types/react-router-4@npm:@types/react-router@5.1.14", "@types/react-router-5@npm:@types/react-router@5.1.14": version "5.1.14" resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.14.tgz#e0442f4eb4c446541ad7435d44a97f8fe6df40da" integrity sha512-LAJpqYUaCTMT2anZheoidiIymt8MuX286zoVFPM3DVb23aQBH0mAkFvzpd4LKqiolV8bBtZWT5Qp7hClCNDENw== @@ -10361,13 +10199,6 @@ dependencies: "@types/yargs-parser" "*" -"@types/yauzl@^2.9.1": - version "2.10.0" - resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.0.tgz#b3248295276cf8c6f153ebe6a9aba0c988cb2599" - integrity sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw== - dependencies: - "@types/node" "*" - "@typescript-eslint/eslint-plugin@^5.48.0": version "5.48.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.0.tgz#54f8368d080eb384a455f60c2ee044e948a8ce67" @@ -12381,12 +12212,17 @@ available-typed-arrays@^1.0.7: dependencies: possible-typed-array-names "^1.0.0" -axios@1.6.7, axios@^1.0.0, axios@^1.6.7: - version "1.6.7" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.7.tgz#7b48c2e27c96f9c68a2f8f31e2ab19f59b06b0a7" - integrity sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA== +aws-ssl-profiles@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz#157dd77e9f19b1d123678e93f120e6f193022641" + integrity sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g== + +axios@1.7.7, axios@^1.0.0, axios@^1.7.7: + version "1.7.7" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.7.tgz#2f554296f9892a72ac8d8e4c5b79c14a91d0a47f" + integrity sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q== dependencies: - follow-redirects "^1.15.4" + follow-redirects "^1.15.6" form-data "^4.0.0" proxy-from-env "^1.1.0" @@ -12871,28 +12707,10 @@ bluebird@^3.4.6, bluebird@^3.7.2: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== -body-parser@1.20.1, body-parser@^1.18.3, body-parser@^1.19.0: - version "1.20.1" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" - integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw== - dependencies: - bytes "3.1.2" - content-type "~1.0.4" - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - http-errors "2.0.0" - iconv-lite "0.4.24" - on-finished "2.4.1" - qs "6.11.0" - raw-body "2.5.1" - type-is "~1.6.18" - unpipe "1.0.0" - -body-parser@1.20.2: - version "1.20.2" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" - integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== +body-parser@1.20.3, body-parser@^1.18.3, body-parser@^1.19.0: + version "1.20.3" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6" + integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g== dependencies: bytes "3.1.2" content-type "~1.0.5" @@ -12902,7 +12720,7 @@ body-parser@1.20.2: http-errors "2.0.0" iconv-lite "0.4.24" on-finished "2.4.1" - qs "6.11.0" + qs "6.13.0" raw-body "2.5.2" type-is "~1.6.18" unpipe "1.0.0" @@ -13024,11 +12842,11 @@ braces@^2.3.1: to-regex "^3.0.1" braces@^3.0.2, braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== dependencies: - fill-range "^7.0.1" + fill-range "^7.1.1" broccoli-amd-funnel@^2.0.1: version "2.0.1" @@ -14122,6 +13940,13 @@ chokidar@^3.5.1, chokidar@^3.6.0: optionalDependencies: fsevents "~2.3.2" +chokidar@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.1.tgz#4a6dff66798fb0f72a94f616abbd7e1a19f31d41" + integrity sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA== + dependencies: + readdirp "^4.0.1" + chownr@^1.1.1: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" @@ -14831,21 +14656,31 @@ cookie-signature@^1.1.0: resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.2.0.tgz#4deed303f5f095e7a02c979e3fcb19157f5eaeea" integrity sha512-R0BOPfLGTitaKhgKROKZQN6iyq2iDQcH1DOF8nJoaWapguX5bC2w+Q/I9NmmM5lfcvEarnLZr+cCvmEYYSXvYA== -cookie@0.5.0, cookie@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" - integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== +cookie@0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.1.tgz#2f73c42142d5d5cf71310a74fc4ae61670e5dbc9" + integrity sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w== -cookie@^0.4.1, cookie@~0.4.1: +cookie@^0.4.1: version "0.4.2" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== +cookie@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" + integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== + cookie@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== +cookie@~0.7.2: + version "0.7.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" + integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== + copy-anything@^2.0.1: version "2.0.6" resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-2.0.6.tgz#092454ea9584a7b7ad5573062b2a87f5900fc480" @@ -15442,7 +15277,7 @@ debug@^4.3.5: dependencies: ms "2.1.2" -debug@^4.3.6: +debug@^4.3.6, debug@~4.3.4: version "4.3.7" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== @@ -16082,9 +15917,9 @@ downlevel-dts@~0.11.0: typescript next dset@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/dset/-/dset-3.1.2.tgz#89c436ca6450398396dc6538ea00abc0c54cd45a" - integrity sha512-g/M9sqy3oHe477Ar4voQxWtaPIFw1jTdKZuomOjhCcBx9nHUNn0pu6NopuFFrTh/TRZIKEj+76vLWFu9BNKk+Q== + version "3.1.4" + resolved "https://registry.yarnpkg.com/dset/-/dset-3.1.4.tgz#f8eaf5f023f068a036d08cd07dc9ffb7d0065248" + integrity sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA== duplexer3@^0.1.4: version "0.1.4" @@ -16943,6 +16778,11 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= +encodeurl@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" + integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== + encoding@^0.1.11, encoding@^0.1.13: version "0.1.13" resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" @@ -16957,28 +16797,26 @@ end-of-stream@^1.1.0, end-of-stream@^1.4.1: dependencies: once "^1.4.0" -engine.io-parser@~5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.0.3.tgz#ca1f0d7b11e290b4bfda251803baea765ed89c09" - integrity sha512-BtQxwF27XUNnSafQLvDi0dQ8s3i6VgzSoQMJacpIcGNrlUdfHSKbgm3jmjCVvQluGzqwujQMPAoMai3oYSTurg== - dependencies: - "@socket.io/base64-arraybuffer" "~1.0.2" +engine.io-parser@~5.2.1: + version "5.2.3" + resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.2.3.tgz#00dc5b97b1f233a23c9398d0209504cf5f94d92f" + integrity sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q== -engine.io@~6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.2.0.tgz#003bec48f6815926f2b1b17873e576acd54f41d0" - integrity sha512-4KzwW3F3bk+KlzSOY57fj/Jx6LyRQ1nbcyIadehl+AnXjKT7gDO0ORdRi/84ixvMKTym6ZKuxvbzN62HDDU1Lg== +engine.io@~6.6.0: + version "6.6.2" + resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.6.2.tgz#32bd845b4db708f8c774a4edef4e5c8a98b3da72" + integrity sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw== dependencies: "@types/cookie" "^0.4.1" "@types/cors" "^2.8.12" "@types/node" ">=10.0.0" accepts "~1.3.4" base64id "2.0.0" - cookie "~0.4.1" + cookie "~0.7.2" cors "~2.8.5" debug "~4.3.1" - engine.io-parser "~5.0.3" - ws "~8.2.3" + engine.io-parser "~5.2.1" + ws "~8.17.1" enhanced-resolve@^5.10.0: version "5.16.0" @@ -18290,37 +18128,37 @@ exponential-backoff@^3.1.1: resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.1.tgz#64ac7526fe341ab18a39016cd22c787d01e00bf6" integrity sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw== -express@4.18.2, express@^4.10.7, express@^4.16.4, express@^4.17.1, express@^4.17.3, express@^4.18.1: - version "4.18.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" - integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== +express@4.21.1, express@^4.10.7, express@^4.16.4, express@^4.17.1, express@^4.17.3, express@^4.18.1, express@^4.21.1: + version "4.21.1" + resolved "https://registry.yarnpkg.com/express/-/express-4.21.1.tgz#9dae5dda832f16b4eec941a4e44aa89ec481b281" + integrity sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ== dependencies: accepts "~1.3.8" array-flatten "1.1.1" - body-parser "1.20.1" + body-parser "1.20.3" content-disposition "0.5.4" content-type "~1.0.4" - cookie "0.5.0" + cookie "0.7.1" cookie-signature "1.0.6" debug "2.6.9" depd "2.0.0" - encodeurl "~1.0.2" + encodeurl "~2.0.0" escape-html "~1.0.3" etag "~1.8.1" - finalhandler "1.2.0" + finalhandler "1.3.1" fresh "0.5.2" http-errors "2.0.0" - merge-descriptors "1.0.1" + merge-descriptors "1.0.3" methods "~1.1.2" on-finished "2.4.1" parseurl "~1.3.3" - path-to-regexp "0.1.7" + path-to-regexp "0.1.10" proxy-addr "~2.0.7" - qs "6.11.0" + qs "6.13.0" range-parser "~1.2.1" safe-buffer "5.2.1" - send "0.18.0" - serve-static "1.15.0" + send "0.19.0" + serve-static "1.16.2" setprototypeof "1.2.0" statuses "2.0.1" type-is "~1.6.18" @@ -18385,17 +18223,6 @@ extract-stack@^2.0.0: resolved "https://registry.yarnpkg.com/extract-stack/-/extract-stack-2.0.0.tgz#11367bc865bfcd9bc0db3123e5edb57786f11f9b" integrity sha512-AEo4zm+TenK7zQorGK1f9mJ8L14hnTDi2ZQPR+Mub1NX8zimka1mXpV5LpH8x9HoUmFSHZCfLHqWvp0Y4FxxzQ== -extract-zip@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" - integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== - dependencies: - debug "^4.1.1" - get-stream "^5.1.0" - yauzl "^2.10.0" - optionalDependencies: - "@types/yauzl" "^2.9.1" - fake-indexeddb@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/fake-indexeddb/-/fake-indexeddb-4.0.2.tgz#e7a884158fa576e00f03e973b9874619947013e4" @@ -18573,6 +18400,11 @@ fdir@^6.3.0: resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.3.0.tgz#fcca5a23ea20e767b15e081ee13b3e6488ee0bb0" integrity sha512-QOnuT+BOtivR77wYvCWHfGt9s4Pz1VIMbD463vegT5MLqNXy8rYFT/lPVEqf/bhYeT6qmqrNHhsX+rWwe3rOCQ== +fdir@^6.4.2: + version "6.4.2" + resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.4.2.tgz#ddaa7ce1831b161bc3657bb99cb36e1622702689" + integrity sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ== + fflate@0.8.1, fflate@^0.8.1: version "0.8.1" resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.1.tgz#1ed92270674d2ad3c73f077cd0acf26486dae6c9" @@ -18616,11 +18448,6 @@ filelist@^1.0.1: dependencies: minimatch "^5.0.1" -filesize@^10.0.6: - version "10.0.6" - resolved "https://registry.yarnpkg.com/filesize/-/filesize-10.0.6.tgz#5f4cd2721664cd925db3a7a5a87bbfd6ab5ebb1a" - integrity sha512-rzpOZ4C9vMFDqOa6dNpog92CoLYjD79dnjLk2TYDDtImRIyLTOzqojCb05Opd1WuiWjs+fshhCgTd8cl7y5t+g== - filesize@^9.0.11: version "9.0.11" resolved "https://registry.yarnpkg.com/filesize/-/filesize-9.0.11.tgz#4ac3a42c084232dd9b2a1da0107f32d42fcfa5e4" @@ -18654,10 +18481,10 @@ fill-range@^4.0.0: repeat-string "^1.6.1" to-regex-range "^2.1.0" -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== dependencies: to-regex-range "^5.0.1" @@ -18674,13 +18501,13 @@ finalhandler@1.1.2: statuses "~1.5.0" unpipe "~1.0.0" -finalhandler@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" - integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== +finalhandler@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.1.tgz#0c575f1d1d324ddd1da35ad7ece3df7d19088019" + integrity sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ== dependencies: debug "2.6.9" - encodeurl "~1.0.2" + encodeurl "~2.0.0" escape-html "~1.0.3" on-finished "2.4.1" parseurl "~1.3.3" @@ -18867,10 +18694,10 @@ follow-redirects@^1.0.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== -follow-redirects@^1.15.4: - version "1.15.5" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.5.tgz#54d4d6d062c0fa7d9d17feb008461550e3ba8020" - integrity sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw== +follow-redirects@^1.15.6: + version "1.15.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" + integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== for-each@^0.3.3: version "0.3.3" @@ -20609,9 +20436,9 @@ http-proxy-agent@^5.0.0: debug "4" http-proxy-middleware@^2.0.3: - version "2.0.6" - resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz#e1a4dd6979572c7ab5a4e4b55095d1f32a74963f" - integrity sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw== + version "2.0.7" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz#915f236d92ae98ef48278a95dedf17e991936ec6" + integrity sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA== dependencies: "@types/http-proxy" "^1.17.8" http-proxy "^1.18.1" @@ -21052,10 +20879,13 @@ ioredis@^5.4.1: redis-parser "^3.0.0" standard-as-callback "^2.1.0" -ip@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" - integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ== +ip-address@^9.0.5: + version "9.0.5" + resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-9.0.5.tgz#117a960819b08780c3bd1f14ef3c1cc1d3f3ea5a" + integrity sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g== + dependencies: + jsbn "1.1.0" + sprintf-js "^1.1.3" ipaddr.js@1.9.1: version "1.9.1" @@ -22298,6 +22128,11 @@ jiti@^1.21.0: resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.0.tgz#7c97f8fe045724e136a397f7340475244156105d" integrity sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q== +jiti@^2.0.0: + version "2.3.3" + resolved "https://registry.yarnpkg.com/jiti/-/jiti-2.3.3.tgz#39c66fc77476b92a694e65dfe04b294070e2e096" + integrity sha512-EX4oNDwcXSivPrw2qKH2LB5PoFxEvgtv2JgwW0bU858HoLQ+kutSvjLMUqBd0PeJYEinLWhoI9Ol0eYMqj/wNQ== + js-cleanup@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/js-cleanup/-/js-cleanup-1.2.0.tgz#8dbc65954b1d38b255f1e8cf02cd17b3f7a053f9" @@ -22342,6 +22177,11 @@ js-yaml@^3.10.0, js-yaml@^3.13.0, js-yaml@^3.13.1, js-yaml@^3.2.5, js-yaml@^3.2. argparse "^1.0.7" esprima "^4.0.0" +jsbn@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040" + integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A== + jsdoctypeparser@^9.0.0: version "9.0.0" resolved "https://registry.yarnpkg.com/jsdoctypeparser/-/jsdoctypeparser-9.0.0.tgz#8c97e2fb69315eb274b0f01377eaa5c940bd7b26" @@ -22888,11 +22728,6 @@ license-webpack-plugin@4.0.2: dependencies: webpack-sources "^3.0.0" -lilconfig@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.1.tgz#9d8a246fa753106cfc205fd2d77042faca56e5e3" - integrity sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ== - lilconfig@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.2.tgz#e4a7c3cb549e3a606c8dcc32e5ae1005e62c05cb" @@ -23010,18 +22845,18 @@ loader-utils@3.2.1: integrity sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw== loader-utils@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" - integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== + version "1.4.2" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.2.tgz#29a957f3a63973883eb684f10ffd3d151fec01a3" + integrity sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg== dependencies: big.js "^5.2.2" emojis-list "^3.0.0" json5 "^1.0.1" loader-utils@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0" - integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ== + version "2.0.4" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c" + integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw== dependencies: big.js "^5.2.2" emojis-list "^3.0.0" @@ -23255,11 +23090,6 @@ lodash.restparam@^3.0.0: resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" integrity sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU= -lodash.set@^4.3.2: - version "4.3.2" - resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23" - integrity sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM= - lodash.snakecase@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz#39d714a35357147837aefd64b5dcbb16becd8f8d" @@ -23430,11 +23260,6 @@ lru-cache@^7.14.1: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89" integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== -lru-cache@^8.0.0: - version "8.0.5" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-8.0.5.tgz#983fe337f3e176667f8e567cfcce7cb064ea214e" - integrity sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA== - lru-cache@^9.0.0: version "9.0.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-9.0.1.tgz#ac061ed291f8b9adaca2b085534bb1d3b61bef83" @@ -23453,6 +23278,11 @@ lru-memoizer@2.3.0: lodash.clonedeep "^4.5.0" lru-cache "6.0.0" +lru.min@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/lru.min/-/lru.min-1.1.1.tgz#146e01e3a183fa7ba51049175de04667d5701f0e" + integrity sha512-FbAj6lXil6t8z4z3j0E5mfRlPzxkySotzUHwRXjlpRh10vc6AI6WN62ehZj82VG7M20rqogJ0GLwar2Xa05a8Q== + lunr@^2.3.8: version "2.3.9" resolved "https://registry.yarnpkg.com/lunr/-/lunr-2.3.9.tgz#18b123142832337dd6e964df1a5a7707b25d35e1" @@ -24009,10 +23839,10 @@ merge-anything@^5.1.7: dependencies: is-what "^4.1.8" -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= +merge-descriptors@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5" + integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ== merge-stream@^2.0.0: version "2.0.0" @@ -24964,16 +24794,17 @@ mute-stream@~1.0.0: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-1.0.0.tgz#e31bd9fe62f0aed23520aa4324ea6671531e013e" integrity sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA== -mysql2@^3.7.1: - version "3.7.1" - resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-3.7.1.tgz#bb088fa3f01deefbfe04adaf0d3ec18571b33410" - integrity sha512-4EEqYu57mnkW5+Bvp5wBebY7PpfyrmvJ3knHcmLkp8FyBu4kqgrF2GxIjsC2tbLNZWqJaL21v/MYH7bU5f03oA== +mysql2@^3.11.3: + version "3.11.3" + resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-3.11.3.tgz#8291e6069a0784310846f6437b8527050dfc10c4" + integrity sha512-Qpu2ADfbKzyLdwC/5d4W7+5Yz7yBzCU05YWt5npWzACST37wJsB23wgOSo00qi043urkiRwXtEvJc9UnuLX/MQ== dependencies: + aws-ssl-profiles "^1.1.1" denque "^2.1.0" generate-function "^2.3.1" iconv-lite "^0.6.3" long "^5.2.1" - lru-cache "^8.0.0" + lru.min "^1.0.0" named-placeholders "^1.1.3" seq-queue "^0.0.5" sqlstring "^2.3.2" @@ -25014,11 +24845,6 @@ nanoid@^3.3.6: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== -nanoid@^5.0.6: - version "5.0.6" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-5.0.6.tgz#7f99a033aa843e4dcf9778bdaec5eb02f4dc44d5" - integrity sha512-rRq0eMHoGZxlvaFOUdK1Ev83Bd1IgzzR+WJ3IbDJ7QOSdAxYjlurSPqFs9s4lJg29RT6nPwizFtJhQS6V5xgiA== - nanoid@^5.0.7: version "5.0.7" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-5.0.7.tgz#6452e8c5a816861fd9d2b898399f7e5fd6944cc6" @@ -25277,14 +25103,13 @@ no-case@^3.0.4: lower-case "^2.0.2" tslib "^2.0.3" -nock@^13.0.4, nock@^13.1.0: - version "13.2.4" - resolved "https://registry.yarnpkg.com/nock/-/nock-13.2.4.tgz#43a309d93143ee5cdcca91358614e7bde56d20e1" - integrity sha512-8GPznwxcPNCH/h8B+XZcKjYPXnUV5clOKCjAqyjsiqA++MpNx9E9+t8YPp0MbThO+KauRo7aZJ1WuIZmOrT2Ug== +nock@^13.5.5: + version "13.5.5" + resolved "https://registry.yarnpkg.com/nock/-/nock-13.5.5.tgz#cd1caaca281d42be17d51946367a3d53a6af3e78" + integrity sha512-XKYnqUrCwXC8DGG1xX4YH5yNIrlh9c065uaMZZHUoeUUINTOyt+x/G+ezYk0Ft6ExSREVIs+qBJDK503viTfFA== dependencies: debug "^4.1.0" json-stringify-safe "^5.0.1" - lodash.set "^4.3.2" propagate "^2.0.0" node-abi@^3.3.0: @@ -26036,6 +25861,11 @@ object-inspect@^1.12.2, object-inspect@^1.9.0: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== +object-inspect@^1.13.1: + version "1.13.2" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff" + integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g== + object-is@^1.0.1: version "1.1.5" resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" @@ -26527,11 +26357,6 @@ p-timeout@^5.0.2: resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-5.1.0.tgz#b3c691cf4415138ce2d9cfe071dba11f0fee085b" integrity sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew== -p-timeout@^6.0.0: - version "6.1.1" - resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-6.1.1.tgz#bcee5e37d730f5474d973b6ff226751a1a5e6ff1" - integrity sha512-yqz2Wi4fiFRpMmK0L2pGAU49naSUaP23fFIQL2Y6YT+qDGPoFwpvgQM/wzc6F8JoenUkIlAFa4Ql7NguXBxI7w== - p-try@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" @@ -26879,10 +26704,10 @@ path-scurry@^1.6.1: lru-cache "^9.0.0" minipass "^5.0.0" -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= +path-to-regexp@0.1.10: + version "0.1.10" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.10.tgz#67e9108c5c0551b9e5326064387de4763c4d5f8b" + integrity sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w== path-to-regexp@3.2.0: version "3.2.0" @@ -26890,21 +26715,16 @@ path-to-regexp@3.2.0: integrity sha512-jczvQbCUS7XmS7o+y1aEO9OBVFeZBQ1MDSEqmO7xSoPgOPoowY/SxLpZ6Vh97/8qHZOteiCKb7gkG9gA2ZUxJA== path-to-regexp@^1.5.3, path-to-regexp@^1.7.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" - integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== + version "1.9.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.9.0.tgz#5dc0753acbf8521ca2e0f137b4578b917b10cf24" + integrity sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g== dependencies: isarray "0.0.1" -path-to-regexp@^6.2.0: - version "6.2.2" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.2.tgz#324377a83e5049cbecadc5554d6a63a9a4866b36" - integrity sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw== - -path-to-regexp@^6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.1.tgz#d54934d6798eb9e5ef14e7af7962c945906918e5" - integrity sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw== +path-to-regexp@^6.2.0, path-to-regexp@^6.2.1: + version "6.3.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.3.0.tgz#2b6a26a337737a8e1416f9272ed0766b1c0389f4" + integrity sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ== path-type@^2.0.0: version "2.0.0" @@ -27185,12 +27005,12 @@ pkg-up@^3.1.0: dependencies: find-up "^3.0.0" -playwright-core@1.44.1, playwright-core@^1.44.1: +playwright-core@1.44.1: version "1.44.1" resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.44.1.tgz#53ec975503b763af6fc1a7aa995f34bc09ff447c" integrity sha512-wh0JWtYTrhv1+OSsLPgFzGzt67Y7BE/ZS3jEqgGBlp2ppp1ZDj8c+9IARNW4dwf1poq5MgHreEM2KV/GuR4cFA== -playwright@1.44.1, playwright@^1.44.1: +playwright@1.44.1: version "1.44.1" resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.44.1.tgz#5634369d777111c1eea9180430b7a184028e7892" integrity sha512-qr/0UJ5CFAtloI3avF95Y0L1xQo6r3LQArLIg/z/PoGJ6xa+EwzrwO5lpNr/09STxdHuUoP2mvuELJS+hLdtgg== @@ -28425,7 +28245,14 @@ pupa@^2.1.1: dependencies: escape-goat "^2.0.0" -qs@6.11.0, qs@^6.4.0: +qs@6.13.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" + integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== + dependencies: + side-channel "^1.0.6" + +qs@^6.4.0: version "6.11.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== @@ -28519,16 +28346,6 @@ range-parser@^1.2.1, range-parser@~1.2.1: resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== -raw-body@2.5.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" - integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== - dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.4.24" - unpipe "1.0.0" - raw-body@2.5.2: version "2.5.2" resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" @@ -28644,7 +28461,7 @@ react-is@^18.0.0: dependencies: "@remix-run/router" "1.0.2" -"react-router-6@npm:react-router@6.3.0": +"react-router-6@npm:react-router@6.3.0", react-router@6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.3.0.tgz#3970cc64b4cb4eae0c1ea5203a80334fdd175557" integrity sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ== @@ -28659,13 +28476,6 @@ react-router-dom@^6.2.2: history "^5.2.0" react-router "6.3.0" -react-router@6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.3.0.tgz#3970cc64b4cb4eae0c1ea5203a80334fdd175557" - integrity sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ== - dependencies: - history "^5.2.0" - react@^18.0.0: version "18.0.0" resolved "https://registry.yarnpkg.com/react/-/react-18.0.0.tgz#b468736d1f4a5891f38585ba8e8fb29f91c3cb96" @@ -28854,6 +28664,11 @@ readdir-glob@^1.1.2: dependencies: minimatch "^5.1.0" +readdirp@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.0.2.tgz#388fccb8b75665da3abffe2d8f8ed59fe74c230a" + integrity sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA== + readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" @@ -29284,9 +29099,9 @@ requirejs-config-file@^4.0.0: stringify-object "^3.2.1" requirejs@^2.3.6: - version "2.3.6" - resolved "https://registry.yarnpkg.com/requirejs/-/requirejs-2.3.6.tgz#e5093d9601c2829251258c0b9445d4d19fa9e7c9" - integrity sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg== + version "2.3.7" + resolved "https://registry.yarnpkg.com/requirejs/-/requirejs-2.3.7.tgz#0b22032e51a967900e0ae9f32762c23a87036bd0" + integrity sha512-DouTG8T1WanGok6Qjg2SXuCMzszOo0eHeH9hDZ5Y4x8Je+9JB38HdTLT4/VA8OaUhBa0JPVHJ0pyBkM1z+pDsw== requires-port@^1.0.0: version "1.0.0" @@ -29693,7 +29508,7 @@ rollup-pluginutils@^2.8.1, rollup-pluginutils@^2.8.2: dependencies: estree-walker "^0.6.1" -rollup@3.29.5: +rollup@3.29.5, rollup@^3.27.1, rollup@^3.28.1: version "3.29.5" resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.29.5.tgz#8a2e477a758b520fb78daf04bca4c522c1da8a54" integrity sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w== @@ -29701,84 +29516,37 @@ rollup@3.29.5: fsevents "~2.3.2" rollup@^2.70.0: - version "2.79.1" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.79.1.tgz#bedee8faef7c9f93a2647ac0108748f497f081c7" - integrity sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw== + version "2.79.2" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.79.2.tgz#f150e4a5db4b121a21a747d762f701e5e9f49090" + integrity sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ== optionalDependencies: fsevents "~2.3.2" -rollup@^3.27.1, rollup@^3.28.1: - version "3.29.4" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.29.4.tgz#4d70c0f9834146df8705bfb69a9a19c9e1109981" - integrity sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw== - optionalDependencies: - fsevents "~2.3.2" - -rollup@^4.13.0: - version "4.13.0" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.13.0.tgz#dd2ae144b4cdc2ea25420477f68d4937a721237a" - integrity sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg== +rollup@^4.13.0, rollup@^4.18.0, rollup@^4.2.0, rollup@^4.20.0, rollup@^4.24.2: + version "4.24.2" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.24.2.tgz#04bbe819c1a0cd933533b79687f5dc43efb7a7f0" + integrity sha512-do/DFGq5g6rdDhdpPq5qb2ecoczeK6y+2UAjdJ5trjQJj5f1AiVdLRWRc9A9/fFukfvJRgM0UXzxBIYMovm5ww== dependencies: - "@types/estree" "1.0.5" - optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.13.0" - "@rollup/rollup-android-arm64" "4.13.0" - "@rollup/rollup-darwin-arm64" "4.13.0" - "@rollup/rollup-darwin-x64" "4.13.0" - "@rollup/rollup-linux-arm-gnueabihf" "4.13.0" - "@rollup/rollup-linux-arm64-gnu" "4.13.0" - "@rollup/rollup-linux-arm64-musl" "4.13.0" - "@rollup/rollup-linux-riscv64-gnu" "4.13.0" - "@rollup/rollup-linux-x64-gnu" "4.13.0" - "@rollup/rollup-linux-x64-musl" "4.13.0" - "@rollup/rollup-win32-arm64-msvc" "4.13.0" - "@rollup/rollup-win32-ia32-msvc" "4.13.0" - "@rollup/rollup-win32-x64-msvc" "4.13.0" - fsevents "~2.3.2" - -rollup@^4.18.0, rollup@^4.20.0: - version "4.22.4" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.22.4.tgz#4135a6446671cd2a2453e1ad42a45d5973ec3a0f" - integrity sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A== - dependencies: - "@types/estree" "1.0.5" - optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.22.4" - "@rollup/rollup-android-arm64" "4.22.4" - "@rollup/rollup-darwin-arm64" "4.22.4" - "@rollup/rollup-darwin-x64" "4.22.4" - "@rollup/rollup-linux-arm-gnueabihf" "4.22.4" - "@rollup/rollup-linux-arm-musleabihf" "4.22.4" - "@rollup/rollup-linux-arm64-gnu" "4.22.4" - "@rollup/rollup-linux-arm64-musl" "4.22.4" - "@rollup/rollup-linux-powerpc64le-gnu" "4.22.4" - "@rollup/rollup-linux-riscv64-gnu" "4.22.4" - "@rollup/rollup-linux-s390x-gnu" "4.22.4" - "@rollup/rollup-linux-x64-gnu" "4.22.4" - "@rollup/rollup-linux-x64-musl" "4.22.4" - "@rollup/rollup-win32-arm64-msvc" "4.22.4" - "@rollup/rollup-win32-ia32-msvc" "4.22.4" - "@rollup/rollup-win32-x64-msvc" "4.22.4" - fsevents "~2.3.2" - -rollup@^4.2.0: - version "4.9.1" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.9.1.tgz#351d6c03e4e6bcd7a0339df3618d2aeeb108b507" - integrity sha512-pgPO9DWzLoW/vIhlSoDByCzcpX92bKEorbgXuZrqxByte3JFk2xSW2JEeAcyLc9Ru9pqcNNW+Ob7ntsk2oT/Xw== + "@types/estree" "1.0.6" optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.9.1" - "@rollup/rollup-android-arm64" "4.9.1" - "@rollup/rollup-darwin-arm64" "4.9.1" - "@rollup/rollup-darwin-x64" "4.9.1" - "@rollup/rollup-linux-arm-gnueabihf" "4.9.1" - "@rollup/rollup-linux-arm64-gnu" "4.9.1" - "@rollup/rollup-linux-arm64-musl" "4.9.1" - "@rollup/rollup-linux-riscv64-gnu" "4.9.1" - "@rollup/rollup-linux-x64-gnu" "4.9.1" - "@rollup/rollup-linux-x64-musl" "4.9.1" - "@rollup/rollup-win32-arm64-msvc" "4.9.1" - "@rollup/rollup-win32-ia32-msvc" "4.9.1" - "@rollup/rollup-win32-x64-msvc" "4.9.1" + "@rollup/rollup-android-arm-eabi" "4.24.2" + "@rollup/rollup-android-arm64" "4.24.2" + "@rollup/rollup-darwin-arm64" "4.24.2" + "@rollup/rollup-darwin-x64" "4.24.2" + "@rollup/rollup-freebsd-arm64" "4.24.2" + "@rollup/rollup-freebsd-x64" "4.24.2" + "@rollup/rollup-linux-arm-gnueabihf" "4.24.2" + "@rollup/rollup-linux-arm-musleabihf" "4.24.2" + "@rollup/rollup-linux-arm64-gnu" "4.24.2" + "@rollup/rollup-linux-arm64-musl" "4.24.2" + "@rollup/rollup-linux-powerpc64le-gnu" "4.24.2" + "@rollup/rollup-linux-riscv64-gnu" "4.24.2" + "@rollup/rollup-linux-s390x-gnu" "4.24.2" + "@rollup/rollup-linux-x64-gnu" "4.24.2" + "@rollup/rollup-linux-x64-musl" "4.24.2" + "@rollup/rollup-win32-arm64-msvc" "4.24.2" + "@rollup/rollup-win32-ia32-msvc" "4.24.2" + "@rollup/rollup-win32-x64-msvc" "4.24.2" fsevents "~2.3.2" rrweb-cssom@^0.6.0: @@ -30116,6 +29884,25 @@ send@0.18.0: range-parser "~1.2.1" statuses "2.0.1" +send@0.19.0: + version "0.19.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8" + integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw== + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "2.0.0" + mime "1.6.0" + ms "2.1.3" + on-finished "2.4.1" + range-parser "~1.2.1" + statuses "2.0.1" + seq-queue@^0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/seq-queue/-/seq-queue-0.0.5.tgz#d56812e1c017a6e4e7c3e3a37a1da6d78dd3c93e" @@ -30165,7 +29952,17 @@ serve-placeholder@^2.0.2: dependencies: defu "^6.1.4" -serve-static@1.15.0, serve-static@^1.15.0: +serve-static@1.16.2: + version "1.16.2" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.2.tgz#b6a5343da47f6bdd2673848bf45754941e803296" + integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw== + dependencies: + encodeurl "~2.0.0" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.19.0" + +serve-static@^1.15.0: version "1.15.0" resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== @@ -30347,6 +30144,16 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" +side-channel@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" + integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + object-inspect "^1.13.1" + sift@13.5.2: version "13.5.2" resolved "https://registry.yarnpkg.com/sift/-/sift-13.5.2.tgz#24a715e13c617b086166cd04917d204a591c9da6" @@ -30403,15 +30210,6 @@ simple-get@^4.0.0, simple-get@^4.0.1: once "^1.3.1" simple-concat "^1.0.0" -simple-git@^3.16.0: - version "3.16.1" - resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-3.16.1.tgz#b67f18cbd3c68bbc4b9177ed49256afe51f12d47" - integrity sha512-xzRxMKiy1zEYeHGXgAzvuXffDS0xgsq07Oi4LWEEcVH29vLpcZ2tyQRWyK0NLLlCVaKysZeem5tC1qHEOxsKwA== - dependencies: - "@kwsites/file-exists" "^1.1.1" - "@kwsites/promise-deferred" "^1.1.1" - debug "^4.3.4" - simple-git@^3.27.0: version "3.27.0" resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-3.27.0.tgz#f4b09e807bda56a4a3968f635c0e4888d3decbd5" @@ -30426,11 +30224,6 @@ simple-html-tokenizer@^0.5.11: resolved "https://registry.yarnpkg.com/simple-html-tokenizer/-/simple-html-tokenizer-0.5.11.tgz#4c5186083c164ba22a7b477b7687ac056ad6b1d9" integrity sha512-C2WEK/Z3HoSFbYq8tI7ni3eOo/NneSPRoPpcM7WdLjFOArFuyXEjAoCdOC3DgMfRyziZQ1hCNR4mrNdWEvD0og== -simple-statistics@^7.8.0: - version "7.8.3" - resolved "https://registry.yarnpkg.com/simple-statistics/-/simple-statistics-7.8.3.tgz#62998dd7786ba14fa27b07f4f3cd498466f7961a" - integrity sha512-JFvMY00t6SBGtwMuJ+nqgsx9ylkMiJ5JlK9bkj8AdvniIe5615wWQYkKHXe84XtSuc40G/tlrPu0A5/NlJvv8A== - simple-swizzle@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" @@ -30473,18 +30266,18 @@ sisteransi@^1.0.5: resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== -size-limit@~11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/size-limit/-/size-limit-11.1.0.tgz#f0386fac40bfc26f4d8940e9d4824135e8391691" - integrity sha512-HvqVEUDJ3y/XL69pR3vxTavPr6s4S9ScJhHjLEjpctF0Ymgo1StmHgFeQoyQYb/1ip7olkWxJG3wRcZk1hZcPg== +size-limit@~11.1.6: + version "11.1.6" + resolved "https://registry.yarnpkg.com/size-limit/-/size-limit-11.1.6.tgz#75cd54f9326d1b065ebcb6ca9ec27294e7ccdfb1" + integrity sha512-S5ux2IB8rU26xwVgMskmknGMFkieaIAqDLuwgKiypk6oa4lFsie8yFPrzRFV+yrLDY2GddjXuCaVk5PveVOHiQ== dependencies: bytes-iec "^3.1.1" - chokidar "^3.6.0" - globby "^14.0.1" - jiti "^1.21.0" - lilconfig "^3.1.1" + chokidar "^4.0.1" + jiti "^2.0.0" + lilconfig "^3.1.2" nanospinner "^1.1.0" - picocolors "^1.0.0" + picocolors "^1.1.0" + tinyglobby "^0.2.7" skip-regex@^1.0.2: version "1.0.2" @@ -30568,30 +30361,34 @@ snapdragon@^0.8.1: source-map-resolve "^0.5.0" use "^3.1.0" -socket.io-adapter@~2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz#b50a4a9ecdd00c34d4c8c808224daa1a786152a6" - integrity sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg== +socket.io-adapter@~2.5.2: + version "2.5.5" + resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz#c7a1f9c703d7756844751b6ff9abfc1780664082" + integrity sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg== + dependencies: + debug "~4.3.4" + ws "~8.17.1" -socket.io-parser@~4.2.0: - version "4.2.3" - resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.3.tgz#926bcc6658e2ae0883dc9dee69acbdc76e4e3667" - integrity sha512-JMafRntWVO2DCJimKsRTh/wnqVvO4hrfwOqtO7f+uzwsQMuxO6VwImtYxaQ+ieoyshWOTJyV0fA21lccEXRPpQ== +socket.io-parser@~4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.4.tgz#c806966cf7270601e47469ddeec30fbdfda44c83" + integrity sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew== dependencies: "@socket.io/component-emitter" "~3.1.0" debug "~4.3.1" socket.io@^4.1.2: - version "4.5.3" - resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.5.3.tgz#44dffea48d7f5aa41df4a66377c386b953bc521c" - integrity sha512-zdpnnKU+H6mOp7nYRXH4GNv1ux6HL6+lHL8g7Ds7Lj8CkdK1jJK/dlwsKDculbyOHifcJ0Pr/yeXnZQ5GeFrcg== + version "4.8.1" + resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.8.1.tgz#fa0eaff965cc97fdf4245e8d4794618459f7558a" + integrity sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg== dependencies: accepts "~1.3.4" base64id "~2.0.0" + cors "~2.8.5" debug "~4.3.2" - engine.io "~6.2.0" - socket.io-adapter "~2.4.0" - socket.io-parser "~4.2.0" + engine.io "~6.6.0" + socket.io-adapter "~2.5.2" + socket.io-parser "~4.2.4" sockjs@^0.3.24: version "0.3.24" @@ -30612,11 +30409,11 @@ socks-proxy-agent@^7.0.0: socks "^2.6.2" socks@^2.6.2: - version "2.7.1" - resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.1.tgz#d8e651247178fde79c0663043e07240196857d55" - integrity sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ== + version "2.8.3" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.3.tgz#1ebd0f09c52ba95a09750afe3f3f9f724a800cb5" + integrity sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw== dependencies: - ip "^2.0.0" + ip-address "^9.0.5" smart-buffer "^4.2.0" solid-js@^1.8.11: @@ -30927,6 +30724,11 @@ sprintf-js@^1.0.3: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug== +sprintf-js@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" + integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -31114,16 +30916,7 @@ string-template@~0.2.1: resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add" integrity sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0= -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@4.2.3, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@4.2.3, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -31235,14 +31028,7 @@ stringify-object@^3.2.1: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -31952,6 +31738,14 @@ tinyglobby@0.2.6, tinyglobby@^0.2.6: fdir "^6.3.0" picomatch "^4.0.2" +tinyglobby@^0.2.7: + version "0.2.10" + resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.10.tgz#e712cf2dc9b95a1f5c5bbd159720e15833977a0f" + integrity sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew== + dependencies: + fdir "^6.4.2" + picomatch "^4.0.2" + tinypool@^0.8.3: version "0.8.4" resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-0.8.4.tgz#e217fe1270d941b39e98c625dcecebb1408c9aa8" @@ -32187,7 +31981,7 @@ ts-jest@^27.1.4: semver "7.x" yargs-parser "20.x" -ts-node@10.9.1, ts-node@^10.9.1: +ts-node@10.9.1: version "10.9.1" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== @@ -32259,6 +32053,11 @@ tslib@2.6.3, tslib@^2.2.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== +tslib@2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01" + integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA== + tslib@^1.11.1, tslib@^1.8.1, tslib@^1.9.0: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" @@ -33846,7 +33645,7 @@ webpack-bundle-analyzer@^4.10.2: sirv "^2.0.3" ws "^7.3.1" -webpack-dev-middleware@5.3.3, webpack-dev-middleware@^5.3.1: +webpack-dev-middleware@5.3.3: version "5.3.3" resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz#efae67c2793908e7311f1d9b06f2a08dcc97e51f" integrity sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA== @@ -33857,6 +33656,17 @@ webpack-dev-middleware@5.3.3, webpack-dev-middleware@^5.3.1: range-parser "^1.2.1" schema-utils "^4.0.0" +webpack-dev-middleware@^5.3.1: + version "5.3.4" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz#eb7b39281cbce10e104eb2b8bf2b63fce49a3517" + integrity sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q== + dependencies: + colorette "^2.0.10" + memfs "^3.4.3" + mime-types "^2.1.31" + range-parser "^1.2.1" + schema-utils "^4.0.0" + webpack-dev-server@4.11.0: version "4.11.0" resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.11.0.tgz#290ee594765cd8260adfe83b2d18115ea04484e7" @@ -33957,10 +33767,10 @@ webpack@5.76.1: watchpack "^2.4.0" webpack-sources "^3.2.3" -webpack@^5.90.3, webpack@^5.94.0, webpack@~5.94.0: - version "5.94.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.94.0.tgz#77a6089c716e7ab90c1c67574a28da518a20970f" - integrity sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg== +webpack@^5.95.0, webpack@~5.95.0: + version "5.95.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.95.0.tgz#8fd8c454fa60dad186fbe36c400a55848307b4c0" + integrity sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q== dependencies: "@types/estree" "^1.0.5" "@webassemblyjs/ast" "^1.12.1" @@ -34259,16 +34069,7 @@ wrangler@^3.67.1: optionalDependencies: fsevents "~2.3.2" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@7.0.0, wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@7.0.0, wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -34339,35 +34140,20 @@ write-pkg@4.0.0: type-fest "^0.4.1" write-json-file "^3.2.0" -ws@^7.3.1: - version "7.5.9" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" - integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== - -ws@^7.4.6: - version "7.5.6" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b" - integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA== +ws@^7.3.1, ws@^7.4.6: + version "7.5.10" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" + integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== -ws@^8.13.0: - version "8.17.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.0.tgz#d145d18eca2ed25aaf791a183903f7be5e295fea" - integrity sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow== - -ws@^8.17.1, ws@^8.18.0: +ws@^8.13.0, ws@^8.17.1, ws@^8.18.0, ws@^8.4.2: version "8.18.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== -ws@^8.4.2: - version "8.16.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.16.0.tgz#d1cd774f36fbc07165066a60e40323eab6446fd4" - integrity sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ== - -ws@~8.2.3: - version "8.2.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.2.3.tgz#63a56456db1b04367d0b721a0b80cae6d8becbba" - integrity sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA== +ws@~8.17.1: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" + integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== xdg-basedir@^4.0.0: version "4.0.0"