diff --git a/.github/workflows/canary.yml b/.github/workflows/canary.yml index 9dcbd43e5547..42c1594ebc5b 100644 --- a/.github/workflows/canary.yml +++ b/.github/workflows/canary.yml @@ -18,6 +18,7 @@ env: ${{ github.workspace }}/node_modules ${{ github.workspace }}/packages/*/node_modules ${{ github.workspace }}/dev-packages/*/node_modules + ${{ github.workspace }}/packages/utils/build permissions: contents: read diff --git a/.github/workflows/external-contributors.yml b/.github/workflows/external-contributors.yml index a0869cc3d2d4..50acb2be8e73 100644 --- a/.github/workflows/external-contributors.yml +++ b/.github/workflows/external-contributors.yml @@ -1,6 +1,6 @@ name: "CI: Mention external contributors" on: - pull_request: + pull_request_target: types: - closed branches: @@ -18,7 +18,7 @@ jobs: && github.event.pull_request.author_association != 'COLLABORATOR' && github.event.pull_request.author_association != 'MEMBER' && github.event.pull_request.author_association != 'OWNER' - && github.actor != 'dependabot[bot]' + && endsWith(github.actor, '[bot]') == false steps: - uses: actions/checkout@v4 - name: Set up Node diff --git a/.size-limit.js b/.size-limit.js index 636b9c64413a..2e7899cb934a 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -177,7 +177,7 @@ module.exports = [ path: createCDNPath('bundle.tracing.replay.min.js'), gzip: false, brotli: false, - limit: '220 KB', + limit: '230 KB', }, { name: 'CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed', diff --git a/CHANGELOG.md b/CHANGELOG.md index 81b502aecd4a..392564b0c24e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,38 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +## 8.18.0 + +### Important Changes + +- **ref: Deprecate `enableTracing` (12897)** + +The `enableTracing` option has been deprecated and will be removed in the next major version. We recommend removing it +in favor of the `tracesSampleRate` and `tracesSampler` options. If you want to enable performance monitoring, please set +the `tracesSampleRate` to a sample rate of your choice, or provide a sampling function as `tracesSampler` option +instead. If you want to disable performance monitoring, remove the `tracesSampler` and `tracesSampleRate` options. + +### Other Changes + +- feat(node): Expose `exclude` and `include` options for ESM loader (#12910) +- feat(browser): Add user agent to INP standalone span attributes (#12896) +- feat(nextjs): Add `experimental_captureRequestError` for `onRequestError` hook (#12885) +- feat(replay): Bump `rrweb` to 2.25.0 (#12478) +- feat(tracing): Add long animation frame tracing (#12646) +- fix: Cleanup hooks when they are not used anymore (#12852) +- fix(angular): Guard `ErrorEvent` check in ErrorHandler to avoid throwing in Node environments (#12892) +- fix(inp): Ensure INP spans have correct transaction (#12871) +- fix(nestjs): Do not make SentryTraced() decorated functions async (#12879) +- fix(nextjs): Support automatic instrumentation for app directory with custom page extensions (#12858) +- fix(node): Ensure correct URL is passed to `ignoreIncomingRequests` callback (#12929) +- fix(otel): Do not add `otel.kind: INTERNAL` attribute (#12841) +- fix(solidstart): Set proper sentry origin for solid router integration when used in solidstart sdk (#12919) +- fix(sveltekit): Add Vite peer dep for proper type resolution (#12926) +- fix(tracing): Ensure you can pass `null` as `parentSpan` in `startSpan*` (#12928) +- ref(core): Small bundle size improvement (#12830) + +Work in this release was contributed by @GitSquared and @mcous. Thank you for your contributions! + ## 8.17.0 - feat: Upgrade OTEL deps (#12809) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 215527c16495..b74d59693a18 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -40,6 +40,9 @@ able to use it. From the top level of the repo, there are three commands availab dependencies (`utils`, `core`, `browser`, etc), and all packages which depend on it (currently `gatsby` and `nextjs`)) - `yarn build:dev:watch`, which runs `yarn build:dev` in watch mode (recommended) +Note: Due to package incompatibilities between Python versions, building native binaries currently requires a Python +version <3.12. + You can also run a production build via `yarn build`, which will build everything except for the tarballs for publishing to NPM. You can use this if you want to bundle Sentry yourself. The build output can be found in the packages `build/` folder, e.g. `packages/browser/build`. Bundled files can be found in `packages/browser/build/bundles`. Note that there diff --git a/MIGRATION.md b/MIGRATION.md index 172d6cb433f3..88235b6fd235 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -711,6 +711,7 @@ Removed top-level exports: `enableAnrDetection`, `Anr`, `deepReadDirSync`, `runW - [Removal of `enableAnrDetection` and `Anr` class](./MIGRATION.md#removal-of-enableanrdetection-and-anr-class) - [Removal of `deepReadDirSync` method](./MIGRATION.md#removal-of-deepreaddirsync-method) - [Removal of `runWithAsyncContext` method](./MIGRATION.md#removal-of-runwithasynccontext-method) +- [Removal of `Apollo` integration](./MIGRATION.md#removal-of-apollo-integration) #### Removal of `enableAnrDetection` and `Anr` class @@ -737,6 +738,21 @@ Sentry.withIsolationScope(() => { }); ``` +#### Removal of Apollo integration + +The Apollo integration has been removed in `8.x` as `8.x` automatically adds GraphQL support via `graphqlIntegration` +which is automatically enabled. + +```js +// before (v7) +Sentry.init({ + integrations: [Sentry.integrations.Apollo()], +}); + +// after (v8) +Sentry.init({}); +``` + ### Next.js SDK Removed top-level exports: `withSentryApi`, `withSentryAPI`, `withSentryGetServerSideProps`, `withSentryGetStaticProps`, diff --git a/biome.json b/biome.json index ccb69e4746db..cee069ac3127 100644 --- a/biome.json +++ b/biome.json @@ -43,7 +43,9 @@ ".angular/**", "angular.json", "ember/instance-initializers/**", - "ember/types.d.ts" + "ember/types.d.ts", + ".output", + ".vinxi" ] }, "files": { diff --git a/dev-packages/browser-integration-tests/suites/replay/canvas/manualSnapshot/template.html b/dev-packages/browser-integration-tests/suites/replay/canvas/manualSnapshot/template.html index 5f23d569fcc2..bd12f84b090a 100644 --- a/dev-packages/browser-integration-tests/suites/replay/canvas/manualSnapshot/template.html +++ b/dev-packages/browser-integration-tests/suites/replay/canvas/manualSnapshot/template.html @@ -13,7 +13,6 @@ function draw() { const canvas = document.getElementById("canvas"); if (canvas.getContext) { - console.log('has canvas') const ctx = canvas.getContext("2d"); ctx.fillRect(25, 25, 100, 100); diff --git a/dev-packages/browser-integration-tests/suites/replay/canvas/records/template.html b/dev-packages/browser-integration-tests/suites/replay/canvas/records/template.html index 5f23d569fcc2..bd12f84b090a 100644 --- a/dev-packages/browser-integration-tests/suites/replay/canvas/records/template.html +++ b/dev-packages/browser-integration-tests/suites/replay/canvas/records/template.html @@ -13,7 +13,6 @@ function draw() { const canvas = document.getElementById("canvas"); if (canvas.getContext) { - console.log('has canvas') const ctx = canvas.getContext("2d"); ctx.fillRect(25, 25, 100, 100); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled/assets/script.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled/assets/script.js new file mode 100644 index 000000000000..9ac3d6fb33d2 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled/assets/script.js @@ -0,0 +1,12 @@ +(() => { + const startTime = Date.now(); + + function getElasped() { + const time = Date.now(); + return time - startTime; + } + + while (getElasped() < 101) { + // + } +})(); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled/init.js new file mode 100644 index 000000000000..e1b3f6b13b01 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled/init.js @@ -0,0 +1,11 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [ + Sentry.browserTracingIntegration({ enableLongTask: false, enableLongAnimationFrame: false, idleTimeout: 9000 }), + ], + tracesSampleRate: 1, +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled/template.html b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled/template.html new file mode 100644 index 000000000000..4cd015b16f51 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled/template.html @@ -0,0 +1,10 @@ + + + + + + +
Rendered Before Long Animation Frame
+ + + diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled/test.ts new file mode 100644 index 000000000000..2527d5a67302 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-disabled/test.ts @@ -0,0 +1,27 @@ +import type { Route } from '@playwright/test'; +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/types'; + +import { sentryTest } from '../../../../utils/fixtures'; +import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; + +sentryTest( + 'should not capture long animation frame when flag is disabled.', + async ({ browserName, getLocalTestPath, page }) => { + // Long animation frames only work on chrome + if (shouldSkipTracingTest() || browserName !== 'chromium') { + sentryTest.skip(); + } + + await page.route('**/path/to/script.js', (route: Route) => + route.fulfill({ path: `${__dirname}/assets/script.js` }), + ); + + const url = await getLocalTestPath({ testDir: __dirname }); + + const eventData = await getFirstSentryEnvelopeRequest(page, url); + const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui')); + + expect(uiSpans?.length).toBe(0); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/assets/script.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/assets/script.js new file mode 100644 index 000000000000..10552eeb5bd5 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/assets/script.js @@ -0,0 +1,25 @@ +function getElapsed(startTime) { + const time = Date.now(); + return time - startTime; +} + +function handleClick() { + const startTime = Date.now(); + while (getElapsed(startTime) < 105) { + // + } +} + +function start() { + const startTime = Date.now(); + while (getElapsed(startTime) < 105) { + // + } +} + +// trigger 2 long-animation-frame events +// one from the top-level and the other from an event-listener +start(); + +const button = document.getElementById('clickme'); +button.addEventListener('click', handleClick); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/init.js new file mode 100644 index 000000000000..4be408ceab7e --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/init.js @@ -0,0 +1,15 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [ + Sentry.browserTracingIntegration({ + idleTimeout: 9000, + enableLongTask: false, + enableLongAnimationFrame: true, + }), + ], + tracesSampleRate: 1, +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/template.html b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/template.html new file mode 100644 index 000000000000..ed02d1117097 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/template.html @@ -0,0 +1,13 @@ + + + + + + +
Rendered Before Long Animation Frame
+ + + + diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/test.ts new file mode 100644 index 000000000000..850e75dbed1f --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled/test.ts @@ -0,0 +1,112 @@ +import type { Route } from '@playwright/test'; +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/types'; + +import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/browser'; +import { sentryTest } from '../../../../utils/fixtures'; +import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; + +sentryTest( + 'should capture long animation frame for top-level script.', + async ({ browserName, getLocalTestPath, page }) => { + // Long animation frames only work on chrome + if (shouldSkipTracingTest() || browserName !== 'chromium') { + sentryTest.skip(); + } + + await page.route('**/path/to/script.js', (route: Route) => + route.fulfill({ path: `${__dirname}/assets/script.js` }), + ); + + const url = await getLocalTestPath({ testDir: __dirname }); + + const promise = getFirstSentryEnvelopeRequest(page); + + await page.goto(url); + + await new Promise(resolve => setTimeout(resolve, 1000)); + + const eventData = await promise; + + const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui.long-animation-frame')); + + expect(uiSpans?.length).toEqual(1); + + const [topLevelUISpan] = uiSpans || []; + expect(topLevelUISpan).toEqual( + expect.objectContaining({ + op: 'ui.long-animation-frame', + description: 'Main UI thread blocked', + parent_span_id: eventData.contexts?.trace?.span_id, + data: { + 'code.filepath': 'https://example.com/path/to/script.js', + 'browser.script.source_char_position': 0, + 'browser.script.invoker': 'https://example.com/path/to/script.js', + 'browser.script.invoker_type': 'classic-script', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.long-animation-frame', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.browser.metrics', + }, + }), + ); + const start = topLevelUISpan.start_timestamp ?? 0; + const end = topLevelUISpan.timestamp ?? 0; + const duration = end - start; + + expect(duration).toBeGreaterThanOrEqual(0.1); + expect(duration).toBeLessThanOrEqual(0.15); + }, +); + +sentryTest( + 'should capture long animation frame for event listener.', + async ({ browserName, getLocalTestPath, page }) => { + // Long animation frames only work on chrome + if (shouldSkipTracingTest() || browserName !== 'chromium') { + sentryTest.skip(); + } + + await page.route('**/path/to/script.js', (route: Route) => + route.fulfill({ path: `${__dirname}/assets/script.js` }), + ); + + const url = await getLocalTestPath({ testDir: __dirname }); + + const promise = getFirstSentryEnvelopeRequest(page); + + await page.goto(url); + + // trigger long animation frame function + await page.getByRole('button').click(); + + await new Promise(resolve => setTimeout(resolve, 1000)); + + const eventData = await promise; + + const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui.long-animation-frame')); + + expect(uiSpans?.length).toEqual(2); + + // ignore the first ui span (top-level long animation frame) + const [, eventListenerUISpan] = uiSpans || []; + + expect(eventListenerUISpan).toEqual( + expect.objectContaining({ + op: 'ui.long-animation-frame', + description: 'Main UI thread blocked', + parent_span_id: eventData.contexts?.trace?.span_id, + data: { + 'browser.script.invoker': 'BUTTON#clickme.onclick', + 'browser.script.invoker_type': 'event-listener', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.long-animation-frame', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.browser.metrics', + }, + }), + ); + const start = eventListenerUISpan.start_timestamp ?? 0; + const end = eventListenerUISpan.timestamp ?? 0; + const duration = end - start; + + expect(duration).toBeGreaterThanOrEqual(0.1); + expect(duration).toBeLessThanOrEqual(0.15); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-non-chromium/assets/script.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-non-chromium/assets/script.js new file mode 100644 index 000000000000..9ac3d6fb33d2 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-non-chromium/assets/script.js @@ -0,0 +1,12 @@ +(() => { + const startTime = Date.now(); + + function getElasped() { + const time = Date.now(); + return time - startTime; + } + + while (getElasped() < 101) { + // + } +})(); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-non-chromium/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-non-chromium/init.js new file mode 100644 index 000000000000..ca1bf10dcddd --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-non-chromium/init.js @@ -0,0 +1,11 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [ + Sentry.browserTracingIntegration({ enableLongTask: true, enableLongAnimationFrame: true, idleTimeout: 9000 }), + ], + tracesSampleRate: 1, +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-non-chromium/template.html b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-non-chromium/template.html new file mode 100644 index 000000000000..5c3a14114991 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-non-chromium/template.html @@ -0,0 +1,10 @@ + + + + + + +
Rendered Before Long Task
+ + + diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-non-chromium/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-non-chromium/test.ts new file mode 100644 index 000000000000..65fb6664ac82 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-non-chromium/test.ts @@ -0,0 +1,27 @@ +import type { Route } from '@playwright/test'; +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/types'; + +import { sentryTest } from '../../../../utils/fixtures'; +import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; + +sentryTest( + 'should not capture long animation frame or long task when browser is non-chromium', + async ({ browserName, getLocalTestPath, page }) => { + // Only test non-chromium browsers + if (shouldSkipTracingTest() || browserName === 'chromium') { + sentryTest.skip(); + } + + await page.route('**/path/to/script.js', (route: Route) => + route.fulfill({ path: `${__dirname}/assets/script.js` }), + ); + + const url = await getLocalTestPath({ testDir: __dirname }); + + const eventData = await getFirstSentryEnvelopeRequest(page, url); + const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui')); + + expect(uiSpans?.length).toBe(0); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/assets/script.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/assets/script.js new file mode 100644 index 000000000000..10552eeb5bd5 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/assets/script.js @@ -0,0 +1,25 @@ +function getElapsed(startTime) { + const time = Date.now(); + return time - startTime; +} + +function handleClick() { + const startTime = Date.now(); + while (getElapsed(startTime) < 105) { + // + } +} + +function start() { + const startTime = Date.now(); + while (getElapsed(startTime) < 105) { + // + } +} + +// trigger 2 long-animation-frame events +// one from the top-level and the other from an event-listener +start(); + +const button = document.getElementById('clickme'); +button.addEventListener('click', handleClick); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/init.js new file mode 100644 index 000000000000..d81b8932803c --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/init.js @@ -0,0 +1,15 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [ + Sentry.browserTracingIntegration({ + idleTimeout: 9000, + enableLongTask: true, + enableLongAnimationFrame: true, + }), + ], + tracesSampleRate: 1, +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/template.html b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/template.html new file mode 100644 index 000000000000..ed02d1117097 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/template.html @@ -0,0 +1,13 @@ + + + + + + +
Rendered Before Long Animation Frame
+ + + + diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/test.ts new file mode 100644 index 000000000000..1949e44bd398 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled/test.ts @@ -0,0 +1,114 @@ +import type { Route } from '@playwright/test'; +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/types'; + +import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/browser'; +import { sentryTest } from '../../../../utils/fixtures'; +import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; + +sentryTest( + 'should capture long animation frame for top-level script.', + async ({ browserName, getLocalTestPath, page }) => { + // Long animation frames only work on chrome + if (shouldSkipTracingTest() || browserName !== 'chromium') { + sentryTest.skip(); + } + + // Long animation frame should take priority over long tasks + + await page.route('**/path/to/script.js', (route: Route) => + route.fulfill({ path: `${__dirname}/assets/script.js` }), + ); + + const url = await getLocalTestPath({ testDir: __dirname }); + + const promise = getFirstSentryEnvelopeRequest(page); + + await page.goto(url); + + await new Promise(resolve => setTimeout(resolve, 1000)); + + const eventData = await promise; + + const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui.long-animation-frame')); + + expect(uiSpans?.length).toEqual(1); + + const [topLevelUISpan] = uiSpans || []; + expect(topLevelUISpan).toEqual( + expect.objectContaining({ + op: 'ui.long-animation-frame', + description: 'Main UI thread blocked', + parent_span_id: eventData.contexts?.trace?.span_id, + data: { + 'code.filepath': 'https://example.com/path/to/script.js', + 'browser.script.source_char_position': 0, + 'browser.script.invoker': 'https://example.com/path/to/script.js', + 'browser.script.invoker_type': 'classic-script', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.long-animation-frame', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.browser.metrics', + }, + }), + ); + const start = topLevelUISpan.start_timestamp ?? 0; + const end = topLevelUISpan.timestamp ?? 0; + const duration = end - start; + + expect(duration).toBeGreaterThanOrEqual(0.1); + expect(duration).toBeLessThanOrEqual(0.15); + }, +); + +sentryTest( + 'should capture long animation frame for event listener.', + async ({ browserName, getLocalTestPath, page }) => { + // Long animation frames only work on chrome + if (shouldSkipTracingTest() || browserName !== 'chromium') { + sentryTest.skip(); + } + + await page.route('**/path/to/script.js', (route: Route) => + route.fulfill({ path: `${__dirname}/assets/script.js` }), + ); + + const url = await getLocalTestPath({ testDir: __dirname }); + + const promise = getFirstSentryEnvelopeRequest(page); + + await page.goto(url); + + // trigger long animation frame function + await page.getByRole('button').click(); + + await new Promise(resolve => setTimeout(resolve, 1000)); + + const eventData = await promise; + + const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui.long-animation-frame')); + + expect(uiSpans?.length).toEqual(2); + + // ignore the first ui span (top-level long animation frame) + const [, eventListenerUISpan] = uiSpans || []; + + expect(eventListenerUISpan).toEqual( + expect.objectContaining({ + op: 'ui.long-animation-frame', + description: 'Main UI thread blocked', + parent_span_id: eventData.contexts?.trace?.span_id, + data: { + 'browser.script.invoker': 'BUTTON#clickme.onclick', + 'browser.script.invoker_type': 'event-listener', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'ui.long-animation-frame', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.browser.metrics', + }, + }), + ); + const start = eventListenerUISpan.start_timestamp ?? 0; + const end = eventListenerUISpan.timestamp ?? 0; + const duration = end - start; + + expect(duration).toBeGreaterThanOrEqual(0.1); + expect(duration).toBeLessThanOrEqual(0.15); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-no-animation-frame/assets/script.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-no-animation-frame/assets/script.js new file mode 100644 index 000000000000..5a2aef02028d --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-no-animation-frame/assets/script.js @@ -0,0 +1,12 @@ +(() => { + const startTime = Date.now(); + + function getElasped() { + const time = Date.now(); + return time - startTime; + } + + while (getElasped() < 105) { + // + } +})(); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-no-animation-frame/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-no-animation-frame/init.js new file mode 100644 index 000000000000..0e35db50764f --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-no-animation-frame/init.js @@ -0,0 +1,15 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [ + Sentry.browserTracingIntegration({ + idleTimeout: 9000, + enableLongTask: true, + enableLongAnimationFrame: false, + }), + ], + tracesSampleRate: 1, +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-no-animation-frame/template.html b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-no-animation-frame/template.html new file mode 100644 index 000000000000..5c3a14114991 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-no-animation-frame/template.html @@ -0,0 +1,10 @@ + + + + + + +
Rendered Before Long Task
+ + + diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-no-animation-frame/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-no-animation-frame/test.ts new file mode 100644 index 000000000000..6189758c0340 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-no-animation-frame/test.ts @@ -0,0 +1,37 @@ +import type { Route } from '@playwright/test'; +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/types'; + +import { sentryTest } from '../../../../utils/fixtures'; +import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; + +sentryTest('should capture long task.', async ({ browserName, getLocalTestPath, page }) => { + // Long tasks only work on chrome + if (shouldSkipTracingTest() || browserName !== 'chromium') { + sentryTest.skip(); + } + + await page.route('**/path/to/script.js', (route: Route) => route.fulfill({ path: `${__dirname}/assets/script.js` })); + + const url = await getLocalTestPath({ testDir: __dirname }); + + const eventData = await getFirstSentryEnvelopeRequest(page, url); + const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui')); + + expect(uiSpans?.length).toBeGreaterThan(0); + + const [firstUISpan] = uiSpans || []; + expect(firstUISpan).toEqual( + expect.objectContaining({ + op: 'ui.long-task', + description: 'Main UI thread blocked', + parent_span_id: eventData.contexts?.trace?.span_id, + }), + ); + const start = firstUISpan.start_timestamp ?? 0; + const end = firstUISpan.timestamp ?? 0; + const duration = end - start; + + expect(duration).toBeGreaterThanOrEqual(0.1); + expect(duration).toBeLessThanOrEqual(0.15); +}); 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 1ec7ec50998a..1b6bc5bc686d 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 @@ -77,6 +77,7 @@ sentryTest('should capture an INP click event span after pageload', async ({ bro 'sentry.sample_rate': 1, 'sentry.source': 'custom', transaction: 'test-url', + 'user_agent.original': expect.stringContaining('Chrome'), }, measurements: { inp: { 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 1354c373253e..a9d5191b5cf3 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 @@ -80,6 +80,7 @@ sentryTest( 'sentry.sample_rate': 1, 'sentry.source': 'custom', transaction: 'test-route', + 'user_agent.original': expect.stringContaining('Chrome'), }, measurements: { inp: { 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 248cb7d1e510..87ba1fd8632c 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 @@ -76,6 +76,7 @@ sentryTest( 'sentry.op': 'ui.interaction.click', 'sentry.origin': 'auto.http.browser.inp', transaction: 'test-route', + 'user_agent.original': expect.stringContaining('Chrome'), }, measurements: { inp: { 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 3f9684cf7f2a..594bd9904052 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 @@ -77,6 +77,7 @@ sentryTest( 'sentry.op': 'ui.interaction.click', 'sentry.origin': 'auto.http.browser.inp', transaction: 'test-url', + 'user_agent.original': expect.stringContaining('Chrome'), }, measurements: { inp: { 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 b62923be0e9b..9d158ea5491e 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,6 +74,14 @@ 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/e2e-tests/test-applications/aws-lambda-layer-cjs/playwright.config.ts b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/playwright.config.ts index 7b14daadc6d1..174593c307df 100644 --- a/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/playwright.config.ts +++ b/dev-packages/e2e-tests/test-applications/aws-lambda-layer-cjs/playwright.config.ts @@ -1,79 +1,3 @@ -import type { PlaywrightTestConfig } from '@playwright/test'; -import { devices } from '@playwright/test'; +import { getPlaywrightConfig } from '@sentry-internal/test-utils'; -// Fix urls not resolving to localhost on Node v17+ -// See: https://github.com/axios/axios/issues/3821#issuecomment-1413727575 -import { setDefaultResultOrder } from 'dns'; -setDefaultResultOrder('ipv4first'); - -const eventProxyPort = 3031; -const lambdaPort = 3030; - -/** - * See https://playwright.dev/docs/test-configuration. - */ -const config: PlaywrightTestConfig = { - testDir: './tests', - /* Maximum time one test can run for. */ - timeout: 150_000, - expect: { - /** - * Maximum time expect() should wait for the condition to be met. - * For example in `await expect(locator).toHaveText();` - */ - timeout: 5000, - }, - /* Run tests in files in parallel */ - fullyParallel: true, - /* Fail the build on CI if you accidentally left test.only in the source code. */ - forbidOnly: !!process.env.CI, - /* Retry on CI only */ - retries: 0, - /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: '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). */ - actionTimeout: 0, - - /* Base URL to use in actions like `await page.goto('/')`. */ - baseURL: `http://localhost:${lambdaPort}`, - - /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - trace: 'on-first-retry', - }, - - /* Configure projects for major browsers */ - projects: [ - { - name: 'chromium', - use: { - ...devices['Desktop Chrome'], - }, - }, - // For now we only test Chrome! - // { - // name: 'firefox', - // use: { - // ...devices['Desktop Firefox'], - // }, - // }, - // { - // name: 'webkit', - // use: { - // ...devices['Desktop Safari'], - // }, - // }, - ], - - /* Run your local dev server before starting the tests */ - webServer: [ - { - command: `node start-event-proxy.mjs && pnpm wait-port ${eventProxyPort}`, - port: eventProxyPort, - stdout: 'pipe', - }, - ], -}; - -export default config; +export default getPlaywrightConfig(); diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless-esm/playwright.config.ts b/dev-packages/e2e-tests/test-applications/aws-serverless-esm/playwright.config.ts index 9b4853af2033..174593c307df 100644 --- a/dev-packages/e2e-tests/test-applications/aws-serverless-esm/playwright.config.ts +++ b/dev-packages/e2e-tests/test-applications/aws-serverless-esm/playwright.config.ts @@ -1,27 +1,3 @@ import { getPlaywrightConfig } from '@sentry-internal/test-utils'; -// Fix urls not resolving to localhost on Node v17+ -// See: https://github.com/axios/axios/issues/3821#issuecomment-1413727575 -import { setDefaultResultOrder } from 'dns'; -setDefaultResultOrder('ipv4first'); - -const eventProxyPort = 3031; - -/** - * See https://playwright.dev/docs/test-configuration. - */ -const config = getPlaywrightConfig( - { startCommand: '' }, - { - /* Run your local dev server before starting the tests */ - webServer: [ - { - command: `node start-event-proxy.mjs && pnpm wait-port ${eventProxyPort}`, - port: eventProxyPort, - stdout: 'pipe', - }, - ], - }, -); - -export default config; +export default getPlaywrightConfig(); diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless-esm/tests/basic.test.ts b/dev-packages/e2e-tests/test-applications/aws-serverless-esm/tests/basic.test.ts index 8e507a469235..83fab96ee117 100644 --- a/dev-packages/e2e-tests/test-applications/aws-serverless-esm/tests/basic.test.ts +++ b/dev-packages/e2e-tests/test-applications/aws-serverless-esm/tests/basic.test.ts @@ -31,7 +31,6 @@ test('AWS Serverless SDK sends events in ESM mode', async ({ request }) => { 'sentry.source': 'component', 'sentry.origin': 'auto.function.serverless', 'sentry.op': 'function.aws.lambda', - 'otel.kind': 'INTERNAL', }, op: 'function.aws.lambda', origin: 'auto.function.serverless', diff --git a/dev-packages/e2e-tests/test-applications/create-next-app/tests/server-transactions.test.ts b/dev-packages/e2e-tests/test-applications/create-next-app/tests/server-transactions.test.ts index 1d789cb4950c..56be8b65d60b 100644 --- a/dev-packages/e2e-tests/test-applications/create-next-app/tests/server-transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/create-next-app/tests/server-transactions.test.ts @@ -40,7 +40,6 @@ test('Sends server-side transactions to Sentry', async ({ baseURL }) => { spans: [ { data: { - 'otel.kind': 'INTERNAL', 'sentry.origin': 'manual', }, description: 'test-span', diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app/app/entry.client.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app/app/entry.client.tsx index d0c95287e0c9..6a1a4b4c427e 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app/app/entry.client.tsx +++ b/dev-packages/e2e-tests/test-applications/create-remix-app/app/entry.client.tsx @@ -19,6 +19,7 @@ Sentry.init({ replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production. replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur. tunnel: 'http://localhost:3031/', // proxy server + release: 'e2e-test', }); startTransition(() => { diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app/app/routes/user.$id.tsx b/dev-packages/e2e-tests/test-applications/create-remix-app/app/routes/user.$id.tsx index 13b2e0a34d1e..ecbeb440219e 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app/app/routes/user.$id.tsx +++ b/dev-packages/e2e-tests/test-applications/create-remix-app/app/routes/user.$id.tsx @@ -1,3 +1,16 @@ export default function User() { - return
I am a blank page
; + return ( +
+
I am a blank page
+ +
+ ); } diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app/tests/client-inp.test.ts b/dev-packages/e2e-tests/test-applications/create-remix-app/tests/client-inp.test.ts new file mode 100644 index 000000000000..8fe1993db3f8 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/create-remix-app/tests/client-inp.test.ts @@ -0,0 +1,198 @@ +import { expect, test } from '@playwright/test'; +import { waitForEnvelopeItem, waitForTransaction } from '@sentry-internal/test-utils'; + +test('sends an INP span during pageload', async ({ page }) => { + const inpSpanPromise = waitForEnvelopeItem('create-remix-app', item => { + return item[0].type === 'span'; + }); + + await page.goto(`/`); + + await page.click('#exception-button'); + + await page.waitForTimeout(500); + + // Page hide to trigger INP + await page.evaluate(() => { + window.dispatchEvent(new Event('pagehide')); + }); + + const inpSpan = await inpSpanPromise; + + expect(inpSpan[1]).toEqual({ + data: { + 'sentry.origin': 'auto.http.browser.inp', + 'sentry.op': 'ui.interaction.click', + release: 'e2e-test', + environment: 'qa', + transaction: 'routes/_index', + 'sentry.exclusive_time': expect.any(Number), + 'sentry.sample_rate': 1, + 'sentry.source': 'custom', + replay_id: expect.any(String), + 'user_agent.original': expect.stringContaining('Chrome'), + }, + description: 'body > div > input#exception-button[type="button"]', + op: 'ui.interaction.click', + parent_span_id: expect.any(String), + is_segment: true, + span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: expect.any(String), + origin: 'auto.http.browser.inp', + exclusive_time: expect.any(Number), + measurements: { inp: { unit: 'millisecond', value: expect.any(Number) } }, + segment_id: expect.any(String), + }); +}); + +test('sends an INP span after pageload', async ({ page }) => { + const transactionPromise = waitForTransaction('create-remix-app', transactionEvent => { + return transactionEvent.contexts?.trace?.op === 'pageload' && transactionEvent.transaction === 'routes/_index'; + }); + + await page.goto(`/`); + + await transactionPromise; + + const inpSpanPromise1 = waitForEnvelopeItem('create-remix-app', item => { + return item[0].type === 'span'; + }); + + await page.click('#exception-button'); + + await page.waitForTimeout(500); + + // Page hide to trigger INP + await page.evaluate(() => { + window.dispatchEvent(new Event('pagehide')); + }); + + const inpSpan1 = await inpSpanPromise1; + + expect(inpSpan1[1]).toEqual({ + data: { + 'sentry.origin': 'auto.http.browser.inp', + 'sentry.op': 'ui.interaction.click', + release: 'e2e-test', + environment: 'qa', + transaction: 'routes/_index', + 'sentry.exclusive_time': expect.any(Number), + 'sentry.sample_rate': 1, + 'sentry.source': 'custom', + replay_id: expect.any(String), + 'user_agent.original': expect.stringContaining('Chrome'), + }, + description: 'body > div > input#exception-button[type="button"]', + op: 'ui.interaction.click', + parent_span_id: expect.any(String), + is_segment: true, + span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: expect.any(String), + origin: 'auto.http.browser.inp', + exclusive_time: expect.any(Number), + measurements: { inp: { unit: 'millisecond', value: expect.any(Number) } }, + segment_id: expect.any(String), + }); +}); + +test('sends an INP span during navigation', async ({ page }) => { + page.on('console', msg => console.log(msg.text())); + const inpSpanPromise = waitForEnvelopeItem('create-remix-app', item => { + return item[0].type === 'span'; + }); + + await page.goto(`/`); + + await page.click('#navigation'); + + await page.waitForTimeout(500); + + // Page hide to trigger INP + await page.evaluate(() => { + window.dispatchEvent(new Event('pagehide')); + }); + + const inpSpan = await inpSpanPromise; + + expect(inpSpan[1]).toEqual({ + data: { + 'sentry.origin': 'auto.http.browser.inp', + 'sentry.op': 'ui.interaction.click', + release: 'e2e-test', + environment: 'qa', + transaction: 'routes/user.$id', + 'sentry.exclusive_time': expect.any(Number), + replay_id: expect.any(String), + 'user_agent.original': expect.stringContaining('Chrome'), + }, + description: '', + op: 'ui.interaction.click', + parent_span_id: expect.any(String), + span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: expect.any(String), + origin: 'auto.http.browser.inp', + exclusive_time: expect.any(Number), + measurements: { inp: { unit: 'millisecond', value: expect.any(Number) } }, + segment_id: expect.any(String), + }); +}); + +test('sends an INP span after navigation', async ({ page }) => { + page.on('console', msg => console.log(msg.text())); + const transactionPromise = waitForTransaction('create-remix-app', transactionEvent => { + return transactionEvent.contexts?.trace?.op === 'navigation' && transactionEvent.transaction === 'routes/user.$id'; + }); + + await page.goto(`/`); + + await page.click('#navigation'); + + await transactionPromise; + + const inpSpanPromise = waitForEnvelopeItem('create-remix-app', item => { + return item[0].type === 'span'; + }); + + await page.click('#button'); + + await page.waitForTimeout(500); + + // Page hide to trigger INP + await page.evaluate(() => { + window.dispatchEvent(new Event('pagehide')); + }); + + const inpSpan = await inpSpanPromise; + + expect(inpSpan[1]).toEqual({ + data: { + 'sentry.origin': 'auto.http.browser.inp', + 'sentry.op': 'ui.interaction.click', + release: 'e2e-test', + environment: 'qa', + transaction: 'routes/user.$id', + 'sentry.exclusive_time': expect.any(Number), + replay_id: expect.any(String), + 'sentry.sample_rate': 1, + 'sentry.source': 'custom', + 'user_agent.original': expect.stringContaining('Chrome'), + }, + description: '', + op: 'ui.interaction.click', + is_segment: true, + span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: expect.any(String), + origin: 'auto.http.browser.inp', + exclusive_time: expect.any(Number), + measurements: { inp: { unit: 'millisecond', value: expect.any(Number) } }, + segment_id: expect.any(String), + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts index b4f9d5588dda..932d1af99611 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts @@ -2,9 +2,10 @@ import { Module } from '@nestjs/common'; import { ScheduleModule } from '@nestjs/schedule'; import { AppController1, AppController2 } from './app.controller'; import { AppService1, AppService2 } from './app.service'; +import { TestModule } from './test-module/test.module'; @Module({ - imports: [ScheduleModule.forRoot()], + imports: [ScheduleModule.forRoot(), TestModule], controllers: [AppController1], providers: [AppService1], }) diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/app.service.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/app.service.ts index b6fd70769e1f..f5666bffeb46 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/app.service.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs/src/app.service.ts @@ -99,12 +99,14 @@ export class AppService1 { } @SentryTraced('return a string') - getString(): string { - return 'test'; + getString(): { result: string } { + return { result: 'test' }; } async testSpanDecoratorSync() { - return this.getString(); + const returned = this.getString(); + // Will fail if getString() is async, because returned will be a Promise<> + return returned.result; } /* diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.controller.ts new file mode 100644 index 000000000000..150fb0e07546 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.controller.ts @@ -0,0 +1,12 @@ +import { Controller, Get } from '@nestjs/common'; +import { TestException } from './test.exception'; + +@Controller('test-module') +export class TestController { + constructor() {} + + @Get() + getTest(): string { + throw new TestException(); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.exception.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.exception.ts new file mode 100644 index 000000000000..b736596b6717 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.exception.ts @@ -0,0 +1,5 @@ +export class TestException extends Error { + constructor() { + super('Something went wrong in the test module!'); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.filter.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.filter.ts new file mode 100644 index 000000000000..87a4ca0920e5 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.filter.ts @@ -0,0 +1,13 @@ +import { ArgumentsHost, BadRequestException, Catch } from '@nestjs/common'; +import { BaseExceptionFilter } from '@nestjs/core'; +import { TestException } from './test.exception'; + +@Catch(TestException) +export class TestExceptionFilter extends BaseExceptionFilter { + catch(exception: unknown, host: ArgumentsHost) { + if (exception instanceof TestException) { + return super.catch(new BadRequestException(exception.message), host); + } + return super.catch(exception, host); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.module.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.module.ts new file mode 100644 index 000000000000..37b6dbe7e819 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; +import { APP_FILTER } from '@nestjs/core'; +import { TestController } from './test.controller'; +import { TestExceptionFilter } from './test.filter'; + +@Module({ + imports: [], + controllers: [TestController], + providers: [ + { + provide: APP_FILTER, + useClass: TestExceptionFilter, + }, + ], +}) +export class TestModule {} diff --git a/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts index 349b25b0eee9..ffb48f4e5e70 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts @@ -53,3 +53,32 @@ test('Does not send expected exception to Sentry', async ({ baseURL }) => { expect(errorEventOccurred).toBe(false); }); + +test('Does not handle expected exception if exception is thrown in module', async ({ baseURL }) => { + const errorEventPromise = waitForError('nestjs', event => { + return !event.type && event.exception?.values?.[0]?.value === 'Something went wrong in the test module!'; + }); + + const response = await fetch(`${baseURL}/test-module`); + expect(response.status).toBe(500); // should be 400 + + // should never arrive, but does because the exception is not handled properly + const errorEvent = await errorEventPromise; + + expect(errorEvent.exception?.values).toHaveLength(1); + expect(errorEvent.exception?.values?.[0]?.value).toBe('Something went wrong in the test module!'); + + expect(errorEvent.request).toEqual({ + method: 'GET', + cookies: {}, + headers: expect.any(Object), + url: 'http://localhost:3030/test-module', + }); + + expect(errorEvent.transaction).toEqual('GET /test-module'); + + expect(errorEvent.contexts?.trace).toEqual({ + trace_id: expect.any(String), + span_id: expect.any(String), + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/nestjs/tests/span-decorator.test.ts b/dev-packages/e2e-tests/test-applications/nestjs/tests/span-decorator.test.ts index 3efdfb979d73..28c925727d89 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/tests/span-decorator.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs/tests/span-decorator.test.ts @@ -24,7 +24,6 @@ test('Transaction includes span and correct value for decorated async function', data: { 'sentry.origin': 'manual', 'sentry.op': 'wait and return a string', - 'otel.kind': 'INTERNAL', }, description: 'wait', parent_span_id: expect.any(String), @@ -60,7 +59,6 @@ test('Transaction includes span and correct value for decorated sync function', data: { 'sentry.origin': 'manual', 'sentry.op': 'return a string', - 'otel.kind': 'INTERNAL', }, description: 'getString', parent_span_id: expect.any(String), diff --git a/dev-packages/e2e-tests/test-applications/nestjs/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs/tests/transactions.test.ts index 22cb0f8e6a8c..ae5d8b63b16d 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs/tests/transactions.test.ts @@ -54,7 +54,6 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'express.name': '/test-transaction', 'express.type': 'request_handler', 'http.route': '/test-transaction', - 'otel.kind': 'INTERNAL', 'sentry.origin': 'auto.http.otel.express', 'sentry.op': 'request_handler.express', }, @@ -70,7 +69,6 @@ test('Sends an API route transaction', async ({ baseURL }) => { }, { data: { - 'otel.kind': 'INTERNAL', 'sentry.origin': 'manual', }, description: 'test-span', @@ -84,7 +82,6 @@ test('Sends an API route transaction', async ({ baseURL }) => { }, { data: { - 'otel.kind': 'INTERNAL', 'sentry.origin': 'manual', }, description: 'child-span', @@ -106,7 +103,6 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'nestjs.version': expect.any(String), 'nestjs.type': 'handler', 'nestjs.callback': 'testTransaction', - 'otel.kind': 'INTERNAL', }, description: 'testTransaction', parent_span_id: expect.any(String), diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/app/nested-rsc-error/[param]/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-15/app/nested-rsc-error/[param]/page.tsx new file mode 100644 index 000000000000..675b248026be --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/app/nested-rsc-error/[param]/page.tsx @@ -0,0 +1,17 @@ +import { Suspense } from 'react'; + +export const dynamic = 'force-dynamic'; + +export default async function Page() { + return ( + Loading...

}> + {/* @ts-ignore */} + ; +
+ ); +} + +async function Crash() { + throw new Error('I am technically uncatchable'); + return

unreachable

; +} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/app/streaming-rsc-error/[param]/client-page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-15/app/streaming-rsc-error/[param]/client-page.tsx new file mode 100644 index 000000000000..7b66c3fbdeef --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/app/streaming-rsc-error/[param]/client-page.tsx @@ -0,0 +1,8 @@ +'use client'; + +import { use } from 'react'; + +export function RenderPromise({ stringPromise }: { stringPromise: Promise }) { + const s = use(stringPromise); + return <>{s}; +} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/app/streaming-rsc-error/[param]/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-15/app/streaming-rsc-error/[param]/page.tsx new file mode 100644 index 000000000000..9531f9a42139 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/app/streaming-rsc-error/[param]/page.tsx @@ -0,0 +1,18 @@ +import { Suspense } from 'react'; +import { RenderPromise } from './client-page'; + +export const dynamic = 'force-dynamic'; + +export default async function Page() { + const crashingPromise = new Promise((_, reject) => { + setTimeout(() => { + reject(new Error('I am a data streaming error')); + }, 100); + }); + + return ( + Loading...

}> + ; +
+ ); +} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/instrumentation.ts b/dev-packages/e2e-tests/test-applications/nextjs-15/instrumentation.ts index 7b89a972e157..ca4a213e58ba 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-15/instrumentation.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/instrumentation.ts @@ -1,3 +1,5 @@ +import * as Sentry from '@sentry/nextjs'; + export async function register() { if (process.env.NEXT_RUNTIME === 'nodejs') { await import('./sentry.server.config'); @@ -7,3 +9,5 @@ export async function register() { await import('./sentry.edge.config'); } } + +export const onRequestError = Sentry.experimental_captureRequestError; diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/package.json b/dev-packages/e2e-tests/test-applications/nextjs-15/package.json index ebd18c6fb10e..4c3f56b0aa0c 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-15/package.json +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/package.json @@ -5,8 +5,8 @@ "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:prod": "TEST_ENV=production __NEXT_EXPERIMENTAL_INSTRUMENTATION=1 playwright test", + "test:dev": "TEST_ENV=development __NEXT_EXPERIMENTAL_INSTRUMENTATION=1 playwright test", "test:build": "pnpm install && npx playwright install && pnpm build", "test:build-canary": "pnpm install && pnpm add next@rc && pnpm add react@beta && pnpm add react-dom@beta && npx playwright install && pnpm build", "test:build-latest": "pnpm install && pnpm add next@rc && pnpm add react@beta && pnpm add react-dom@beta && npx playwright install && pnpm build", @@ -17,7 +17,7 @@ "@types/node": "18.11.17", "@types/react": "18.0.26", "@types/react-dom": "18.0.9", - "next": "14.3.0-canary.73", + "next": "15.0.0-canary.63", "react": "beta", "react-dom": "beta", "typescript": "4.9.5" diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/tests/nested-rsc-error.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-15/tests/nested-rsc-error.test.ts new file mode 100644 index 000000000000..223da5b245e9 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/tests/nested-rsc-error.test.ts @@ -0,0 +1,33 @@ +import { expect, test } from '@playwright/test'; +import { waitForError, waitForTransaction } from '@sentry-internal/test-utils'; + +test('Should capture errors from nested server components when `Sentry.captureRequestError` is added to the `onRequestError` hook', async ({ + page, +}) => { + const errorEventPromise = waitForError('nextjs-15', errorEvent => { + return !!errorEvent?.exception?.values?.some(value => value.value === 'I am technically uncatchable'); + }); + + const serverTransactionPromise = waitForTransaction('nextjs-15', async transactionEvent => { + return transactionEvent?.transaction === 'GET /nested-rsc-error/[param]'; + }); + + await page.goto(`/nested-rsc-error/123`); + const errorEvent = await errorEventPromise; + const serverTransactionEvent = await serverTransactionPromise; + + // error event is part of the transaction + expect(errorEvent.contexts?.trace?.trace_id).toBe(serverTransactionEvent.contexts?.trace?.trace_id); + + expect(errorEvent.request).toMatchObject({ + headers: expect.any(Object), + method: 'GET', + }); + + expect(errorEvent.contexts?.nextjs).toEqual({ + route_type: 'render', + router_kind: 'App Router', + router_path: '/nested-rsc-error/[param]', + request_path: '/nested-rsc-error/123', + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/tests/streaming-rsc-error.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-15/tests/streaming-rsc-error.test.ts new file mode 100644 index 000000000000..b50e9688861e --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/tests/streaming-rsc-error.test.ts @@ -0,0 +1,33 @@ +import { expect, test } from '@playwright/test'; +import { waitForError, waitForTransaction } from '@sentry-internal/test-utils'; + +test('Should capture errors for crashing streaming promises in server components when `Sentry.captureRequestError` is added to the `onRequestError` hook', async ({ + page, +}) => { + const errorEventPromise = waitForError('nextjs-15', errorEvent => { + return !!errorEvent?.exception?.values?.some(value => value.value === 'I am a data streaming error'); + }); + + const serverTransactionPromise = waitForTransaction('nextjs-15', async transactionEvent => { + return transactionEvent?.transaction === 'GET /streaming-rsc-error/[param]'; + }); + + await page.goto(`/streaming-rsc-error/123`); + const errorEvent = await errorEventPromise; + const serverTransactionEvent = await serverTransactionPromise; + + // error event is part of the transaction + expect(errorEvent.contexts?.trace?.trace_id).toBe(serverTransactionEvent.contexts?.trace?.trace_id); + + expect(errorEvent.request).toMatchObject({ + headers: expect.any(Object), + method: 'GET', + }); + + expect(errorEvent.contexts?.nextjs).toEqual({ + route_type: 'render', + router_kind: 'App Router', + router_path: '/streaming-rsc-error/[param]', + request_path: '/streaming-rsc-error/123', + }); +}); 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 8645d36c4c8a..9143bd0b2f90 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 @@ -39,7 +39,10 @@ test('Creates a navigation transaction for app router routes', async ({ page }) const serverComponentTransactionPromise = waitForTransaction('nextjs-app-dir', async transactionEvent => { return ( - transactionEvent?.transaction === 'GET /server-component/parameter/foo/bar/baz' && + // 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 ); 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 ba232ad558b0..49afe791328f 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 @@ -3,7 +3,10 @@ import { waitForError, waitForTransaction } from '@sentry-internal/test-utils'; test('Sends a transaction for a request to app router', async ({ page }) => { const serverComponentTransactionPromise = waitForTransaction('nextjs-app-dir', transactionEvent => { - return transactionEvent?.transaction === 'GET /server-component/parameter/[...parameters]'; + return ( + transactionEvent?.transaction === 'GET /server-component/parameter/[...parameters]' && + transactionEvent.contexts?.trace?.data?.['http.target'].startsWith('/server-component/parameter/1337/42') + ); }); await page.goto('/server-component/parameter/1337/42'); diff --git a/dev-packages/e2e-tests/test-applications/node-connect/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-connect/tests/transactions.test.ts index 031fa71ec581..f0da250d8be9 100644 --- a/dev-packages/e2e-tests/test-applications/node-connect/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-connect/tests/transactions.test.ts @@ -53,7 +53,6 @@ test('Sends an API route transaction', async ({ baseURL }) => { { data: { 'sentry.origin': 'manual', - 'otel.kind': 'INTERNAL', }, description: 'test-span', parent_span_id: expect.any(String), @@ -71,7 +70,6 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'http.route': '/test-transaction', 'connect.type': 'request_handler', 'connect.name': '/test-transaction', - 'otel.kind': 'INTERNAL', }, op: 'request_handler.connect', description: '/test-transaction', diff --git a/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/tests/server.test.ts b/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/tests/server.test.ts index 5b8fd26f1647..5316cc377358 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/tests/server.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express-cjs-preload/tests/server.test.ts @@ -66,7 +66,6 @@ test('Should record a transaction for route with parameters', async ({ request } 'express.name': 'query', 'express.type': 'middleware', 'http.route': '/', - 'otel.kind': 'INTERNAL', 'sentry.origin': 'auto.http.otel.express', 'sentry.op': 'middleware.express', }, @@ -86,7 +85,6 @@ test('Should record a transaction for route with parameters', async ({ request } 'express.name': 'expressInit', 'express.type': 'middleware', 'http.route': '/', - 'otel.kind': 'INTERNAL', 'sentry.origin': 'auto.http.otel.express', 'sentry.op': 'middleware.express', }, @@ -106,7 +104,6 @@ test('Should record a transaction for route with parameters', async ({ request } 'express.name': '/test-transaction/:param', 'express.type': 'request_handler', 'http.route': '/test-transaction/:param', - 'otel.kind': 'INTERNAL', 'sentry.origin': 'auto.http.otel.express', 'sentry.op': 'request_handler.express', }, diff --git a/dev-packages/e2e-tests/test-applications/node-express-esm-loader/tests/server.test.ts b/dev-packages/e2e-tests/test-applications/node-express-esm-loader/tests/server.test.ts index 3b65819e2c29..06e2c2e09434 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-esm-loader/tests/server.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express-esm-loader/tests/server.test.ts @@ -66,7 +66,6 @@ test('Should record a transaction for route with parameters', async ({ request } 'express.name': 'query', 'express.type': 'middleware', 'http.route': '/', - 'otel.kind': 'INTERNAL', 'sentry.origin': 'auto.http.otel.express', 'sentry.op': 'middleware.express', }, @@ -86,7 +85,6 @@ test('Should record a transaction for route with parameters', async ({ request } 'express.name': 'expressInit', 'express.type': 'middleware', 'http.route': '/', - 'otel.kind': 'INTERNAL', 'sentry.origin': 'auto.http.otel.express', 'sentry.op': 'middleware.express', }, @@ -106,7 +104,6 @@ test('Should record a transaction for route with parameters', async ({ request } 'express.name': '/test-transaction/:param', 'express.type': 'request_handler', 'http.route': '/test-transaction/:param', - 'otel.kind': 'INTERNAL', 'sentry.origin': 'auto.http.otel.express', 'sentry.op': 'request_handler.express', }, diff --git a/dev-packages/e2e-tests/test-applications/node-express-esm-preload/tests/server.test.ts b/dev-packages/e2e-tests/test-applications/node-express-esm-preload/tests/server.test.ts index e7b9a3faa878..9d6b10a3dd74 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-esm-preload/tests/server.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express-esm-preload/tests/server.test.ts @@ -66,7 +66,6 @@ test('Should record a transaction for route with parameters', async ({ request } 'express.name': 'query', 'express.type': 'middleware', 'http.route': '/', - 'otel.kind': 'INTERNAL', 'sentry.origin': 'auto.http.otel.express', 'sentry.op': 'middleware.express', }, @@ -86,7 +85,6 @@ test('Should record a transaction for route with parameters', async ({ request } 'express.name': 'expressInit', 'express.type': 'middleware', 'http.route': '/', - 'otel.kind': 'INTERNAL', 'sentry.origin': 'auto.http.otel.express', 'sentry.op': 'middleware.express', }, @@ -106,7 +104,6 @@ test('Should record a transaction for route with parameters', async ({ request } 'express.name': '/test-transaction/:param', 'express.type': 'request_handler', 'http.route': '/test-transaction/:param', - 'otel.kind': 'INTERNAL', 'sentry.origin': 'auto.http.otel.express', 'sentry.op': 'request_handler.express', }, diff --git a/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts index 03262edcf6b5..fede408d258e 100644 --- a/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express/tests/transactions.test.ts @@ -65,7 +65,6 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'http.route': '/', 'express.name': 'query', 'express.type': 'middleware', - 'otel.kind': 'INTERNAL', }, description: 'query', op: 'middleware.express', @@ -85,7 +84,6 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'http.route': '/', 'express.name': 'expressInit', 'express.type': 'middleware', - 'otel.kind': 'INTERNAL', }, description: 'expressInit', op: 'middleware.express', @@ -105,7 +103,6 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'http.route': '/test-transaction', 'express.name': '/test-transaction', 'express.type': 'request_handler', - 'otel.kind': 'INTERNAL', }, description: '/test-transaction', op: 'request_handler.express', @@ -146,7 +143,6 @@ test('Sends an API route transaction for an errored route', async ({ baseURL }) 'http.route': '/', 'express.name': 'query', 'express.type': 'middleware', - 'otel.kind': 'INTERNAL', }, description: 'query', op: 'middleware.express', @@ -166,7 +162,6 @@ test('Sends an API route transaction for an errored route', async ({ baseURL }) 'http.route': '/', 'express.name': 'expressInit', 'express.type': 'middleware', - 'otel.kind': 'INTERNAL', }, description: 'expressInit', op: 'middleware.express', @@ -186,7 +181,6 @@ test('Sends an API route transaction for an errored route', async ({ baseURL }) 'http.route': '/test-exception/:id', 'express.name': '/test-exception/:id', 'express.type': 'request_handler', - 'otel.kind': 'INTERNAL', }, description: '/test-exception/:id', op: 'request_handler.express', diff --git a/dev-packages/e2e-tests/test-applications/node-fastify/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-fastify/tests/transactions.test.ts index fa0ace0e1e5d..c9cb53afb94c 100644 --- a/dev-packages/e2e-tests/test-applications/node-fastify/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-fastify/tests/transactions.test.ts @@ -63,7 +63,6 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'plugin.name': 'fastify -> sentry-fastify-error-handler', 'fastify.type': 'middleware', 'hook.name': 'onRequest', - 'otel.kind': 'INTERNAL', 'sentry.origin': 'auto.http.otel.fastify', 'sentry.op': 'middleware.fastify', }, @@ -83,7 +82,6 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'plugin.name': 'fastify -> sentry-fastify-error-handler', 'fastify.type': 'request_handler', 'http.route': '/test-transaction', - 'otel.kind': 'INTERNAL', 'sentry.op': 'request_handler.fastify', 'sentry.origin': 'auto.http.otel.fastify', }, @@ -100,7 +98,6 @@ test('Sends an API route transaction', async ({ baseURL }) => { expect(spans).toContainEqual({ data: { - 'otel.kind': 'INTERNAL', 'sentry.origin': 'manual', }, description: 'test-span', @@ -115,7 +112,6 @@ test('Sends an API route transaction', async ({ baseURL }) => { expect(spans).toContainEqual({ data: { - 'otel.kind': 'INTERNAL', 'sentry.origin': 'manual', }, description: 'child-span', diff --git a/dev-packages/e2e-tests/test-applications/node-hapi/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-hapi/tests/transactions.test.ts index 4fd0c72ffd54..c3237de74a5a 100644 --- a/dev-packages/e2e-tests/test-applications/node-hapi/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-hapi/tests/transactions.test.ts @@ -63,7 +63,6 @@ test('Sends successful transaction', async ({ baseURL }) => { 'hapi.type': 'router', 'http.method': 'GET', 'http.route': '/test-success', - 'otel.kind': 'INTERNAL', 'sentry.op': 'router.hapi', 'sentry.origin': 'auto.http.otel.hapi', }, @@ -81,7 +80,6 @@ test('Sends successful transaction', async ({ baseURL }) => { // this comes from "onPreResponse" data: { 'hapi.type': 'server.ext', - 'otel.kind': 'INTERNAL', 'sentry.op': 'server.ext.hapi', 'sentry.origin': 'auto.http.otel.hapi', 'server.ext.type': 'onPreResponse', diff --git a/dev-packages/e2e-tests/test-applications/node-koa/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-koa/tests/transactions.test.ts index fd57b23cb8bb..4c52c932e7b4 100644 --- a/dev-packages/e2e-tests/test-applications/node-koa/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-koa/tests/transactions.test.ts @@ -53,7 +53,6 @@ test('Sends an API route transaction', async ({ baseURL }) => { data: { 'koa.name': '', 'koa.type': 'middleware', - 'otel.kind': 'INTERNAL', 'sentry.origin': 'auto.http.otel.koa', 'sentry.op': 'middleware.koa', }, @@ -72,7 +71,6 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'http.route': '/test-transaction', 'koa.name': '/test-transaction', 'koa.type': 'router', - 'otel.kind': 'INTERNAL', 'sentry.origin': 'auto.http.otel.koa', 'sentry.op': 'router.koa', }, @@ -88,7 +86,6 @@ test('Sends an API route transaction', async ({ baseURL }) => { }, { data: { - 'otel.kind': 'INTERNAL', 'sentry.origin': 'manual', }, description: 'test-span', @@ -102,7 +99,6 @@ test('Sends an API route transaction', async ({ baseURL }) => { }, { data: { - 'otel.kind': 'INTERNAL', 'sentry.origin': 'manual', }, description: 'child-span', diff --git a/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/tests/transactions.test.ts index 39a7d27e9cb1..f7fee0559a97 100644 --- a/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-otel-sdk-node/tests/transactions.test.ts @@ -77,7 +77,6 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'http.route': '/', 'express.name': 'query', 'express.type': 'middleware', - 'otel.kind': 'INTERNAL', }, description: 'query', op: 'middleware.express', @@ -97,7 +96,6 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'http.route': '/', 'express.name': 'expressInit', 'express.type': 'middleware', - 'otel.kind': 'INTERNAL', }, description: 'expressInit', op: 'middleware.express', @@ -117,7 +115,6 @@ test('Sends an API route transaction', async ({ baseURL }) => { 'http.route': '/test-transaction', 'express.name': '/test-transaction', 'express.type': 'request_handler', - 'otel.kind': 'INTERNAL', }, description: '/test-transaction', op: 'request_handler.express', @@ -158,7 +155,6 @@ test('Sends an API route transaction for an errored route', async ({ baseURL }) 'http.route': '/', 'express.name': 'query', 'express.type': 'middleware', - 'otel.kind': 'INTERNAL', }, description: 'query', op: 'middleware.express', @@ -178,7 +174,6 @@ test('Sends an API route transaction for an errored route', async ({ baseURL }) 'http.route': '/', 'express.name': 'expressInit', 'express.type': 'middleware', - 'otel.kind': 'INTERNAL', }, description: 'expressInit', op: 'middleware.express', @@ -198,7 +193,6 @@ test('Sends an API route transaction for an errored route', async ({ baseURL }) 'http.route': '/test-exception/:id', 'express.name': '/test-exception/:id', 'express.type': 'request_handler', - 'otel.kind': 'INTERNAL', }, description: '/test-exception/:id', op: 'request_handler.express', diff --git a/dev-packages/e2e-tests/test-applications/react-17/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/react-17/tests/transactions.test.ts index 665b5c02aafe..3b9c5ab1fdaf 100644 --- a/dev-packages/e2e-tests/test-applications/react-17/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-17/tests/transactions.test.ts @@ -82,6 +82,7 @@ test('sends an INP span', async ({ page }) => { transaction: '/', 'sentry.exclusive_time': expect.any(Number), replay_id: expect.any(String), + 'user_agent.original': expect.stringContaining('Chrome'), }, description: 'body > div#root > input#exception-button[type="button"]', op: 'ui.interaction.click', diff --git a/dev-packages/e2e-tests/test-applications/react-router-6/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/react-router-6/tests/transactions.test.ts index 39e07b89c0ee..3f108931b00e 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-6/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-6/tests/transactions.test.ts @@ -82,6 +82,7 @@ test('sends an INP span', async ({ page }) => { transaction: '/', 'sentry.exclusive_time': expect.any(Number), replay_id: expect.any(String), + 'user_agent.original': expect.stringContaining('Chrome'), }, description: 'body > div#root > input#exception-button[type="button"]', op: 'ui.interaction.click', diff --git a/dev-packages/e2e-tests/test-applications/solid-solidrouter/package.json b/dev-packages/e2e-tests/test-applications/solid-solidrouter/package.json index 3f0d7ca13fce..26f2ee5ba342 100644 --- a/dev-packages/e2e-tests/test-applications/solid-solidrouter/package.json +++ b/dev-packages/e2e-tests/test-applications/solid-solidrouter/package.json @@ -27,7 +27,7 @@ }, "dependencies": { "@solidjs/router": "^0.13.5", - "solid-js": "^1.8.11", + "solid-js": "^1.8.18", "@sentry/solid": "latest || *" } } diff --git a/dev-packages/e2e-tests/test-applications/solid/package.json b/dev-packages/e2e-tests/test-applications/solid/package.json index ebfde066e6ef..6d56a61d08cf 100644 --- a/dev-packages/e2e-tests/test-applications/solid/package.json +++ b/dev-packages/e2e-tests/test-applications/solid/package.json @@ -26,7 +26,7 @@ "vite-plugin-solid": "^2.8.2" }, "dependencies": { - "solid-js": "^1.8.11", + "solid-js": "^1.8.18", "@sentry/solid": "latest || *" } } diff --git a/dev-packages/e2e-tests/test-applications/solidstart/.gitignore b/dev-packages/e2e-tests/test-applications/solidstart/.gitignore new file mode 100644 index 000000000000..a51ed3c20c8d --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidstart/.gitignore @@ -0,0 +1,46 @@ + +dist +.solid +.output +.vercel +.netlify +.vinxi + +# Environment +.env +.env*.local + +# dependencies +/node_modules +/.pnp +.pnp.js + +# IDEs and editors +/.idea +.project +.classpath +*.launch +.settings/ + +# Temp +gitignore + +# testing +/coverage + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +/test-results/ +/playwright-report/ +/playwright/.cache/ + +!*.d.ts diff --git a/dev-packages/e2e-tests/test-applications/solidstart/.npmrc b/dev-packages/e2e-tests/test-applications/solidstart/.npmrc new file mode 100644 index 000000000000..070f80f05092 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidstart/.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/solidstart/README.md b/dev-packages/e2e-tests/test-applications/solidstart/README.md new file mode 100644 index 000000000000..9a141e9c2f0d --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidstart/README.md @@ -0,0 +1,45 @@ +# SolidStart + +Everything you need to build a Solid project, powered by [`solid-start`](https://start.solidjs.com); + +## Creating a project + +```bash +# create a new project in the current directory +npm init solid@latest + +# create a new project in my-app +npm init solid@latest my-app +``` + +## Developing + +Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a +development server: + +```bash +npm run dev + +# or start the server and open the app in a new browser tab +npm run dev -- --open +``` + +## Building + +Solid apps are built with _presets_, which optimise your project for deployment to different environments. + +By default, `npm run build` will generate a Node app that you can run with `npm start`. To use a different preset, add +it to the `devDependencies` in `package.json` and specify in your `app.config.js`. + +## Testing + +Tests are written with `vitest`, `@solidjs/testing-library` and `@testing-library/jest-dom` to extend expect with some +helpful custom matchers. + +To run them, simply start: + +```sh +npm test +``` + +## This project was created with the [Solid CLI](https://solid-cli.netlify.app) diff --git a/dev-packages/e2e-tests/test-applications/solidstart/app.config.ts b/dev-packages/e2e-tests/test-applications/solidstart/app.config.ts new file mode 100644 index 000000000000..b3c737efe5ba --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidstart/app.config.ts @@ -0,0 +1,3 @@ +import { defineConfig } from '@solidjs/start/config'; + +export default defineConfig({}); diff --git a/dev-packages/e2e-tests/test-applications/solidstart/package.json b/dev-packages/e2e-tests/test-applications/solidstart/package.json new file mode 100644 index 000000000000..6409d191de5b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidstart/package.json @@ -0,0 +1,39 @@ +{ + "name": "example-with-vitest", + "version": "0.0.0", + "scripts": { + "clean": "pnpx rimraf node_modules pnpm-lock.yaml .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" + ], + "preview": "HOST=localhost PORT=3030 NODE_OPTIONS='--import ./src/instrument.server.mjs' vinxi dev", + "test:prod": "TEST_ENV=production playwright test", + "test:build": "pnpm install && npx playwright install && pnpm build", + "test:assert": "pnpm test:prod" + }, + "type": "module", + "dependencies": { + "@sentry/solidstart": "latest || *" + }, + "devDependencies": { + "@playwright/test": "^1.44.1", + "@solidjs/meta": "^0.29.4", + "@solidjs/router": "^0.13.4", + "@solidjs/start": "^1.0.2", + "@solidjs/testing-library": "^0.8.7", + "@testing-library/jest-dom": "^6.4.2", + "@testing-library/user-event": "^14.5.2", + "@vitest/ui": "^1.5.0", + "jsdom": "^24.0.0", + "solid-js": "1.8.17", + "typescript": "^5.4.5", + "vinxi": "^0.3.12", + "vite": "^5.2.8", + "vite-plugin-solid": "^2.10.2", + "vitest": "^1.5.0" + } +} diff --git a/dev-packages/e2e-tests/test-applications/solidstart/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/solidstart/playwright.config.mjs new file mode 100644 index 000000000000..395acfc282f9 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidstart/playwright.config.mjs @@ -0,0 +1,8 @@ +import { getPlaywrightConfig } from '@sentry-internal/test-utils'; + +const config = getPlaywrightConfig({ + startCommand: 'pnpm preview', + port: 3030, +}); + +export default config; diff --git a/dev-packages/e2e-tests/test-applications/solidstart/public/favicon.ico b/dev-packages/e2e-tests/test-applications/solidstart/public/favicon.ico new file mode 100644 index 000000000000..fb282da0719e Binary files /dev/null and b/dev-packages/e2e-tests/test-applications/solidstart/public/favicon.ico differ diff --git a/dev-packages/e2e-tests/test-applications/solidstart/src/app.tsx b/dev-packages/e2e-tests/test-applications/solidstart/src/app.tsx new file mode 100644 index 000000000000..3eb85218b575 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidstart/src/app.tsx @@ -0,0 +1,22 @@ +import { withSentryRouterRouting } from '@sentry/solidstart/solidrouter'; +import { MetaProvider, Title } from '@solidjs/meta'; +import { Router } from '@solidjs/router'; +import { FileRoutes } from '@solidjs/start/router'; +import { Suspense } from 'solid-js'; + +const SentryRouter = withSentryRouterRouting(Router); + +export default function App() { + return ( + ( + + SolidStart - with Vitest + {props.children} + + )} + > + + + ); +} diff --git a/dev-packages/e2e-tests/test-applications/solidstart/src/entry-client.tsx b/dev-packages/e2e-tests/test-applications/solidstart/src/entry-client.tsx new file mode 100644 index 000000000000..9391faa9652d --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidstart/src/entry-client.tsx @@ -0,0 +1,17 @@ +// @refresh reload +import * as Sentry from '@sentry/solidstart'; +import { solidRouterBrowserTracingIntegration } from '@sentry/solidstart/solidrouter'; +import { StartClient, mount } from '@solidjs/start/client'; + +Sentry.init({ + // We can't use env variables here, seems like they are stripped + // out in production builds. + dsn: 'https://public@dsn.ingest.sentry.io/1337', + environment: 'qa', // dynamic sampling bias to keep transactions + integrations: [solidRouterBrowserTracingIntegration()], + tunnel: 'http://localhost:3031/', // proxy server + // Performance Monitoring + tracesSampleRate: 1.0, // Capture 100% of the transactions +}); + +mount(() => , document.getElementById('app')!); diff --git a/dev-packages/e2e-tests/test-applications/solidstart/src/entry-server.tsx b/dev-packages/e2e-tests/test-applications/solidstart/src/entry-server.tsx new file mode 100644 index 000000000000..276935366318 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidstart/src/entry-server.tsx @@ -0,0 +1,21 @@ +// @refresh reload +import { StartServer, createHandler } from '@solidjs/start/server'; + +export default createHandler(() => ( + ( + + + + + + {assets} + + +
{children}
+ {scripts} + + + )} + /> +)); diff --git a/dev-packages/e2e-tests/test-applications/solidstart/src/instrument.server.mjs b/dev-packages/e2e-tests/test-applications/solidstart/src/instrument.server.mjs new file mode 100644 index 000000000000..4146470295e1 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidstart/src/instrument.server.mjs @@ -0,0 +1,8 @@ +import * as Sentry from '@sentry/solidstart'; + +Sentry.init({ + dsn: process.env.E2E_TEST_DSN, + environment: 'qa', // dynamic sampling bias to keep transactions + tracesSampleRate: 1.0, // Capture 100% of the transactions + tunnel: 'http://localhost:3031/', // proxy server +}); diff --git a/dev-packages/e2e-tests/test-applications/solidstart/src/routes/client-error.tsx b/dev-packages/e2e-tests/test-applications/solidstart/src/routes/client-error.tsx new file mode 100644 index 000000000000..e997e4fbb1e3 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidstart/src/routes/client-error.tsx @@ -0,0 +1,75 @@ +import * as Sentry from '@sentry/solidstart'; +import type { ParentProps } from 'solid-js'; +import { ErrorBoundary, createSignal, onMount } from 'solid-js'; + +const SentryErrorBoundary = Sentry.withSentryErrorBoundary(ErrorBoundary); + +const [count, setCount] = createSignal(1); +const [caughtError, setCaughtError] = createSignal(false); + +export default function ClientErrorPage() { + return ( + + {caughtError() && ( + + )} +
+
+ +
+
+ +
+
+
+ ); +} + +function Throw(props: { error: string }) { + onMount(() => { + throw new Error(props.error); + }); + return null; +} + +function SampleErrorBoundary(props: ParentProps) { + return ( + ( +
+

Error Boundary Fallback

+
+ {error.message} +
+ +
+ )} + > + {props.children} +
+ ); +} diff --git a/dev-packages/e2e-tests/test-applications/solidstart/src/routes/index.tsx b/dev-packages/e2e-tests/test-applications/solidstart/src/routes/index.tsx new file mode 100644 index 000000000000..f1635dee3b63 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidstart/src/routes/index.tsx @@ -0,0 +1,27 @@ +import { A } from '@solidjs/router'; + +export default function Home() { + return ( + <> +

Welcome to Solid Start

+

+ Visit docs.solidjs.com/solid-start to read the documentation +

+ + + ); +} diff --git a/dev-packages/e2e-tests/test-applications/solidstart/src/routes/users/[id].tsx b/dev-packages/e2e-tests/test-applications/solidstart/src/routes/users/[id].tsx new file mode 100644 index 000000000000..639ab0be8118 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidstart/src/routes/users/[id].tsx @@ -0,0 +1,6 @@ +import { useParams } from '@solidjs/router'; + +export default function User() { + const params = useParams(); + return
User ID: {params.id}
; +} diff --git a/dev-packages/e2e-tests/test-applications/solidstart/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/solidstart/start-event-proxy.mjs new file mode 100644 index 000000000000..d45a7a013f36 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidstart/start-event-proxy.mjs @@ -0,0 +1,6 @@ +import { startEventProxyServer } from '@sentry-internal/test-utils'; + +startEventProxyServer({ + port: 3031, + proxyServerName: 'solidstart', +}); 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 new file mode 100644 index 000000000000..a4edf3c46236 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidstart/tests/errorboundary.test.ts @@ -0,0 +1,90 @@ +import { expect, test } from '@playwright/test'; +import { waitForError } from '@sentry-internal/test-utils'; + +test('captures an exception', async ({ page }) => { + const errorEventPromise = waitForError('solidstart', errorEvent => { + return ( + !errorEvent.type && + errorEvent.exception?.values?.[0]?.value === + 'Error 1 thrown from Sentry ErrorBoundary in Solid Start E2E test app' + ); + }); + + await page.goto('/client-error'); + await page.locator('#caughtErrorBtn').click(); + const errorEvent = await errorEventPromise; + + expect(errorEvent).toMatchObject({ + exception: { + values: [ + { + type: 'Error', + value: 'Error 1 thrown from Sentry ErrorBoundary in Solid Start E2E test app', + mechanism: { + type: 'generic', + handled: true, + }, + }, + ], + }, + transaction: '/client-error', + }); +}); + +test('captures a second exception after resetting the boundary', async ({ page }) => { + const firstErrorEventPromise = waitForError('solidstart', errorEvent => { + return ( + !errorEvent.type && + errorEvent.exception?.values?.[0]?.value === + 'Error 1 thrown from Sentry ErrorBoundary in Solid Start E2E test app' + ); + }); + + await page.goto('/client-error'); + await page.locator('#caughtErrorBtn').click(); + const firstErrorEvent = await firstErrorEventPromise; + + expect(firstErrorEvent).toMatchObject({ + exception: { + values: [ + { + type: 'Error', + value: 'Error 1 thrown from Sentry ErrorBoundary in Solid Start E2E test app', + mechanism: { + type: 'generic', + handled: true, + }, + }, + ], + }, + transaction: '/client-error', + }); + + const secondErrorEventPromise = waitForError('solidstart', errorEvent => { + return ( + !errorEvent.type && + errorEvent.exception?.values?.[0]?.value === + 'Error 2 thrown from Sentry ErrorBoundary in Solid Start E2E test app' + ); + }); + + await page.locator('#errorBoundaryResetBtn').click(); + await page.locator('#caughtErrorBtn').click(); + const secondErrorEvent = await secondErrorEventPromise; + + expect(secondErrorEvent).toMatchObject({ + exception: { + values: [ + { + type: 'Error', + value: 'Error 2 thrown from Sentry ErrorBoundary in Solid Start E2E test app', + mechanism: { + type: 'generic', + handled: true, + }, + }, + ], + }, + transaction: '/client-error', + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/solidstart/tests/errors.client.test.ts b/dev-packages/e2e-tests/test-applications/solidstart/tests/errors.client.test.ts new file mode 100644 index 000000000000..c9ab1db244b5 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidstart/tests/errors.client.test.ts @@ -0,0 +1,32 @@ +import { expect, test } from '@playwright/test'; +import { waitForError } from '@sentry-internal/test-utils'; + +test.describe('client-side errors', () => { + test('captures error thrown on click', async ({ page }) => { + const errorPromise = waitForError('solidstart', async errorEvent => { + return errorEvent?.exception?.values?.[0]?.value === 'Error thrown from Solid Start E2E test app'; + }); + + await page.goto(`/client-error`); + await page.locator('#errorBtn').click(); + const error = await errorPromise; + + expect(error).toMatchObject({ + exception: { + values: [ + { + type: 'Error', + value: 'Error thrown from Solid Start E2E test app', + mechanism: { + type: 'instrument', + handled: false, + }, + }, + ], + }, + transaction: '/client-error', + }); + expect(error.tags).toMatchObject({ runtime: 'browser' }); + expect(error.transaction).toEqual('/client-error'); + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/solidstart/tests/performance.client.test.ts b/dev-packages/e2e-tests/test-applications/solidstart/tests/performance.client.test.ts new file mode 100644 index 000000000000..6e5f43e016c8 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidstart/tests/performance.client.test.ts @@ -0,0 +1,93 @@ +import { expect, test } from '@playwright/test'; +import { waitForTransaction } from '@sentry-internal/test-utils'; + +test('sends a pageload transaction', async ({ page }) => { + const transactionPromise = waitForTransaction('solidstart', async transactionEvent => { + return transactionEvent?.transaction === '/' && transactionEvent.contexts?.trace?.op === 'pageload'; + }); + + await page.goto('/'); + const pageloadTransaction = await transactionPromise; + + expect(pageloadTransaction).toMatchObject({ + contexts: { + trace: { + op: 'pageload', + origin: 'auto.pageload.browser', + }, + }, + transaction: '/', + transaction_info: { + source: 'url', + }, + }); +}); + +test('sends a navigation transaction', async ({ page }) => { + const transactionPromise = waitForTransaction('solidstart', async transactionEvent => { + return transactionEvent?.transaction === '/users/5' && transactionEvent.contexts?.trace?.op === 'navigation'; + }); + + await page.goto(`/`); + await page.locator('#navLink').click(); + const navigationTransaction = await transactionPromise; + + expect(navigationTransaction).toMatchObject({ + contexts: { + trace: { + op: 'navigation', + origin: 'auto.navigation.solidstart.solidrouter', + }, + }, + transaction: '/users/5', + transaction_info: { + source: 'url', + }, + }); +}); + +test('updates the transaction when using the back button', async ({ page }) => { + // Solid Router sends a `-1` navigation when using the back button. + // The sentry solidRouterBrowserTracingIntegration tries to update such + // transactions with the proper name once the `useLocation` hook triggers. + const navigationTxnPromise = waitForTransaction('solidstart', async transactionEvent => { + return transactionEvent?.transaction === '/users/6' && transactionEvent.contexts?.trace?.op === 'navigation'; + }); + + await page.goto(`/`); + await page.locator('#navLinkUserBack').click(); + const navigationTxn = await navigationTxnPromise; + + expect(navigationTxn).toMatchObject({ + contexts: { + trace: { + op: 'navigation', + origin: 'auto.navigation.solidstart.solidrouter', + }, + }, + transaction: '/users/6', + transaction_info: { + source: 'url', + }, + }); + + const backNavigationTxnPromise = waitForTransaction('solidstart', async transactionEvent => { + return transactionEvent?.transaction === '/' && transactionEvent.contexts?.trace?.op === 'navigation'; + }); + + await page.goBack(); + const backNavigationTxn = await backNavigationTxnPromise; + + expect(backNavigationTxn).toMatchObject({ + contexts: { + trace: { + op: 'navigation', + origin: 'auto.navigation.solidstart.solidrouter', + }, + }, + transaction: '/', + transaction_info: { + source: 'url', + }, + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/solidstart/tsconfig.json b/dev-packages/e2e-tests/test-applications/solidstart/tsconfig.json new file mode 100644 index 000000000000..6f11292cc5d8 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidstart/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "allowJs": true, + "strict": true, + "noEmit": true, + "types": ["vinxi/types/client", "vitest/globals", "@testing-library/jest-dom"], + "isolatedModules": true, + "paths": { + "~/*": ["./src/*"] + } + } +} diff --git a/dev-packages/e2e-tests/test-applications/solidstart/vitest.config.ts b/dev-packages/e2e-tests/test-applications/solidstart/vitest.config.ts new file mode 100644 index 000000000000..6c2b639dc300 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/solidstart/vitest.config.ts @@ -0,0 +1,10 @@ +import solid from 'vite-plugin-solid'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + plugins: [solid()], + resolve: { + conditions: ['development', 'browser'], + }, + envPrefix: 'PUBLIC_', +}); diff --git a/dev-packages/e2e-tests/verdaccio-config/config.yaml b/dev-packages/e2e-tests/verdaccio-config/config.yaml index 973d2173aefa..67ee55a9d9ce 100644 --- a/dev-packages/e2e-tests/verdaccio-config/config.yaml +++ b/dev-packages/e2e-tests/verdaccio-config/config.yaml @@ -62,6 +62,12 @@ packages: unpublish: $all # proxy: npmjs # Don't proxy for E2E tests! + '@sentry/cloudflare': + access: $all + publish: $all + unpublish: $all + # proxy: npmjs # Don't proxy for E2E tests! + '@sentry/deno': access: $all publish: $all diff --git a/dev-packages/node-integration-tests/src/index.ts b/dev-packages/node-integration-tests/src/index.ts index 08afc11fe7ea..4bd0a9ccce25 100644 --- a/dev-packages/node-integration-tests/src/index.ts +++ b/dev-packages/node-integration-tests/src/index.ts @@ -20,13 +20,17 @@ export function loggingTransport(_options: BaseTransportOptions): Transport { /** * Starts an express server and sends the port to the runner + * @param app Express app + * @param port Port to start the app on. USE WITH CAUTION! By default a random port will be chosen. + * Setting this port to something specific is useful for local debugging but dangerous for + * CI/CD environments where port collisions can cause flakes! */ -export function startExpressServerAndSendPortToRunner(app: Express): void { - const server = app.listen(0, () => { +export function startExpressServerAndSendPortToRunner(app: Express, port: number | undefined = undefined): void { + const server = app.listen(port || 0, () => { const address = server.address() as AddressInfo; // eslint-disable-next-line no-console - console.log(`{"port":${address.port}}`); + console.log(`{"port":${port || address.port}}`); }); } diff --git a/dev-packages/node-integration-tests/suites/tracing/apollo-graphql/test.ts b/dev-packages/node-integration-tests/suites/tracing/apollo-graphql/test.ts index 593ccff485fe..5bf91f7653c1 100644 --- a/dev-packages/node-integration-tests/suites/tracing/apollo-graphql/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/apollo-graphql/test.ts @@ -9,7 +9,6 @@ describe('GraphQL/Apollo Tests', () => { data: { 'graphql.operation.type': 'query', 'graphql.source': '{hello}', - 'otel.kind': 'INTERNAL', 'sentry.origin': 'auto.graphql.otel.graphql', }, description: 'query', @@ -31,7 +30,6 @@ describe('GraphQL/Apollo Tests', () => { 'graphql.operation.name': 'Mutation', 'graphql.operation.type': 'mutation', 'graphql.source': 'mutation Mutation($email: String) {\n login(email: $email)\n}', - 'otel.kind': 'INTERNAL', 'sentry.origin': 'auto.graphql.otel.graphql', }, description: 'mutation Mutation', diff --git a/dev-packages/node-integration-tests/suites/tracing/connect/test.ts b/dev-packages/node-integration-tests/suites/tracing/connect/test.ts index e343e6edf6fd..ad49a4e4532d 100644 --- a/dev-packages/node-integration-tests/suites/tracing/connect/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/connect/test.ts @@ -15,7 +15,6 @@ describe('connect auto-instrumentation', () => { 'connect.name': '/', 'connect.type': 'request_handler', 'http.route': '/', - 'otel.kind': 'INTERNAL', 'sentry.origin': 'auto.http.otel.connect', 'sentry.op': 'request_handler.connect', }), diff --git a/dev-packages/node-integration-tests/suites/tracing/httpIntegration/server-ignoreIncomingRequests.js b/dev-packages/node-integration-tests/suites/tracing/httpIntegration/server-ignoreIncomingRequests.js new file mode 100644 index 000000000000..f1e5d9870fcf --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/httpIntegration/server-ignoreIncomingRequests.js @@ -0,0 +1,38 @@ +const { loggingTransport } = require('@sentry-internal/node-integration-tests'); +const Sentry = require('@sentry/node'); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, + transport: loggingTransport, + + integrations: [ + Sentry.httpIntegration({ + ignoreIncomingRequests: url => { + return url.includes('/liveness'); + }, + }), + ], +}); + +// express must be required after Sentry is initialized +const express = require('express'); +const cors = require('cors'); +const { startExpressServerAndSendPortToRunner } = require('@sentry-internal/node-integration-tests'); + +const app = express(); + +app.use(cors()); + +app.get('/test', (_req, res) => { + res.send({ response: 'response 1' }); +}); + +app.get('/liveness', (_req, res) => { + res.send({ response: 'liveness' }); +}); + +Sentry.setupExpressErrorHandler(app); + +startExpressServerAndSendPortToRunner(app); diff --git a/dev-packages/node-integration-tests/suites/tracing/httpIntegration/server-ignoreOutgoingRequests.js b/dev-packages/node-integration-tests/suites/tracing/httpIntegration/server-ignoreOutgoingRequests.js new file mode 100644 index 000000000000..ce520c999259 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/httpIntegration/server-ignoreOutgoingRequests.js @@ -0,0 +1,42 @@ +const { loggingTransport } = require('@sentry-internal/node-integration-tests'); +const Sentry = require('@sentry/node'); +const http = require('http'); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, + transport: loggingTransport, + + integrations: [ + Sentry.httpIntegration({ + ignoreOutgoingRequests: url => { + return url.includes('example.com'); + }, + }), + ], +}); + +// express must be required after Sentry is initialized +const express = require('express'); +const cors = require('cors'); +const { startExpressServerAndSendPortToRunner } = require('@sentry-internal/node-integration-tests'); + +const app = express(); + +app.use(cors()); + +app.get('/test', (_req, response) => { + http + .request('http://example.com/', res => { + res.on('data', () => {}); + res.on('end', () => { + response.send({ response: 'done' }); + }); + }) + .end(); +}); + +Sentry.setupExpressErrorHandler(app); + +startExpressServerAndSendPortToRunner(app); diff --git a/dev-packages/node-integration-tests/suites/tracing/httpIntegration/test.ts b/dev-packages/node-integration-tests/suites/tracing/httpIntegration/test.ts index 6be5d36e2ee3..aebe0dd676ba 100644 --- a/dev-packages/node-integration-tests/suites/tracing/httpIntegration/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/httpIntegration/test.ts @@ -75,4 +75,59 @@ describe('httpIntegration', () => { .start(done) .makeRequest('get', '/test'); }); + + test("doesn't create a root span for incoming requests ignored via `ignoreIncomingRequests`", done => { + const runner = createRunner(__dirname, 'server-ignoreIncomingRequests.js') + .expect({ + transaction: { + contexts: { + trace: { + span_id: expect.any(String), + trace_id: expect.any(String), + data: { + url: expect.stringMatching(/\/test$/), + 'http.response.status_code': 200, + }, + op: 'http.server', + status: 'ok', + }, + }, + transaction: 'GET /test', + }, + }) + .start(done); + + runner.makeRequest('get', '/liveness'); // should be ignored + runner.makeRequest('get', '/test'); + }); + + test("doesn't create child spans for outgoing requests ignored via `ignoreOutgoingRequests`", done => { + const runner = createRunner(__dirname, 'server-ignoreOutgoingRequests.js') + .expect({ + transaction: { + contexts: { + trace: { + span_id: expect.any(String), + trace_id: expect.any(String), + data: { + url: expect.stringMatching(/\/test$/), + 'http.response.status_code': 200, + }, + op: 'http.server', + status: 'ok', + }, + }, + transaction: 'GET /test', + spans: [ + expect.objectContaining({ op: 'middleware.express', description: 'query' }), + expect.objectContaining({ op: 'middleware.express', description: 'expressInit' }), + expect.objectContaining({ op: 'middleware.express', description: 'corsMiddleware' }), + expect.objectContaining({ op: 'request_handler.express', description: '/test' }), + ], + }, + }) + .start(done); + + runner.makeRequest('get', '/test'); + }); }); diff --git a/dev-packages/node-integration-tests/suites/tracing/nestjs/test.ts b/dev-packages/node-integration-tests/suites/tracing/nestjs/test.ts index abb9cf6f0bdd..2b42f23c857a 100644 --- a/dev-packages/node-integration-tests/suites/tracing/nestjs/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/nestjs/test.ts @@ -27,7 +27,6 @@ conditionalTest({ min: 16 })('nestjs auto instrumentation', () => { 'nestjs.callback': 'getHello', 'nestjs.controller': 'AppController', 'nestjs.type': 'request_context', - 'otel.kind': 'INTERNAL', 'sentry.op': 'http', }), }), diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/test.ts b/dev-packages/node-integration-tests/suites/tracing/prisma-orm/test.ts index 3c96c6d80779..dd92de5d0292 100644 --- a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm/test.ts @@ -11,7 +11,6 @@ conditionalTest({ min: 16 })('Prisma ORM Tests', () => { method: 'create', model: 'User', name: 'User.create', - 'otel.kind': 'INTERNAL', 'sentry.origin': 'auto.db.otel.prisma', }, description: 'prisma:client:operation', @@ -19,7 +18,6 @@ conditionalTest({ min: 16 })('Prisma ORM Tests', () => { }), expect.objectContaining({ data: { - 'otel.kind': 'INTERNAL', 'sentry.origin': 'auto.db.otel.prisma', }, description: 'prisma:client:serialize', @@ -27,7 +25,6 @@ conditionalTest({ min: 16 })('Prisma ORM Tests', () => { }), expect.objectContaining({ data: { - 'otel.kind': 'INTERNAL', 'sentry.origin': 'auto.db.otel.prisma', }, description: 'prisma:client:connect', @@ -35,7 +32,6 @@ conditionalTest({ min: 16 })('Prisma ORM Tests', () => { }), expect.objectContaining({ data: { - 'otel.kind': 'INTERNAL', 'sentry.origin': 'auto.db.otel.prisma', }, description: 'prisma:engine', @@ -44,7 +40,6 @@ conditionalTest({ min: 16 })('Prisma ORM Tests', () => { expect.objectContaining({ data: { 'db.type': 'postgres', - 'otel.kind': 'INTERNAL', 'sentry.origin': 'auto.db.otel.prisma', }, description: 'prisma:engine:connection', @@ -55,7 +50,6 @@ conditionalTest({ min: 16 })('Prisma ORM Tests', () => { 'db.statement': expect.stringContaining( 'INSERT INTO "public"."User" ("createdAt","email","name") VALUES ($1,$2,$3) RETURNING "public"."User"."id", "public"."User"."createdAt", "public"."User"."email", "public"."User"."name" /* traceparent', ), - 'otel.kind': 'INTERNAL', 'sentry.origin': 'auto.db.otel.prisma', 'db.system': 'prisma', 'sentry.op': 'db', @@ -67,7 +61,6 @@ conditionalTest({ min: 16 })('Prisma ORM Tests', () => { }), expect.objectContaining({ data: { - 'otel.kind': 'INTERNAL', 'sentry.origin': 'auto.db.otel.prisma', }, description: 'prisma:engine:serialize', @@ -75,7 +68,6 @@ conditionalTest({ min: 16 })('Prisma ORM Tests', () => { }), expect.objectContaining({ data: { - 'otel.kind': 'INTERNAL', 'sentry.origin': 'auto.db.otel.prisma', }, description: 'prisma:engine:response_json_serialization', @@ -86,7 +78,6 @@ conditionalTest({ min: 16 })('Prisma ORM Tests', () => { method: 'findMany', model: 'User', name: 'User.findMany', - 'otel.kind': 'INTERNAL', 'sentry.origin': 'auto.db.otel.prisma', }, description: 'prisma:client:operation', @@ -94,7 +85,6 @@ conditionalTest({ min: 16 })('Prisma ORM Tests', () => { }), expect.objectContaining({ data: { - 'otel.kind': 'INTERNAL', 'sentry.origin': 'auto.db.otel.prisma', }, description: 'prisma:client:serialize', @@ -102,7 +92,6 @@ conditionalTest({ min: 16 })('Prisma ORM Tests', () => { }), expect.objectContaining({ data: { - 'otel.kind': 'INTERNAL', 'sentry.origin': 'auto.db.otel.prisma', }, description: 'prisma:engine', diff --git a/dev-packages/node-integration-tests/suites/tsconfig.json b/dev-packages/node-integration-tests/suites/tsconfig.json index 120c3aff3716..c8f7f2d29f65 100644 --- a/dev-packages/node-integration-tests/suites/tsconfig.json +++ b/dev-packages/node-integration-tests/suites/tsconfig.json @@ -1,3 +1,9 @@ { "extends": "../tsconfig.test.json", + + "compilerOptions": { + // Although this seems wrong to include `DOM` here, it's necessary to make + // global fetch available in tests in lower Node versions. + "lib": ["DOM", "ES2018"], + } } diff --git a/dev-packages/node-integration-tests/tsconfig.json b/dev-packages/node-integration-tests/tsconfig.json index 92db70d5ca09..9ef29d983a08 100644 --- a/dev-packages/node-integration-tests/tsconfig.json +++ b/dev-packages/node-integration-tests/tsconfig.json @@ -4,6 +4,9 @@ "include": ["utils/**/*.ts", "src/**/*.ts"], "compilerOptions": { + // Although this seems wrong to include `DOM` here, it's necessary to make + // global fetch available in tests in lower Node versions. + "lib": ["DOM", "ES2018"], // package-specific options "esModuleInterop": true, "types": ["node", "jest"] diff --git a/dev-packages/node-integration-tests/tsconfig.test.json b/dev-packages/node-integration-tests/tsconfig.test.json index 5a37b90c4fe2..3bd3452a3ead 100644 --- a/dev-packages/node-integration-tests/tsconfig.test.json +++ b/dev-packages/node-integration-tests/tsconfig.test.json @@ -4,6 +4,9 @@ "include": ["suites/**/*.ts"], "compilerOptions": { + // Although this seems wrong to include `DOM` here, it's necessary to make + // global fetch available in tests in lower Node versions. + "lib": ["DOM", "ES2018"], // should include all types from `./tsconfig.json` plus types for all test frameworks used "types": ["node", "jest"] diff --git a/dev-packages/test-utils/.eslintrc.js b/dev-packages/test-utils/.eslintrc.js index 175b9389af00..98318aea5c41 100644 --- a/dev-packages/test-utils/.eslintrc.js +++ b/dev-packages/test-utils/.eslintrc.js @@ -3,5 +3,12 @@ module.exports = { node: true, }, extends: ['../../.eslintrc.js'], - overrides: [], + overrides: [ + { + files: ['**/*.ts'], + rules: { + '@sentry-internal/sdk/no-optional-chaining': 'off', + }, + }, + ], }; diff --git a/dev-packages/test-utils/src/playwright-config.ts b/dev-packages/test-utils/src/playwright-config.ts index 33de29f5a7fc..a48ca969ad06 100644 --- a/dev-packages/test-utils/src/playwright-config.ts +++ b/dev-packages/test-utils/src/playwright-config.ts @@ -2,8 +2,8 @@ import type { PlaywrightTestConfig } from '@playwright/test'; /** Get a playwright config to use in an E2E test app. */ export function getPlaywrightConfig( - options: { - startCommand: string; + options?: { + startCommand?: string; port?: number; eventProxyPort?: number; eventProxyFile?: string; @@ -11,10 +11,10 @@ export function getPlaywrightConfig( overwriteConfig?: Partial, ): PlaywrightTestConfig { const testEnv = process.env['TEST_ENV'] || 'production'; - const appPort = options.port || 3030; - const eventProxyPort = options.eventProxyPort || 3031; - const eventProxyFile = options.eventProxyFile || 'start-event-proxy.mjs'; - const { startCommand } = options; + const appPort = options?.port || 3030; + const eventProxyPort = options?.eventProxyPort || 3031; + const eventProxyFile = options?.eventProxyFile || 'start-event-proxy.mjs'; + const startCommand = options?.startCommand; /** * See https://playwright.dev/docs/test-configuration. @@ -76,18 +76,23 @@ export function getPlaywrightConfig( stdout: 'pipe', stderr: 'pipe', }, - { - command: startCommand, - port: appPort, - stdout: 'pipe', - stderr: 'pipe', - env: { - PORT: appPort.toString(), - }, - }, ], }; + if (startCommand) { + // @ts-expect-error - we set `config.webserver` to an array above. + // TS just can't infer that and thinks it could also be undefined or an object. + config.webServer.push({ + command: startCommand, + port: appPort, + stdout: 'pipe', + stderr: 'pipe', + env: { + PORT: appPort.toString(), + }, + }); + } + return { ...config, ...overwriteConfig, diff --git a/package.json b/package.json index a77d4f3a4202..3d2a137ab650 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,6 @@ "clean:deps": "lerna clean --yes && rm -rf node_modules && yarn", "clean:tarballs": "rimraf **/*.tgz", "clean:all": "run-s clean:build clean:tarballs clean:caches clean:deps", - "codecov": "codecov", "fix": "run-s fix:biome fix:prettier fix:lerna", "fix:lerna": "lerna run fix", "fix:biome": "biome check --apply .", @@ -52,6 +51,7 @@ "packages/browser-utils", "packages/bun", "packages/core", + "packages/cloudflare", "packages/deno", "packages/ember", "packages/eslint-config-sdk", @@ -109,7 +109,6 @@ "@types/jsdom": "^21.1.6", "@types/node": "^14.18.0", "@vitest/coverage-v8": "^1.6.0", - "codecov": "^3.6.5", "deepmerge": "^4.2.2", "downlevel-dts": "~0.11.0", "eslint": "7.32.0", diff --git a/packages/angular/src/errorhandler.ts b/packages/angular/src/errorhandler.ts index 4ea44c2ffc63..28c06e1e6bfd 100644 --- a/packages/angular/src/errorhandler.ts +++ b/packages/angular/src/errorhandler.ts @@ -39,7 +39,8 @@ function extractHttpModuleError(error: HttpErrorResponse): string | Error { } // ... or an`ErrorEvent`, which can provide us with the message but no stack... - if (error.error instanceof ErrorEvent && error.error.message) { + // guarding `ErrorEvent` against `undefined` as it's not defined in Node environments + if (typeof ErrorEvent !== 'undefined' && error.error instanceof ErrorEvent && error.error.message) { return error.error.message; } diff --git a/packages/angular/test/errorhandler.test.ts b/packages/angular/test/errorhandler.test.ts index 3c2b2f994446..c30a7a87efc9 100644 --- a/packages/angular/test/errorhandler.test.ts +++ b/packages/angular/test/errorhandler.test.ts @@ -45,6 +45,11 @@ describe('SentryErrorHandler', () => { }); describe('handleError method', () => { + const originalErrorEvent = globalThis.ErrorEvent; + afterEach(() => { + globalThis.ErrorEvent = originalErrorEvent; + }); + it('extracts `null` error', () => { createErrorHandler().handleError(null); @@ -223,6 +228,18 @@ describe('SentryErrorHandler', () => { expect(captureExceptionSpy).toHaveBeenCalledWith('Handled unknown error', captureExceptionEventHint); }); + it('handles ErrorEvent being undefined', () => { + const httpErr = new ErrorEvent('http', { message: 'sentry-http-test' }); + const err = new HttpErrorResponse({ error: httpErr }); + + // @ts-expect-error - this is fine in this test + delete globalThis.ErrorEvent; + + expect(() => { + createErrorHandler().handleError(err); + }).not.toThrow(); + }); + it('extracts an Error with `ngOriginalError`', () => { const ngErr = new Error('sentry-ng-test'); const err = { diff --git a/packages/angular/tsconfig.ngc.json b/packages/angular/tsconfig.ngc.json index e915bd8cc32c..df29c7ffdfee 100644 --- a/packages/angular/tsconfig.ngc.json +++ b/packages/angular/tsconfig.ngc.json @@ -7,7 +7,7 @@ "compilerOptions": { "target": "es2018", "declarationMap": false, - "lib": ["dom", "es2018"], + "lib": ["DOM", "ES2018"], "baseUrl": "./" }, "angularCompilerOptions": { diff --git a/packages/browser-utils/src/index.ts b/packages/browser-utils/src/index.ts index f59ccbf8da8f..c71b2d70e31d 100644 --- a/packages/browser-utils/src/index.ts +++ b/packages/browser-utils/src/index.ts @@ -11,6 +11,7 @@ export { addPerformanceEntries, startTrackingInteractions, startTrackingLongTasks, + startTrackingLongAnimationFrames, startTrackingWebVitals, startTrackingINP, registerInpInteractionListener, diff --git a/packages/browser-utils/src/metrics/browserMetrics.ts b/packages/browser-utils/src/metrics/browserMetrics.ts index 4e473e42ea47..cb48c2e8b675 100644 --- a/packages/browser-utils/src/metrics/browserMetrics.ts +++ b/packages/browser-utils/src/metrics/browserMetrics.ts @@ -8,6 +8,7 @@ import { spanToJSON } from '@sentry/core'; import { DEBUG_BUILD } from '../debug-build'; import { WINDOW } from '../types'; import { + type PerformanceLongAnimationFrameTiming, addClsInstrumentationHandler, addFidInstrumentationHandler, addLcpInstrumentationHandler, @@ -120,6 +121,59 @@ export function startTrackingLongTasks(): void { }); } +/** + * Start tracking long animation frames. + */ +export function startTrackingLongAnimationFrames(): void { + // NOTE: the current web-vitals version (3.5.2) does not support long-animation-frame, so + // we directly observe `long-animation-frame` events instead of through the web-vitals + // `observe` helper function. + const observer = new PerformanceObserver(list => { + for (const entry of list.getEntries() as PerformanceLongAnimationFrameTiming[]) { + if (!getActiveSpan()) { + return; + } + if (!entry.scripts[0]) { + return; + } + + const startTime = msToSec((browserPerformanceTimeOrigin as number) + entry.startTime); + const duration = msToSec(entry.duration); + + const attributes: SpanAttributes = { + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.browser.metrics', + }; + const initialScript = entry.scripts[0]; + if (initialScript) { + const { invoker, invokerType, sourceURL, sourceFunctionName, sourceCharPosition } = initialScript; + attributes['browser.script.invoker'] = invoker; + attributes['browser.script.invoker_type'] = invokerType; + if (sourceURL) { + attributes['code.filepath'] = sourceURL; + } + if (sourceFunctionName) { + attributes['code.function'] = sourceFunctionName; + } + if (sourceCharPosition !== -1) { + attributes['browser.script.source_char_position'] = sourceCharPosition; + } + } + + const span = startInactiveSpan({ + name: 'Main UI thread blocked', + op: 'ui.long-animation-frame', + startTime, + attributes, + }); + if (span) { + span.end(startTime + duration); + } + } + }); + + observer.observe({ type: 'long-animation-frame', buffered: true }); +} + /** * Start tracking interaction events. */ diff --git a/packages/browser-utils/src/metrics/inp.ts b/packages/browser-utils/src/metrics/inp.ts index 00e524c048b6..c4186a20f17e 100644 --- a/packages/browser-utils/src/metrics/inp.ts +++ b/packages/browser-utils/src/metrics/inp.ts @@ -10,8 +10,9 @@ import { spanToJSON, startInactiveSpan, } from '@sentry/core'; -import type { Integration, SpanAttributes } from '@sentry/types'; +import type { Integration, Span, SpanAttributes } from '@sentry/types'; import { browserPerformanceTimeOrigin, dropUndefinedKeys, htmlTreeAsString } from '@sentry/utils'; +import { WINDOW } from '../types'; import { addInpInstrumentationHandler, addPerformanceInstrumentationHandler, @@ -19,13 +20,8 @@ import { } from './instrument'; import { getBrowserPerformanceAPI, msToSec } from './utils'; -// We only care about name here -interface PartialRouteInfo { - name: string | undefined; -} - const LAST_INTERACTIONS: number[] = []; -const INTERACTIONS_ROUTE_MAP = new Map(); +const INTERACTIONS_SPAN_MAP = new Map(); /** * Start tracking INP webvital events. @@ -97,14 +93,15 @@ function _trackINP(): () => void { const activeSpan = getActiveSpan(); const rootSpan = activeSpan ? getRootSpan(activeSpan) : undefined; - // We first try to lookup the route name from our INTERACTIONS_ROUTE_MAP, + // We first try to lookup the span from our INTERACTIONS_SPAN_MAP, // where we cache the route per interactionId - const cachedRouteName = interactionId != null ? INTERACTIONS_ROUTE_MAP.get(interactionId) : undefined; + const cachedSpan = interactionId != null ? INTERACTIONS_SPAN_MAP.get(interactionId) : undefined; + + const spanToUse = cachedSpan || rootSpan; // Else, we try to use the active span. // Finally, we fall back to look at the transactionName on the scope - const routeName = - cachedRouteName || (rootSpan ? spanToJSON(rootSpan).description : scope.getScopeData().transactionName); + const routeName = spanToUse ? spanToJSON(spanToUse).description : scope.getScopeData().transactionName; const user = scope.getUser(); @@ -133,6 +130,9 @@ function _trackINP(): () => void { user: userDisplay || undefined, profile_id: profileId || undefined, replay_id: replayId || undefined, + // INP score calculation in the sentry backend relies on the user agent + // to account for different INP values being reported from different browsers + 'user_agent.original': WINDOW.navigator && WINDOW.navigator.userAgent, }); const span = startInactiveSpan({ @@ -154,11 +154,17 @@ function _trackINP(): () => void { }); } -/** Register a listener to cache route information for INP interactions. */ -export function registerInpInteractionListener(latestRoute: PartialRouteInfo): void { +/** + * Register a listener to cache route information for INP interactions. + * TODO(v9): `latestRoute` no longer needs to be passed in and will be removed in v9. + */ +export function registerInpInteractionListener(_latestRoute?: unknown): void { const handleEntries = ({ entries }: { entries: PerformanceEntry[] }): void => { + const activeSpan = getActiveSpan(); + const activeRootSpan = activeSpan && getRootSpan(activeSpan); + entries.forEach(entry => { - if (!isPerformanceEventTiming(entry) || !latestRoute.name) { + if (!isPerformanceEventTiming(entry) || !activeRootSpan) { return; } @@ -168,21 +174,20 @@ export function registerInpInteractionListener(latestRoute: PartialRouteInfo): v } // If the interaction was already recorded before, nothing more to do - if (INTERACTIONS_ROUTE_MAP.has(interactionId)) { + if (INTERACTIONS_SPAN_MAP.has(interactionId)) { return; } // We keep max. 10 interactions in the list, then remove the oldest one & clean up if (LAST_INTERACTIONS.length > 10) { const last = LAST_INTERACTIONS.shift() as number; - INTERACTIONS_ROUTE_MAP.delete(last); + INTERACTIONS_SPAN_MAP.delete(last); } // We add the interaction to the list of recorded interactions - // and store the route information for this interaction - // (we clone the object because it is mutated when it changes) + // and store the span for this interaction LAST_INTERACTIONS.push(interactionId); - INTERACTIONS_ROUTE_MAP.set(interactionId, latestRoute.name); + INTERACTIONS_SPAN_MAP.set(interactionId, activeRootSpan); }); }; diff --git a/packages/browser-utils/src/metrics/instrument.ts b/packages/browser-utils/src/metrics/instrument.ts index e22a345e3116..39292fb19b83 100644 --- a/packages/browser-utils/src/metrics/instrument.ts +++ b/packages/browser-utils/src/metrics/instrument.ts @@ -36,6 +36,17 @@ interface PerformanceEventTiming extends PerformanceEntry { interactionId?: number; } +interface PerformanceScriptTiming extends PerformanceEntry { + sourceURL: string; + sourceFunctionName: string; + sourceCharPosition: number; + invoker: string; + invokerType: string; +} +export interface PerformanceLongAnimationFrameTiming extends PerformanceEntry { + scripts: PerformanceScriptTiming[]; +} + interface Metric { /** * The name of the metric (in acronym form). diff --git a/packages/browser-utils/tsconfig.json b/packages/browser-utils/tsconfig.json index bf45a09f2d71..6d1756d12826 100644 --- a/packages/browser-utils/tsconfig.json +++ b/packages/browser-utils/tsconfig.json @@ -4,6 +4,6 @@ "include": ["src/**/*"], "compilerOptions": { - // package-specific options + "lib": ["DOM", "ES2018"], } } diff --git a/packages/browser/src/tracing/browserTracingIntegration.ts b/packages/browser/src/tracing/browserTracingIntegration.ts index c058b1930928..9d5421f697cd 100644 --- a/packages/browser/src/tracing/browserTracingIntegration.ts +++ b/packages/browser/src/tracing/browserTracingIntegration.ts @@ -5,6 +5,7 @@ import { registerInpInteractionListener, startTrackingINP, startTrackingInteractions, + startTrackingLongAnimationFrames, startTrackingLongTasks, startTrackingWebVitals, } from '@sentry-internal/browser-utils'; @@ -102,6 +103,13 @@ export interface BrowserTracingOptions { */ enableLongTask: boolean; + /** + * If true, Sentry will capture long animation frames and add them to the corresponding transaction. + * + * Default: false + */ + enableLongAnimationFrame: boolean; + /** * If true, Sentry will capture first input delay and add it to the corresponding transaction. * @@ -160,6 +168,7 @@ const DEFAULT_BROWSER_TRACING_OPTIONS: BrowserTracingOptions = { instrumentPageLoad: true, markBackgroundSpan: true, enableLongTask: true, + enableLongAnimationFrame: false, enableInp: true, _experiments: {}, ...defaultRequestInstrumentationOptions, @@ -180,6 +189,7 @@ export const browserTracingIntegration = ((_options: Partial + + Sentry + +

+ +# Official Sentry SDK for Cloudflare [UNRELEASED] + +[![npm version](https://img.shields.io/npm/v/@sentry/cloudflare.svg)](https://www.npmjs.com/package/@sentry/cloudflare) +[![npm dm](https://img.shields.io/npm/dm/@sentry/cloudflare.svg)](https://www.npmjs.com/package/@sentry/cloudflare) +[![npm dt](https://img.shields.io/npm/dt/@sentry/cloudflare.svg)](https://www.npmjs.com/package/@sentry/cloudflare) + +## Links + +- [Official SDK Docs](https://docs.sentry.io/quickstart/) +- [TypeDoc](http://getsentry.github.io/sentry-javascript/) + +**Note: This SDK is unreleased. Please follow the +[tracking GH issue](https://github.com/getsentry/sentry-javascript/issues/12620) for updates.** + +## Usage + +TODO: Add usage instructions here. diff --git a/packages/cloudflare/package.json b/packages/cloudflare/package.json new file mode 100644 index 000000000000..eff219dd1cf8 --- /dev/null +++ b/packages/cloudflare/package.json @@ -0,0 +1,76 @@ +{ + "name": "@sentry/cloudflare", + "version": "8.17.0", + "description": "Offical Sentry SDK for Cloudflare Workers and Pages", + "repository": "git://github.com/getsentry/sentry-javascript.git", + "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/cloudflare", + "author": "Sentry", + "license": "MIT", + "engines": { + "node": ">=14.18" + }, + "files": [ + "/build" + ], + "main": "build/cjs/index.js", + "module": "build/esm/index.js", + "types": "build/types/index.d.ts", + "exports": { + "./package.json": "./package.json", + ".": { + "import": { + "types": "./build/types/index.d.ts", + "default": "./build/esm/index.js" + }, + "require": { + "types": "./build/types/index.d.ts", + "default": "./build/cjs/index.js" + } + } + }, + "typesVersions": { + "<4.9": { + "build/types/index.d.ts": [ + "build/types-ts3.8/index.d.ts" + ] + } + }, + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@sentry/core": "8.17.0", + "@sentry/types": "8.17.0", + "@sentry/utils": "8.17.0" + }, + "devDependencies": { + "@cloudflare/workers-types": "^4.20240620.0", + "miniflare": "^3.20240701.0", + "wrangler": "^3.63.2" + }, + "scripts": { + "build": "run-p build:transpile build:types", + "build:dev": "yarn build", + "build:transpile": "rollup -c rollup.npm.config.mjs", + "build:types": "run-s build:types:core build:types:downlevel", + "build:types:core": "tsc -p tsconfig.types.json", + "build:types:downlevel": "yarn downlevel-dts build/types build/types-ts3.8 --to ts3.8", + "build:watch": "run-p build:transpile:watch build:types:watch", + "build:dev:watch": "yarn build:watch", + "build:transpile:watch": "rollup -c rollup.npm.config.mjs --watch", + "build:types:watch": "tsc -p tsconfig.types.json --watch", + "build:tarball": "npm pack", + "circularDepCheck": "madge --circular src/index.ts", + "clean": "rimraf build coverage sentry-cloudflare-*.tgz", + "fix": "eslint . --format stylish --fix", + "lint": "eslint . --format stylish", + "test": "yarn test:unit", + "test:unit": "vitest run", + "test:watch": "vitest --watch", + "yalc:publish": "yalc publish --push --sig" + }, + "volta": { + "extends": "../../package.json" + }, + "sideEffects": false +} diff --git a/packages/cloudflare/rollup.npm.config.mjs b/packages/cloudflare/rollup.npm.config.mjs new file mode 100644 index 000000000000..84a06f2fb64a --- /dev/null +++ b/packages/cloudflare/rollup.npm.config.mjs @@ -0,0 +1,3 @@ +import { makeBaseNPMConfig, makeNPMConfigVariants } from '@sentry-internal/rollup-utils'; + +export default makeNPMConfigVariants(makeBaseNPMConfig()); diff --git a/packages/cloudflare/src/debug-build.ts b/packages/cloudflare/src/debug-build.ts new file mode 100644 index 000000000000..60aa50940582 --- /dev/null +++ b/packages/cloudflare/src/debug-build.ts @@ -0,0 +1,8 @@ +declare const __DEBUG_BUILD__: boolean; + +/** + * This serves as a build time flag that will be true by default, but false in non-debug builds or if users replace `__SENTRY_DEBUG__` in their generated code. + * + * ATTENTION: This constant must never cross package boundaries (i.e. be exported) to guarantee that it can be used for tree shaking. + */ +export const DEBUG_BUILD = __DEBUG_BUILD__; diff --git a/packages/cloudflare/src/index.ts b/packages/cloudflare/src/index.ts new file mode 100644 index 000000000000..cb0ff5c3b541 --- /dev/null +++ b/packages/cloudflare/src/index.ts @@ -0,0 +1 @@ +export {}; diff --git a/packages/cloudflare/test/fixtures/worker.mjs b/packages/cloudflare/test/fixtures/worker.mjs new file mode 100644 index 000000000000..2023f7471c43 --- /dev/null +++ b/packages/cloudflare/test/fixtures/worker.mjs @@ -0,0 +1,8 @@ +/** + * @type {import('@cloudflare/workers-types').ExportedHandler} + */ +export default { + async fetch(_request, _env, _ctx) { + return new Response('Hello Sentry!'); + }, +}; diff --git a/packages/cloudflare/test/index.test.ts b/packages/cloudflare/test/index.test.ts new file mode 100644 index 000000000000..30bd1f0962f6 --- /dev/null +++ b/packages/cloudflare/test/index.test.ts @@ -0,0 +1,17 @@ +import { describe, expect, test } from 'vitest'; + +import { Miniflare } from 'miniflare'; + +describe('index', () => { + test('simple test', async () => { + const mf = new Miniflare({ + scriptPath: './test/fixtures/worker.mjs', + modules: true, + port: 8787, + }); + + const res = await mf.dispatchFetch('http://localhost:8787/'); + expect(await res.text()).toBe('Hello Sentry!'); + await mf.dispose(); + }); +}); diff --git a/packages/cloudflare/test/tsconfig.json b/packages/cloudflare/test/tsconfig.json new file mode 100644 index 000000000000..120c3aff3716 --- /dev/null +++ b/packages/cloudflare/test/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../tsconfig.test.json", +} diff --git a/packages/cloudflare/tsconfig.json b/packages/cloudflare/tsconfig.json new file mode 100644 index 000000000000..18b3ec720bfe --- /dev/null +++ b/packages/cloudflare/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + + "include": ["src/**/*"], + + "compilerOptions": { + "types": ["@cloudflare/workers-types"] + } +} diff --git a/packages/cloudflare/tsconfig.test.json b/packages/cloudflare/tsconfig.test.json new file mode 100644 index 000000000000..42d9d0df227e --- /dev/null +++ b/packages/cloudflare/tsconfig.test.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + + "include": ["test/**/*"], + + "compilerOptions": { + // other package-specific, test-specific options + } +} diff --git a/packages/cloudflare/tsconfig.types.json b/packages/cloudflare/tsconfig.types.json new file mode 100644 index 000000000000..65455f66bd75 --- /dev/null +++ b/packages/cloudflare/tsconfig.types.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "build/types" + } +} diff --git a/packages/core/src/baseclient.ts b/packages/core/src/baseclient.ts index 1ad35e291013..9c4b43bd9c9d 100644 --- a/packages/core/src/baseclient.ts +++ b/packages/core/src/baseclient.ts @@ -459,27 +459,19 @@ export abstract class BaseClient implements Client { /** @inheritdoc */ public on(hook: string, callback: unknown): () => void { - // Note that the code below, with nullish coalescing assignment, - // may reduce the code, so it may be switched to when Node 14 support - // is dropped (the `??=` operator is supported since Node 15). - // (this._hooks[hook] ??= []).push(callback); - if (!this._hooks[hook]) { - this._hooks[hook] = []; - } + const hooks = (this._hooks[hook] = this._hooks[hook] || []); // @ts-expect-error We assue the types are correct - this._hooks[hook].push(callback); + hooks.push(callback); // This function returns a callback execution handler that, when invoked, // deregisters a callback. This is crucial for managing instances where callbacks // need to be unregistered to prevent self-referencing in callback closures, // ensuring proper garbage collection. return () => { - const hooks = this._hooks[hook]; - - if (hooks) { - // @ts-expect-error We assue the types are correct - const cbIndex = hooks.indexOf(callback); + // @ts-expect-error We assue the types are correct + const cbIndex = hooks.indexOf(callback); + if (cbIndex > -1) { hooks.splice(cbIndex, 1); } }; diff --git a/packages/core/src/integrations/metadata.ts b/packages/core/src/integrations/metadata.ts index 48fa23d6b6ab..0bf11746dc1b 100644 --- a/packages/core/src/integrations/metadata.ts +++ b/packages/core/src/integrations/metadata.ts @@ -33,7 +33,7 @@ export const moduleMetadataIntegration = defineIntegration(() => { client.on('applyFrameMetadata', event => { // Only apply stack frame metadata to error events - if (event.type !== undefined) { + if (event.type) { return; } diff --git a/packages/core/src/integrations/third-party-errors-filter.ts b/packages/core/src/integrations/third-party-errors-filter.ts index 1f1887604866..652ca23a6da6 100644 --- a/packages/core/src/integrations/third-party-errors-filter.ts +++ b/packages/core/src/integrations/third-party-errors-filter.ts @@ -56,7 +56,7 @@ export const thirdPartyErrorFilterIntegration = defineIntegration((options: Opti client.on('applyFrameMetadata', event => { // Only apply stack frame metadata to error events - if (event.type !== undefined) { + if (event.type) { return; } diff --git a/packages/core/src/tracing/idleSpan.ts b/packages/core/src/tracing/idleSpan.ts index 67093076f443..c3d66a4b7593 100644 --- a/packages/core/src/tracing/idleSpan.ts +++ b/packages/core/src/tracing/idleSpan.ts @@ -96,6 +96,8 @@ export function startIdleSpan(startSpanOptions: StartSpanOptions, options: Parti let _autoFinishAllowed: boolean = !options.disableAutoFinish; + const _cleanupHooks: (() => void)[] = []; + const { idleTimeout = TRACING_DEFAULTS.idleTimeout, finalTimeout = TRACING_DEFAULTS.finalTimeout, @@ -240,6 +242,8 @@ export function startIdleSpan(startSpanOptions: StartSpanOptions, options: Parti _finished = true; activities.clear(); + _cleanupHooks.forEach(cleanup => cleanup()); + _setSpanForScope(scope, previousActiveSpan); const spanJSON = spanToJSON(span); @@ -298,41 +302,47 @@ export function startIdleSpan(startSpanOptions: StartSpanOptions, options: Parti } } - client.on('spanStart', startedSpan => { - // If we already finished the idle span, - // or if this is the idle span itself being started, - // or if the started span has already been closed, - // we don't care about it for activity - if (_finished || startedSpan === span || !!spanToJSON(startedSpan).timestamp) { - return; - } + _cleanupHooks.push( + client.on('spanStart', startedSpan => { + // If we already finished the idle span, + // or if this is the idle span itself being started, + // or if the started span has already been closed, + // we don't care about it for activity + if (_finished || startedSpan === span || !!spanToJSON(startedSpan).timestamp) { + return; + } - const allSpans = getSpanDescendants(span); + const allSpans = getSpanDescendants(span); - // If the span that was just started is a child of the idle span, we should track it - if (allSpans.includes(startedSpan)) { - _pushActivity(startedSpan.spanContext().spanId); - } - }); + // If the span that was just started is a child of the idle span, we should track it + if (allSpans.includes(startedSpan)) { + _pushActivity(startedSpan.spanContext().spanId); + } + }), + ); - client.on('spanEnd', endedSpan => { - if (_finished) { - return; - } + _cleanupHooks.push( + client.on('spanEnd', endedSpan => { + if (_finished) { + return; + } - _popActivity(endedSpan.spanContext().spanId); - }); + _popActivity(endedSpan.spanContext().spanId); + }), + ); - client.on('idleSpanEnableAutoFinish', spanToAllowAutoFinish => { - if (spanToAllowAutoFinish === span) { - _autoFinishAllowed = true; - _restartIdleTimeout(); + _cleanupHooks.push( + client.on('idleSpanEnableAutoFinish', spanToAllowAutoFinish => { + if (spanToAllowAutoFinish === span) { + _autoFinishAllowed = true; + _restartIdleTimeout(); - if (activities.size) { - _restartChildSpanTimeout(); + if (activities.size) { + _restartChildSpanTimeout(); + } } - } - }); + }), + ); // We only start the initial idle timeout if we are not delaying the auto finish if (!options.disableAutoFinish) { diff --git a/packages/core/src/tracing/trace.ts b/packages/core/src/tracing/trace.ts index cdb2efd7221c..0cb98082c590 100644 --- a/packages/core/src/tracing/trace.ts +++ b/packages/core/src/tracing/trace.ts @@ -157,7 +157,7 @@ export function startInactiveSpan(options: StartSpanOptions): Span { // If `options.parentSpan` is defined, we want to wrap the callback in `withActiveSpan` const wrapper = options.scope ? (callback: () => Span) => withScope(options.scope, callback) - : customParentSpan + : customParentSpan !== undefined ? (callback: () => Span) => withActiveSpan(customParentSpan, callback) : (callback: () => Span) => callback(); @@ -445,8 +445,8 @@ function getParentSpan(scope: Scope): SentrySpan | undefined { return span; } -function getActiveSpanWrapper(parentSpan?: Span): (callback: () => T) => T { - return parentSpan +function getActiveSpanWrapper(parentSpan: Span | undefined | null): (callback: () => T) => T { + return parentSpan !== undefined ? (callback: () => T) => { return withActiveSpan(parentSpan, callback); } diff --git a/packages/core/src/utils/hasTracingEnabled.ts b/packages/core/src/utils/hasTracingEnabled.ts index 97463d9d5e5e..5e673bc08caa 100644 --- a/packages/core/src/utils/hasTracingEnabled.ts +++ b/packages/core/src/utils/hasTracingEnabled.ts @@ -17,6 +17,7 @@ export function hasTracingEnabled( } const options = maybeOptions || getClientOptions(); + // eslint-disable-next-line deprecation/deprecation return !!options && (options.enableTracing || 'tracesSampleRate' in options || 'tracesSampler' in options); } diff --git a/packages/core/test/lib/tracing/trace.test.ts b/packages/core/test/lib/tracing/trace.test.ts index 33b8e0572835..fe58ce6f9f7d 100644 --- a/packages/core/test/lib/tracing/trace.test.ts +++ b/packages/core/test/lib/tracing/trace.test.ts @@ -282,6 +282,14 @@ describe('startSpan', () => { expect(getActiveSpan()).toBe(undefined); }); + it('allows to pass parentSpan=null', () => { + startSpan({ name: 'GET users/[id]' }, () => { + startSpan({ name: 'GET users/[id]', parentSpan: null }, span => { + expect(spanToJSON(span).parent_span_id).toBe(undefined); + }); + }); + }); + it('allows to force a transaction with forceTransaction=true', async () => { const options = getDefaultTestClientOptions({ tracesSampleRate: 1.0 }); client = new TestClient(options); @@ -693,6 +701,15 @@ describe('startSpanManual', () => { expect(getActiveSpan()).toBe(undefined); }); + it('allows to pass parentSpan=null', () => { + startSpan({ name: 'GET users/[id]' }, () => { + startSpanManual({ name: 'child', parentSpan: null }, span => { + expect(spanToJSON(span).parent_span_id).toBe(undefined); + span.end(); + }); + }); + }); + it('allows to force a transaction with forceTransaction=true', async () => { const options = getDefaultTestClientOptions({ tracesSampleRate: 1.0 }); client = new TestClient(options); @@ -1014,6 +1031,14 @@ describe('startInactiveSpan', () => { expect(getActiveSpan()).toBeUndefined(); }); + it('allows to pass parentSpan=null', () => { + startSpan({ name: 'outer' }, () => { + const span = startInactiveSpan({ name: 'GET users/[id]', parentSpan: null }); + expect(spanToJSON(span).parent_span_id).toBe(undefined); + span.end(); + }); + }); + it('allows to force a transaction with forceTransaction=true', async () => { const options = getDefaultTestClientOptions({ tracesSampleRate: 1.0 }); client = new TestClient(options); diff --git a/packages/core/tsconfig.test.json b/packages/core/tsconfig.test.json index 87f6afa06b86..6fde53bec436 100644 --- a/packages/core/tsconfig.test.json +++ b/packages/core/tsconfig.test.json @@ -4,6 +4,7 @@ "include": ["test/**/*"], "compilerOptions": { + "lib": ["DOM", "ES2018"], // should include all types from `./tsconfig.json` plus types for all test frameworks used "types": ["node", "jest"] diff --git a/packages/ember/tsconfig.json b/packages/ember/tsconfig.json index 95bb38c78628..584a13e19669 100644 --- a/packages/ember/tsconfig.json +++ b/packages/ember/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.json", "compilerOptions": { "target": "es2022", + "lib": ["DOM", "ES2022"], "allowJs": true, "moduleResolution": "node", "allowSyntheticDefaultImports": true, diff --git a/packages/feedback/src/core/sendFeedback.ts b/packages/feedback/src/core/sendFeedback.ts index 3f8c08a51ee3..ca9875284c6e 100644 --- a/packages/feedback/src/core/sendFeedback.ts +++ b/packages/feedback/src/core/sendFeedback.ts @@ -40,12 +40,13 @@ export const sendFeedback: SendFeedback = ( // After 5s, we want to clear anyhow const timeout = setTimeout(() => reject('Unable to determine if Feedback was correctly sent.'), 5_000); - client.on('afterSendEvent', (event: Event, response: TransportMakeRequestResponse) => { + const cleanup = client.on('afterSendEvent', (event: Event, response: TransportMakeRequestResponse) => { if (event.event_id !== eventId) { return; } clearTimeout(timeout); + cleanup(); // Require valid status codes, otherwise can assume feedback was not sent successfully if ( diff --git a/packages/gatsby/tsconfig.json b/packages/gatsby/tsconfig.json index b2c40b91a630..77d5f63b9345 100644 --- a/packages/gatsby/tsconfig.json +++ b/packages/gatsby/tsconfig.json @@ -4,6 +4,7 @@ "include": ["src/**/*"], "compilerOptions": { + "lib": ["DOM", "ES2018"], // package-specific options "jsx": "react" } diff --git a/packages/nestjs/README.md b/packages/nestjs/README.md index 8928327b1470..b9ac0c9371c2 100644 --- a/packages/nestjs/README.md +++ b/packages/nestjs/README.md @@ -41,7 +41,7 @@ Note that it is necessary to initialize Sentry **before you import any package t ## Span Decorator Use the @SentryTraced() decorator to gain additional performance insights for any function within your NestJS -application. +applications. ```js import { Injectable } from '@nestjs/common'; diff --git a/packages/nestjs/src/span-decorator.ts b/packages/nestjs/src/span-decorator.ts index c56056a26621..b9ef861bc3b2 100644 --- a/packages/nestjs/src/span-decorator.ts +++ b/packages/nestjs/src/span-decorator.ts @@ -6,7 +6,7 @@ import { startSpan } from '@sentry/node'; export function SentryTraced(op: string = 'function') { return function (target: unknown, propertyKey: string, descriptor: PropertyDescriptor) { // eslint-disable-next-line @typescript-eslint/no-explicit-any - const originalMethod = descriptor.value as (...args: any[]) => Promise; + const originalMethod = descriptor.value as (...args: any[]) => Promise | any; // function can be sync or async // eslint-disable-next-line @typescript-eslint/no-explicit-any descriptor.value = function (...args: any[]) { @@ -15,7 +15,7 @@ export function SentryTraced(op: string = 'function') { op: op, name: propertyKey, }, - async () => { + () => { return originalMethod.apply(this, args); }, ); diff --git a/packages/nextjs/src/common/captureRequestError.ts b/packages/nextjs/src/common/captureRequestError.ts new file mode 100644 index 000000000000..7968907ad9bf --- /dev/null +++ b/packages/nextjs/src/common/captureRequestError.ts @@ -0,0 +1,50 @@ +import { captureException, withScope } from '@sentry/core'; + +type RequestInfo = { + url: string; + method: string; + headers: Record; +}; + +type ErrorContext = { + routerKind: string; // 'Pages Router' | 'App Router' + routePath: string; + routeType: string; // 'render' | 'route' | 'middleware' +}; + +/** + * Reports errors for the Next.js `onRequestError` instrumentation hook. + * + * Notice: This function is experimental and not intended for production use. Breaking changes may be done to this funtion in any release. + * + * @experimental + */ +export function experimental_captureRequestError( + error: unknown, + request: RequestInfo, + errorContext: ErrorContext, +): void { + withScope(scope => { + scope.setSDKProcessingMetadata({ + request: { + headers: request.headers, + method: request.method, + }, + }); + + scope.setContext('nextjs', { + request_path: request.url, + router_kind: errorContext.routerKind, + router_path: errorContext.routePath, + route_type: errorContext.routeType, + }); + + scope.setTransactionName(errorContext.routePath); + + captureException(error, { + mechanism: { + handled: false, + }, + }); + }); +} diff --git a/packages/nextjs/src/common/index.ts b/packages/nextjs/src/common/index.ts index e308537f1358..23ddfa383772 100644 --- a/packages/nextjs/src/common/index.ts +++ b/packages/nextjs/src/common/index.ts @@ -1,25 +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 { wrapServerComponentWithSentry } from './wrapServerComponentWithSentry'; - export { wrapRouteHandlerWithSentry } from './wrapRouteHandlerWithSentry'; - export { wrapApiHandlerWithSentryVercelCrons } from './wrapApiHandlerWithSentryVercelCrons'; - export { wrapMiddlewareWithSentry } from './wrapMiddlewareWithSentry'; - export { wrapPageComponentWithSentry } from './wrapPageComponentWithSentry'; - export { wrapGenerationFunctionWithSentry } from './wrapGenerationFunctionWithSentry'; - export { withServerActionInstrumentation } from './withServerActionInstrumentation'; +export { experimental_captureRequestError } from './captureRequestError'; diff --git a/packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts b/packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts index e55eedd9802e..bf0d475603f2 100644 --- a/packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts +++ b/packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts @@ -22,7 +22,9 @@ import { import { vercelWaitUntil } from './utils/vercelWaitUntil'; /** - * Wraps a Next.js route handler with performance and error instrumentation. + * Wraps a Next.js App Router Route handler with Sentry error and performance instrumentation. + * + * NOTICE: This wrapper is for App Router API routes. If you are looking to wrap Pages Router API routes use `wrapApiHandlerWithSentry` instead. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any export function wrapRouteHandlerWithSentry any>( diff --git a/packages/nextjs/src/config/loaders/wrappingLoader.ts b/packages/nextjs/src/config/loaders/wrappingLoader.ts index a0d953d8315b..ea7828497f95 100644 --- a/packages/nextjs/src/config/loaders/wrappingLoader.ts +++ b/packages/nextjs/src/config/loaders/wrappingLoader.ts @@ -182,7 +182,8 @@ export default function wrappingLoader( const componentTypeMatch = path.posix .normalize(path.relative(appDir, this.resourcePath)) - .match(/\/?([^/]+)\.(?:js|ts|jsx|tsx)$/); + // eslint-disable-next-line @sentry-internal/sdk/no-regexp-constructor + .match(new RegExp(`/\\/?([^/]+)\\.(?:${pageExtensionRegex})$`)); if (componentTypeMatch && componentTypeMatch[1]) { let componentType: ServerComponentContext['componentType']; diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index 4002db18f295..ecc39f7372dd 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -147,7 +147,7 @@ export function constructWebpackConfigFunction( ); }; - const possibleMiddlewareLocations = ['js', 'jsx', 'ts', 'tsx'].map(middlewareFileEnding => { + const possibleMiddlewareLocations = pageExtensions.map(middlewareFileEnding => { return path.join(middlewareLocationFolder, `middleware.${middlewareFileEnding}`); }); const isMiddlewareResource = (resourcePath: string): boolean => { @@ -163,7 +163,10 @@ export function constructWebpackConfigFunction( return ( appDirPath !== undefined && normalizedAbsoluteResourcePath.startsWith(appDirPath + path.sep) && - !!normalizedAbsoluteResourcePath.match(/[\\/](page|layout|loading|head|not-found)\.(js|jsx|tsx)$/) + !!normalizedAbsoluteResourcePath.match( + // eslint-disable-next-line @sentry-internal/sdk/no-regexp-constructor + new RegExp(`[\\\\/](page|layout|loading|head|not-found)\\.(${pageExtensionRegex})$`), + ) ); }; @@ -172,7 +175,10 @@ export function constructWebpackConfigFunction( return ( appDirPath !== undefined && normalizedAbsoluteResourcePath.startsWith(appDirPath + path.sep) && - !!normalizedAbsoluteResourcePath.match(/[\\/]route\.(js|jsx|ts|tsx)$/) + !!normalizedAbsoluteResourcePath.match( + // eslint-disable-next-line @sentry-internal/sdk/no-regexp-constructor + new RegExp(`[\\\\/]route\\.(${pageExtensionRegex})$`), + ) ); }; @@ -285,10 +291,12 @@ export function constructWebpackConfigFunction( } if (appDirPath) { - const hasGlobalErrorFile = ['global-error.js', 'global-error.jsx', 'global-error.ts', 'global-error.tsx'].some( - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - globalErrorFile => fs.existsSync(path.join(appDirPath!, globalErrorFile)), - ); + const hasGlobalErrorFile = pageExtensions + .map(extension => `global-error.${extension}`) + .some( + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + globalErrorFile => fs.existsSync(path.join(appDirPath!, globalErrorFile)), + ); if ( !hasGlobalErrorFile && diff --git a/packages/nextjs/src/index.types.ts b/packages/nextjs/src/index.types.ts index 629560b96312..b093968bdebe 100644 --- a/packages/nextjs/src/index.types.ts +++ b/packages/nextjs/src/index.types.ts @@ -41,7 +41,9 @@ export declare const metrics: typeof clientSdk.metrics & typeof serverSdk.metric export { withSentryConfig } from './config'; /** - * Wraps a Next.js API handler with Sentry error and performance instrumentation. + * Wraps a Next.js Pages Router API route with Sentry error and performance instrumentation. + * + * NOTICE: This wrapper is for Pages Router API routes. If you are looking to wrap App Router API routes use `wrapRouteHandlerWithSentry` instead. * * @param handler The handler exported from the API route file. * @param parameterizedRoute The page's parameterized route. @@ -138,3 +140,5 @@ export declare function wrapApiHandlerWithSentryVercelCrons(WrappingTarget: C): C; + +export { experimental_captureRequestError } from './common/captureRequestError'; diff --git a/packages/nextjs/test/config/fixtures.ts b/packages/nextjs/test/config/fixtures.ts index 7da47e37be33..a3c4feb0123b 100644 --- a/packages/nextjs/test/config/fixtures.ts +++ b/packages/nextjs/test/config/fixtures.ts @@ -13,6 +13,7 @@ export const EDGE_SDK_CONFIG_FILE = 'sentry.edge.config.js'; /** Mock next config object */ export const userNextConfig: NextConfigObject = { publicRuntimeConfig: { location: 'dogpark', activities: ['fetch', 'chasing', 'digging'] }, + pageExtensions: ['jsx', 'js', 'tsx', 'ts', 'custom.jsx', 'custom.js', 'custom.tsx', 'custom.ts'], webpack: (incomingWebpackConfig: WebpackConfigObject, _options: BuildContext) => ({ ...incomingWebpackConfig, mode: 'universal-sniffing', diff --git a/packages/nextjs/test/config/loaders.test.ts b/packages/nextjs/test/config/loaders.test.ts index c2aaf0c9a707..c559ee643baf 100644 --- a/packages/nextjs/test/config/loaders.test.ts +++ b/packages/nextjs/test/config/loaders.test.ts @@ -96,6 +96,10 @@ describe('webpack loaders', () => { resourcePath: '/Users/Maisey/projects/squirrelChasingSimulator/src/pages/testPage.tsx', expectedWrappingTargetKind: 'page', }, + { + resourcePath: '/Users/Maisey/projects/squirrelChasingSimulator/src/pages/testPage.custom.tsx', + expectedWrappingTargetKind: 'page', + }, { resourcePath: './src/pages/testPage.tsx', expectedWrappingTargetKind: 'page', @@ -133,6 +137,10 @@ describe('webpack loaders', () => { resourcePath: '/Users/Maisey/projects/squirrelChasingSimulator/src/middleware.js', expectedWrappingTargetKind: 'middleware', }, + { + resourcePath: '/Users/Maisey/projects/squirrelChasingSimulator/src/middleware.custom.js', + expectedWrappingTargetKind: 'middleware', + }, { resourcePath: './src/middleware.js', expectedWrappingTargetKind: 'middleware', @@ -162,17 +170,33 @@ describe('webpack loaders', () => { resourcePath: '/Users/Maisey/projects/squirrelChasingSimulator/src/pages/api/nested/testApiRoute.js', expectedWrappingTargetKind: 'api-route', }, + { + resourcePath: '/Users/Maisey/projects/squirrelChasingSimulator/src/pages/api/nested/testApiRoute.custom.js', + expectedWrappingTargetKind: 'api-route', + }, + { + resourcePath: '/Users/Maisey/projects/squirrelChasingSimulator/src/app/nested/route.ts', + expectedWrappingTargetKind: 'route-handler', + }, + { + resourcePath: '/Users/Maisey/projects/squirrelChasingSimulator/src/app/nested/route.custom.ts', + expectedWrappingTargetKind: 'route-handler', + }, { resourcePath: '/Users/Maisey/projects/squirrelChasingSimulator/src/app/page.js', expectedWrappingTargetKind: 'server-component', }, + { + resourcePath: '/Users/Maisey/projects/squirrelChasingSimulator/src/app/page.custom.js', + expectedWrappingTargetKind: 'server-component', + }, { resourcePath: '/Users/Maisey/projects/squirrelChasingSimulator/src/app/nested/page.js', expectedWrappingTargetKind: 'server-component', }, { - resourcePath: '/Users/Maisey/projects/squirrelChasingSimulator/src/app/nested/page.ts', // ts is not a valid file ending for pages in the app dir - expectedWrappingTargetKind: undefined, + resourcePath: '/Users/Maisey/projects/squirrelChasingSimulator/src/app/nested/page.ts', + expectedWrappingTargetKind: 'server-component', }, { resourcePath: '/Users/Maisey/projects/squirrelChasingSimulator/src/app/(group)/nested/page.tsx', @@ -180,7 +204,7 @@ describe('webpack loaders', () => { }, { resourcePath: '/Users/Maisey/projects/squirrelChasingSimulator/src/app/(group)/nested/loading.ts', - expectedWrappingTargetKind: undefined, + expectedWrappingTargetKind: 'server-component', }, { resourcePath: '/Users/Maisey/projects/squirrelChasingSimulator/src/app/layout.js', diff --git a/packages/nextjs/tsconfig.test.json b/packages/nextjs/tsconfig.test.json index 87f6afa06b86..f72f7d93a39e 100644 --- a/packages/nextjs/tsconfig.test.json +++ b/packages/nextjs/tsconfig.test.json @@ -5,8 +5,9 @@ "compilerOptions": { // should include all types from `./tsconfig.json` plus types for all test frameworks used - "types": ["node", "jest"] + "types": ["node", "jest"], // other package-specific, test-specific options + "lib": ["DOM", "ESNext"] } } diff --git a/packages/node/README.md b/packages/node/README.md index 20fe8cc175c3..6471538fb4f0 100644 --- a/packages/node/README.md +++ b/packages/node/README.md @@ -21,6 +21,11 @@ yarn add @sentry/node ## Usage +Sentry should be initialized as early in your app as possible. It is essential that you call `Sentry.init` before you +require any other modules in your application, otherwise auto-instrumentation of these modules will **not** work. + +You need to create a file named `instrument.js` that imports and initializes Sentry: + ```js // CJS Syntax const Sentry = require('@sentry/node'); @@ -33,26 +38,39 @@ Sentry.init({ }); ``` -Note that it is necessary to initialize Sentry **before you import any package that may be instrumented by us**. +You need to require or import the `instrument.js` file before importing any other modules in your application. This is +necessary to ensure that Sentry can automatically instrument all modules in your application: + +```js +// Import this first! +import './instrument'; + +// Now import other modules +import http from 'http'; -[More information on how to set up Sentry for Node in v8.](https://github.com/getsentry/sentry-javascript/blob/develop/docs/v8-node.md) +// Your application code goes here +``` ### ESM Support -Due to the way OpenTelemetry handles instrumentation, this only works out of the box for CommonJS (`require`) -applications. +When running your application in ESM mode, you should use the Node.js +[`--import`](https://nodejs.org/api/cli.html#--importmodule) command line option to ensure that Sentry is loaded before +the application code is evaluated. -There is experimental support for running OpenTelemetry with ESM (`"type": "module"`): +Adjust the Node.js call for your application to use the `--import` parameter and point it at `instrument.js`, which +contains your `Sentry.init`() code: ```bash -node --experimental-loader=@opentelemetry/instrumentation/hook.mjs ./app.js +# Note: This is only available for Node v18.19.0 onwards. +node --import ./instrument.mjs app.mjs ``` -You'll need to install `@opentelemetry/instrumentation` in your app to ensure this works. +If it is not possible for you to pass the `--import` flag to the Node.js binary, you can alternatively use the +`NODE_OPTIONS` environment variable as follows: -See -[OpenTelemetry Instrumentation Docs](https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-instrumentation#instrumentation-for-es-modules-in-nodejs-experimental) -for details on this - but note that this is a) experimental, and b) does not work with all integrations. +```bash +NODE_OPTIONS="--import ./instrument.mjs" npm run start +``` ## Links diff --git a/packages/node/src/integrations/http.ts b/packages/node/src/integrations/http.ts index 418fa8aa7853..632b6023e7a3 100644 --- a/packages/node/src/integrations/http.ts +++ b/packages/node/src/integrations/http.ts @@ -34,14 +34,20 @@ interface HttpOptions { /** * Do not capture spans or breadcrumbs for outgoing HTTP requests to URLs where the given callback returns `true`. * This controls both span & breadcrumb creation - spans will be non recording if tracing is disabled. + * + * The `url` param contains the entire URL, including query string (if any), protocol, host, etc. of the outgoing request. + * For example: `'https://someService.com/users/details?id=123'` */ ignoreOutgoingRequests?: (url: string) => boolean; /** * Do not capture spans or breadcrumbs for incoming HTTP requests to URLs where the given callback returns `true`. * This controls both span & breadcrumb creation - spans will be non recording if tracing is disabled. + * + * The `urlPath` param consists of the URL path and query string (if any) of the incoming request. + * For example: `'/users/details?id=123'` */ - ignoreIncomingRequests?: (url: string) => boolean; + ignoreIncomingRequests?: (urlPath: string) => boolean; /** * Additional instrumentation options that are passed to the underlying HttpInstrumentation. @@ -103,7 +109,9 @@ export const instrumentHttp = Object.assign( }, ignoreIncomingRequestHook: request => { - const url = getRequestUrl(request); + // request.url is the only property that holds any information about the url + // it only consists of the URL path and query string (if any) + const urlPath = request.url; const method = request.method?.toUpperCase(); // We do not capture OPTIONS/HEAD requests as transactions @@ -112,7 +120,7 @@ export const instrumentHttp = Object.assign( } const _ignoreIncomingRequests = _httpOptions.ignoreIncomingRequests; - if (_ignoreIncomingRequests && _ignoreIncomingRequests(url)) { + if (urlPath && _ignoreIncomingRequests && _ignoreIncomingRequests(urlPath)) { return true; } diff --git a/packages/node/src/sdk/index.ts b/packages/node/src/sdk/index.ts index 2ac62a2b5b91..7dd145854993 100644 --- a/packages/node/src/sdk/index.ts +++ b/packages/node/src/sdk/index.ts @@ -92,6 +92,7 @@ function shouldAddPerformanceIntegrations(options: Options): boolean { } // We want to ensure `tracesSampleRate` is not just undefined/null here + // eslint-disable-next-line deprecation/deprecation return options.enableTracing || options.tracesSampleRate != null || 'tracesSampler' in options; } @@ -131,7 +132,7 @@ function _init( } if (!isCjs() && options.registerEsmLoaderHooks !== false) { - maybeInitializeEsmLoader(); + maybeInitializeEsmLoader(options.registerEsmLoaderHooks === true ? undefined : options.registerEsmLoaderHooks); } setOpenTelemetryContextAsyncContextStrategy(); diff --git a/packages/node/src/sdk/initOtel.ts b/packages/node/src/sdk/initOtel.ts index 47c8879ae3e9..947486ba26cb 100644 --- a/packages/node/src/sdk/initOtel.ts +++ b/packages/node/src/sdk/initOtel.ts @@ -13,6 +13,7 @@ import { GLOBAL_OBJ, consoleSandbox, logger } from '@sentry/utils'; import { getOpenTelemetryInstrumentationToPreload } from '../integrations/tracing'; import { SentryContextManager } from '../otel/contextManager'; +import type { EsmLoaderHookOptions } from '../types'; import { isCjs } from '../utils/commonjs'; import type { NodeClient } from './client'; @@ -31,7 +32,7 @@ export function initOpenTelemetry(client: NodeClient): void { } /** Initialize the ESM loader. */ -export function maybeInitializeEsmLoader(): void { +export function maybeInitializeEsmLoader(esmHookConfig?: EsmLoaderHookOptions): void { const [nodeMajor = 0, nodeMinor = 0] = process.versions.node.split('.').map(Number); // Register hook was added in v20.6.0 and v18.19.0 @@ -43,7 +44,7 @@ export function maybeInitializeEsmLoader(): void { if (!GLOBAL_OBJ._sentryEsmLoaderHookRegistered && importMetaUrl) { try { // @ts-expect-error register is available in these versions - moduleModule.register('@opentelemetry/instrumentation/hook.mjs', importMetaUrl); + moduleModule.register('import-in-the-middle/hook.mjs', importMetaUrl, { data: esmHookConfig }); GLOBAL_OBJ._sentryEsmLoaderHookRegistered = true; } catch (error) { logger.warn('Failed to register ESM hook', error); diff --git a/packages/node/src/types.ts b/packages/node/src/types.ts index 882114a013f9..9cf3047e6c0a 100644 --- a/packages/node/src/types.ts +++ b/packages/node/src/types.ts @@ -4,6 +4,11 @@ import type { ClientOptions, Options, SamplingContext, Scope, Span, TracePropaga import type { NodeTransportOptions } from './transports'; +export interface EsmLoaderHookOptions { + include?: string[]; + exclude?: string[]; +} + export interface BaseNodeOptions { /** * List of strings/regex controlling to which outgoing requests @@ -87,13 +92,22 @@ export interface BaseNodeOptions { /** * Whether to register ESM loader hooks to automatically instrument libraries. - * This is necessary to auto instrument libraries that are loaded via ESM imports, but might it can cause issues + * This is necessary to auto instrument libraries that are loaded via ESM imports, but it can cause issues * with certain libraries. If you run into problems running your app with this enabled, * please raise an issue in https://github.com/getsentry/sentry-javascript. * + * You can optionally exclude specific modules or only include specific modules from being instrumented by providing + * an object with `include` or `exclude` properties. + * + * ```js + * registerEsmLoaderHooks: { + * exclude: ['openai'], + * } + * ``` + * * Defaults to `true`. */ - registerEsmLoaderHooks?: boolean; + registerEsmLoaderHooks?: boolean | EsmLoaderHookOptions; /** Callback that is executed when a fatal global error occurs. */ onFatalError?(this: void, error: Error): void; diff --git a/packages/node/test/integration/scope.test.ts b/packages/node/test/integration/scope.test.ts index 036ab4741fa8..1a7a899ab423 100644 --- a/packages/node/test/integration/scope.test.ts +++ b/packages/node/test/integration/scope.test.ts @@ -100,7 +100,6 @@ describe('Integration | Scope', () => { contexts: expect.objectContaining({ trace: { data: { - 'otel.kind': 'INTERNAL', 'sentry.origin': 'manual', 'sentry.source': 'custom', 'sentry.sample_rate': 1, diff --git a/packages/node/test/integration/transactions.test.ts b/packages/node/test/integration/transactions.test.ts index e3c9203ddaf5..048496f363b4 100644 --- a/packages/node/test/integration/transactions.test.ts +++ b/packages/node/test/integration/transactions.test.ts @@ -87,7 +87,6 @@ describe('Integration | Transactions', () => { expect(transaction.contexts?.trace).toEqual({ data: { - 'otel.kind': 'INTERNAL', 'sentry.op': 'test op', 'sentry.origin': 'auto.test', 'sentry.source': 'task', @@ -133,7 +132,6 @@ describe('Integration | Transactions', () => { expect(spans).toEqual([ { data: { - 'otel.kind': 'INTERNAL', 'sentry.origin': 'manual', }, description: 'inner span 1', @@ -147,7 +145,6 @@ describe('Integration | Transactions', () => { }, { data: { - 'otel.kind': 'INTERNAL', 'test.inner': 'test value', 'sentry.origin': 'manual', }, @@ -241,7 +238,6 @@ describe('Integration | Transactions', () => { contexts: expect.objectContaining({ trace: { data: { - 'otel.kind': 'INTERNAL', 'sentry.op': 'test op', 'sentry.origin': 'auto.test', 'sentry.source': 'task', @@ -280,7 +276,6 @@ describe('Integration | Transactions', () => { contexts: expect.objectContaining({ trace: { data: { - 'otel.kind': 'INTERNAL', 'sentry.op': 'test op b', 'sentry.origin': 'manual', 'sentry.source': 'custom', @@ -386,7 +381,6 @@ describe('Integration | Transactions', () => { contexts: expect.objectContaining({ trace: { data: { - 'otel.kind': 'INTERNAL', 'sentry.origin': 'manual', 'sentry.source': 'custom', 'test.outer': 'test value', @@ -422,7 +416,6 @@ describe('Integration | Transactions', () => { contexts: expect.objectContaining({ trace: { data: { - 'otel.kind': 'INTERNAL', 'sentry.origin': 'manual', 'sentry.source': 'custom', 'test.outer': 'test value b', @@ -495,7 +488,6 @@ describe('Integration | Transactions', () => { contexts: expect.objectContaining({ trace: { data: { - 'otel.kind': 'INTERNAL', 'sentry.op': 'test op', 'sentry.origin': 'auto.test', 'sentry.source': 'task', @@ -532,7 +524,6 @@ describe('Integration | Transactions', () => { expect(spans).toEqual([ { data: { - 'otel.kind': 'INTERNAL', 'sentry.origin': 'manual', }, description: 'inner span 1', @@ -546,7 +537,6 @@ describe('Integration | Transactions', () => { }, { data: { - 'otel.kind': 'INTERNAL', 'sentry.origin': 'manual', }, description: 'inner span 2', diff --git a/packages/nuxt/README.md b/packages/nuxt/README.md index 163d92836897..c75fa334b8a5 100644 --- a/packages/nuxt/README.md +++ b/packages/nuxt/README.md @@ -28,18 +28,30 @@ The minimum supported version of Nuxt is `3.0.0`. This package is a wrapper around `@sentry/node` for the server and `@sentry/vue` for the client side, with added functionality related to Nuxt. -What is working: +**What is working:** - Error Reporting + - Vue + - Node + - Nitro -What is partly working: +**What is partly working:** - Tracing by setting `tracesSampleRate` + - UI (Vue) traces + - HTTP (Node) traces -What is not yet(!) included: +**What is not yet(!) included:** - Source Maps -- Connected Traces +- Nuxt-specific traces and connecting frontend & backend traces + +**Known Issues:** + +- When adding `sentry.server.config.(ts/js)`, you get this error: "Failed to register ESM hook", but the application + will still work +- When initializing Sentry on the server with `instrument.server.(js|ts)`, you get an `'import-in-the-middle'` error, + and the application won't work ## Automatic Setup @@ -96,10 +108,38 @@ Add a `sentry.server.config.(js|ts)` file to the root of your project: import * as Sentry from '@sentry/nuxt'; Sentry.init({ - dsn: env.DSN, + dsn: process.env.DSN, }); ``` +**Alternative Setup (ESM-compatible)** + +This setup makes sure Sentry is imported on the server before any other imports. As of now, this however leads to an +import-in-the-middle error ([related reproduction](https://github.com/getsentry/sentry-javascript-examples/pull/38)). + +Add an `instrument.server.mjs` file to your `public` folder: + +```javascript +import * as Sentry from '@sentry/nuxt'; + +// Only run `init` when DSN is available +if (process.env.SENTRY_DSN) { + Sentry.init({ + dsn: process.env.DSN, + }); +} +``` + +Add an import flag to the node options, so the file loads before any other imports: + +```json +{ + "scripts": { + "preview": "NODE_OPTIONS='--import ./public/instrument.server.mjs' nuxt preview" + } +} +``` + ### 5. Vite Setup todo: add vite setup diff --git a/packages/opentelemetry/src/spanExporter.ts b/packages/opentelemetry/src/spanExporter.ts index 3064de5818aa..5714a3d93970 100644 --- a/packages/opentelemetry/src/spanExporter.ts +++ b/packages/opentelemetry/src/spanExporter.ts @@ -352,9 +352,11 @@ function removeSentryAttributes(data: Record): Record { const attributes = span.attributes; - const data: Record = { - 'otel.kind': SpanKind[span.kind], - }; + const data: Record = {}; + + if (span.kind !== SpanKind.INTERNAL) { + data['otel.kind'] = SpanKind[span.kind]; + } if (attributes[SEMATTRS_HTTP_STATUS_CODE]) { const statusCode = attributes[SEMATTRS_HTTP_STATUS_CODE] as string; diff --git a/packages/opentelemetry/src/trace.ts b/packages/opentelemetry/src/trace.ts index 5ea5381a2db3..6f9fe5dad6d1 100644 --- a/packages/opentelemetry/src/trace.ts +++ b/packages/opentelemetry/src/trace.ts @@ -286,8 +286,8 @@ export function continueTrace(options: Parameters[0 }); } -function getActiveSpanWrapper(parentSpan?: Span | SentrySpan): (callback: () => T) => T { - return parentSpan +function getActiveSpanWrapper(parentSpan: Span | SentrySpan | undefined | null): (callback: () => T) => T { + return parentSpan !== undefined ? (callback: () => T) => { // We cast this, because the OTEL Span has a few more methods than our Span interface // TODO: Add these missing methods to the Span interface diff --git a/packages/opentelemetry/test/integration/scope.test.ts b/packages/opentelemetry/test/integration/scope.test.ts index d4cbdfa8f411..c2e3dcc86701 100644 --- a/packages/opentelemetry/test/integration/scope.test.ts +++ b/packages/opentelemetry/test/integration/scope.test.ts @@ -107,7 +107,6 @@ describe('Integration | Scope', () => { contexts: expect.objectContaining({ trace: { data: { - 'otel.kind': 'INTERNAL', 'sentry.origin': 'manual', 'sentry.source': 'custom', 'sentry.sample_rate': 1, diff --git a/packages/opentelemetry/test/integration/transactions.test.ts b/packages/opentelemetry/test/integration/transactions.test.ts index 13f8c91af7b7..fe03fc8a1030 100644 --- a/packages/opentelemetry/test/integration/transactions.test.ts +++ b/packages/opentelemetry/test/integration/transactions.test.ts @@ -102,7 +102,6 @@ describe('Integration | Transactions', () => { expect(transaction.contexts?.trace).toEqual({ data: { - 'otel.kind': 'INTERNAL', 'sentry.op': 'test op', 'sentry.origin': 'auto.test', 'sentry.source': 'task', @@ -148,7 +147,6 @@ describe('Integration | Transactions', () => { expect(spans).toEqual([ { data: { - 'otel.kind': 'INTERNAL', 'sentry.origin': 'manual', }, description: 'inner span 1', @@ -162,7 +160,6 @@ describe('Integration | Transactions', () => { }, { data: { - 'otel.kind': 'INTERNAL', 'test.inner': 'test value', 'sentry.origin': 'manual', }, @@ -256,7 +253,6 @@ describe('Integration | Transactions', () => { contexts: expect.objectContaining({ trace: { data: { - 'otel.kind': 'INTERNAL', 'sentry.op': 'test op', 'sentry.origin': 'auto.test', 'sentry.source': 'task', @@ -295,7 +291,6 @@ describe('Integration | Transactions', () => { contexts: expect.objectContaining({ trace: { data: { - 'otel.kind': 'INTERNAL', 'sentry.op': 'test op b', 'sentry.origin': 'manual', 'sentry.source': 'custom', @@ -376,7 +371,6 @@ describe('Integration | Transactions', () => { contexts: expect.objectContaining({ trace: { data: { - 'otel.kind': 'INTERNAL', 'sentry.op': 'test op', 'sentry.origin': 'auto.test', 'sentry.source': 'task', @@ -413,7 +407,6 @@ describe('Integration | Transactions', () => { expect(spans).toEqual([ { data: { - 'otel.kind': 'INTERNAL', 'sentry.origin': 'manual', }, description: 'inner span 1', @@ -427,7 +420,6 @@ describe('Integration | Transactions', () => { }, { data: { - 'otel.kind': 'INTERNAL', 'sentry.origin': 'manual', }, description: 'inner span 2', diff --git a/packages/opentelemetry/test/trace.test.ts b/packages/opentelemetry/test/trace.test.ts index 6fd4ada4dc46..2332fd1ced05 100644 --- a/packages/opentelemetry/test/trace.test.ts +++ b/packages/opentelemetry/test/trace.test.ts @@ -325,6 +325,16 @@ describe('trace', () => { expect(getActiveSpan()).toBe(undefined); }); + it('allows to pass parentSpan=null', () => { + startSpan({ name: 'GET users/[id' }, () => { + startSpan({ name: 'child', parentSpan: null }, span => { + // Due to the way we propagate the scope in OTEL, + // the parent_span_id is not actually undefined here, but comes from the propagation context + expect(spanToJSON(span).parent_span_id).toBe(getCurrentScope().getPropagationContext().spanId); + }); + }); + }); + it('allows to force a transaction with forceTransaction=true', async () => { const client = getClient()!; const transactionEvents: Event[] = []; @@ -379,7 +389,6 @@ describe('trace', () => { 'sentry.source': 'custom', 'sentry.sample_rate': 1, 'sentry.origin': 'manual', - 'otel.kind': 'INTERNAL', }, span_id: expect.any(String), trace_id: expect.any(String), @@ -403,7 +412,6 @@ describe('trace', () => { data: { 'sentry.source': 'custom', 'sentry.origin': 'manual', - 'otel.kind': 'INTERNAL', 'sentry.sample_rate': 1, }, parent_span_id: innerParentSpanId, @@ -579,6 +587,17 @@ describe('trace', () => { expect(getActiveSpan()).toBe(undefined); }); + it('allows to pass parentSpan=null', () => { + startSpan({ name: 'outer' }, () => { + const span = startInactiveSpan({ name: 'test span', parentSpan: null }); + + // Due to the way we propagate the scope in OTEL, + // the parent_span_id is not actually undefined here, but comes from the propagation context + expect(spanToJSON(span).parent_span_id).toBe(getCurrentScope().getPropagationContext().spanId); + span.end(); + }); + }); + it('allows to force a transaction with forceTransaction=true', async () => { const client = getClient()!; const transactionEvents: Event[] = []; @@ -630,7 +649,6 @@ describe('trace', () => { 'sentry.source': 'custom', 'sentry.sample_rate': 1, 'sentry.origin': 'manual', - 'otel.kind': 'INTERNAL', }, span_id: expect.any(String), trace_id: expect.any(String), @@ -654,7 +672,6 @@ describe('trace', () => { data: { 'sentry.source': 'custom', 'sentry.origin': 'manual', - 'otel.kind': 'INTERNAL', 'sentry.sample_rate': 1, }, parent_span_id: innerParentSpanId, @@ -860,6 +877,17 @@ describe('trace', () => { expect(getActiveSpan()).toBe(undefined); }); + it('allows to pass parentSpan=null', () => { + startSpan({ name: 'outer' }, () => { + startSpanManual({ name: 'GET users/[id]', parentSpan: null }, span => { + // Due to the way we propagate the scope in OTEL, + // the parent_span_id is not actually undefined here, but comes from the propagation context + expect(spanToJSON(span).parent_span_id).toBe(getCurrentScope().getPropagationContext().spanId); + span.end(); + }); + }); + }); + it('allows to force a transaction with forceTransaction=true', async () => { const client = getClient()!; const transactionEvents: Event[] = []; @@ -918,7 +946,6 @@ describe('trace', () => { 'sentry.source': 'custom', 'sentry.sample_rate': 1, 'sentry.origin': 'manual', - 'otel.kind': 'INTERNAL', }, span_id: expect.any(String), trace_id: expect.any(String), @@ -942,7 +969,6 @@ describe('trace', () => { data: { 'sentry.source': 'custom', 'sentry.origin': 'manual', - 'otel.kind': 'INTERNAL', 'sentry.sample_rate': 1, }, parent_span_id: innerParentSpanId, diff --git a/packages/profiling-node/README.md b/packages/profiling-node/README.md index 7203752643ed..4357e23bb194 100644 --- a/packages/profiling-node/README.md +++ b/packages/profiling-node/README.md @@ -56,6 +56,8 @@ there is a fairly good chance this will work out of the box. The required packag **Windows:** If you are building on windows, you may need to install windows-build-tools +**_Python:_** Python 3.12 is not supported yet so you will need a version of python that is lower than 3.12 + ```bash # using yarn package manager @@ -64,6 +66,22 @@ yarn global add windows-build-tools npm i -g windows-build-tools ``` +After you have installed the toolchain, you should be able to build the binaries from source + +```bash +# configure node-gyp using yarn +yarn build:bindings:configure +# or using npm +npm run build:bindings:configure + +# compile the binaries using yarn +yarn build:bindings +# or using npm +npm run build:bindings +``` + +After the binaries are built, you should see them inside the profiling-node/lib folder. + ### Prebuilt binaries We currently ship prebuilt binaries for a few of the most common platforms and node versions (v16-22). diff --git a/packages/react/src/errorboundary.tsx b/packages/react/src/errorboundary.tsx index e12ca9f44d79..abd532c29a53 100644 --- a/packages/react/src/errorboundary.tsx +++ b/packages/react/src/errorboundary.tsx @@ -77,6 +77,7 @@ class ErrorBoundary extends React.Component void; public constructor(props: ErrorBoundaryProps) { super(props); @@ -87,7 +88,7 @@ class ErrorBoundary extends React.Component { + this._cleanupHook = client.on('afterSendEvent', event => { if (!event.type && this._lastEventId && event.event_id === this._lastEventId) { showReportDialog({ ...props.dialogOptions, eventId: this._lastEventId }); } @@ -137,6 +138,11 @@ class ErrorBoundary extends React.Component void = () => { diff --git a/packages/react/tsconfig.json b/packages/react/tsconfig.json index f074f990a911..67260f799e86 100644 --- a/packages/react/tsconfig.json +++ b/packages/react/tsconfig.json @@ -4,6 +4,7 @@ "include": ["src/**/*"], "compilerOptions": { + "lib": ["DOM", "ES2018"], // package-specific options "esModuleInterop": true, "jsx": "react" diff --git a/packages/remix/src/client/browserTracingIntegration.ts b/packages/remix/src/client/browserTracingIntegration.ts index c0eb1a97148d..ce7387904e57 100644 --- a/packages/remix/src/client/browserTracingIntegration.ts +++ b/packages/remix/src/client/browserTracingIntegration.ts @@ -7,19 +7,13 @@ import type { RemixBrowserTracingIntegrationOptions } from './performance'; * This integration will create pageload and navigation spans. */ export function browserTracingIntegration(options: RemixBrowserTracingIntegrationOptions): Integration { - if (options.instrumentPageLoad === undefined) { - options.instrumentPageLoad = true; - } - - if (options.instrumentNavigation === undefined) { - options.instrumentNavigation = true; - } + const { instrumentPageLoad = true, instrumentNavigation = true, useEffect, useLocation, useMatches } = options; setGlobals({ - useEffect: options.useEffect, - useLocation: options.useLocation, - useMatches: options.useMatches, - instrumentNavigation: options.instrumentNavigation, + useEffect, + useLocation, + useMatches, + instrumentNavigation, }); const browserTracingIntegrationInstance = originalBrowserTracingIntegration({ @@ -33,8 +27,8 @@ export function browserTracingIntegration(options: RemixBrowserTracingIntegratio afterAllSetup(client) { browserTracingIntegrationInstance.afterAllSetup(client); - if (options.instrumentPageLoad) { - startPageloadSpan(); + if (instrumentPageLoad) { + startPageloadSpan(client); } }, }; diff --git a/packages/remix/src/client/performance.tsx b/packages/remix/src/client/performance.tsx index f8432aa0b613..cbb886a6dc8b 100644 --- a/packages/remix/src/client/performance.tsx +++ b/packages/remix/src/client/performance.tsx @@ -14,7 +14,7 @@ import { startBrowserTracingPageLoadSpan, withErrorBoundary, } from '@sentry/react'; -import type { StartSpanOptions } from '@sentry/types'; +import type { Client, StartSpanOptions } from '@sentry/types'; import { isNodeEnv, logger } from '@sentry/utils'; import * as React from 'react'; @@ -67,7 +67,7 @@ function isRemixV2(remixVersion: number | undefined): boolean { return remixVersion === 2 || getFutureFlagsBrowser()?.v2_errorBoundary || false; } -export function startPageloadSpan(): void { +export function startPageloadSpan(client: Client): void { const initPathName = getInitPathName(); if (!initPathName) { @@ -83,12 +83,6 @@ export function startPageloadSpan(): void { }, }; - const client = getClient(); - - if (!client) { - return; - } - startBrowserTracingPageLoadSpan(client, spanContext); } diff --git a/packages/remix/test/integration/test/server/instrumentation-otel/action.test.ts b/packages/remix/test/integration/test/server/instrumentation-otel/action.test.ts index a784cd3b8d9c..f883c4bfeee5 100644 --- a/packages/remix/test/integration/test/server/instrumentation-otel/action.test.ts +++ b/packages/remix/test/integration/test/server/instrumentation-otel/action.test.ts @@ -22,7 +22,6 @@ describe('Remix API Actions', () => { data: { 'code.function': 'action', 'sentry.op': 'action.remix', - 'otel.kind': 'INTERNAL', 'match.route.id': `routes/action-json-response${useV2 ? '.' : '/'}$id`, 'match.params.id': '123123', }, @@ -31,7 +30,6 @@ describe('Remix API Actions', () => { data: { 'code.function': 'loader', 'sentry.op': 'loader.remix', - 'otel.kind': 'INTERNAL', 'match.route.id': `routes/action-json-response${useV2 ? '.' : '/'}$id`, 'match.params.id': '123123', }, @@ -40,7 +38,6 @@ describe('Remix API Actions', () => { data: { 'code.function': 'loader', 'sentry.op': 'loader.remix', - 'otel.kind': 'INTERNAL', 'match.route.id': 'root', 'match.params.id': '123123', }, diff --git a/packages/remix/test/integration/test/server/instrumentation-otel/loader.test.ts b/packages/remix/test/integration/test/server/instrumentation-otel/loader.test.ts index 62e0bf78ac10..49b0fa7665fd 100644 --- a/packages/remix/test/integration/test/server/instrumentation-otel/loader.test.ts +++ b/packages/remix/test/integration/test/server/instrumentation-otel/loader.test.ts @@ -102,7 +102,6 @@ describe('Remix API Loaders', () => { { data: { 'code.function': 'loader', - 'otel.kind': 'INTERNAL', 'sentry.op': 'loader.remix', }, origin: 'manual', @@ -110,7 +109,6 @@ describe('Remix API Loaders', () => { { data: { 'code.function': 'loader', - 'otel.kind': 'INTERNAL', 'sentry.op': 'loader.remix', }, origin: 'manual', @@ -244,7 +242,6 @@ describe('Remix API Loaders', () => { data: { 'code.function': 'loader', 'sentry.op': 'loader.remix', - 'otel.kind': 'INTERNAL', 'match.route.id': `routes/loader-defer-response${useV2 ? '.' : '/'}$id`, }, }, @@ -252,7 +249,6 @@ describe('Remix API Loaders', () => { data: { 'code.function': 'loader', 'sentry.op': 'loader.remix', - 'otel.kind': 'INTERNAL', 'match.route.id': 'root', }, }, diff --git a/packages/remix/tsconfig.test.json b/packages/remix/tsconfig.test.json index ffcc2b26016c..3d6564a39ff5 100644 --- a/packages/remix/tsconfig.test.json +++ b/packages/remix/tsconfig.test.json @@ -4,6 +4,7 @@ "include": ["test/**/*", "vitest.config.ts"], "compilerOptions": { + "lib": ["DOM", "ES2018"], "types": ["node", "jest"], "esModuleInterop": true } diff --git a/packages/replay-canvas/package.json b/packages/replay-canvas/package.json index 9d0bdb6eac50..2af80dec1c27 100644 --- a/packages/replay-canvas/package.json +++ b/packages/replay-canvas/package.json @@ -66,7 +66,7 @@ "homepage": "https://docs.sentry.io/platforms/javascript/session-replay/", "devDependencies": { "@babel/core": "^7.17.5", - "@sentry-internal/rrweb": "2.15.0" + "@sentry-internal/rrweb": "2.25.0" }, "dependencies": { "@sentry-internal/replay": "8.17.0", diff --git a/packages/replay-canvas/tsconfig.json b/packages/replay-canvas/tsconfig.json index f8f54556da93..cd1b8207ea06 100644 --- a/packages/replay-canvas/tsconfig.json +++ b/packages/replay-canvas/tsconfig.json @@ -1,6 +1,7 @@ { "extends": "../../tsconfig.json", "compilerOptions": { + "lib": ["DOM", "ES2018"], "module": "esnext" }, "include": ["src/**/*.ts"] diff --git a/packages/replay-canvas/tsconfig.test.json b/packages/replay-canvas/tsconfig.test.json index ad87caa06c48..3995d3e18e59 100644 --- a/packages/replay-canvas/tsconfig.test.json +++ b/packages/replay-canvas/tsconfig.test.json @@ -4,6 +4,7 @@ "include": ["test/**/*.ts", "jest.config.ts", "jest.setup.ts"], "compilerOptions": { + "lib": ["DOM", "ES2018"], "types": ["node", "jest"], "esModuleInterop": true, "allowJs": true, diff --git a/packages/replay-internal/package.json b/packages/replay-internal/package.json index 1ac8466f0e90..bc05dfb4ea80 100644 --- a/packages/replay-internal/package.json +++ b/packages/replay-internal/package.json @@ -69,8 +69,8 @@ "devDependencies": { "@babel/core": "^7.17.5", "@sentry-internal/replay-worker": "8.17.0", - "@sentry-internal/rrweb": "2.15.0", - "@sentry-internal/rrweb-snapshot": "2.15.0", + "@sentry-internal/rrweb": "2.25.0", + "@sentry-internal/rrweb-snapshot": "2.25.0", "fflate": "^0.8.1", "jest-matcher-utils": "^29.0.0", "jsdom-worker": "^0.2.1" diff --git a/packages/replay-internal/src/types/rrweb.ts b/packages/replay-internal/src/types/rrweb.ts index a490a6e46c1b..cb194e193a5d 100644 --- a/packages/replay-internal/src/types/rrweb.ts +++ b/packages/replay-internal/src/types/rrweb.ts @@ -31,7 +31,7 @@ export type ReplayEventWithTime = { /** * This is a partial copy of rrweb's recording options which only contains the properties - * we specifically us in the SDK. Users can specify additional properties, hence we add the + * we specifically use in the SDK. Users can specify additional properties, hence we add the * Record union type. */ export type RrwebRecordOptions = { @@ -52,6 +52,9 @@ export interface CanvasManagerInterface { lock(): void; unlock(): void; snapshot(): void; + addWindow(win: typeof globalThis & Window): void; + addShadowRoot(shadowRoot: ShadowRoot): void; + resetShadowRoots(): void; } export interface CanvasManagerOptions { diff --git a/packages/replay-internal/tsconfig.json b/packages/replay-internal/tsconfig.json index f8f54556da93..cd1b8207ea06 100644 --- a/packages/replay-internal/tsconfig.json +++ b/packages/replay-internal/tsconfig.json @@ -1,6 +1,7 @@ { "extends": "../../tsconfig.json", "compilerOptions": { + "lib": ["DOM", "ES2018"], "module": "esnext" }, "include": ["src/**/*.ts"] diff --git a/packages/solid/src/solidrouter.ts b/packages/solid/src/solidrouter.ts index 2f343cfe9d7c..da0391dea35e 100644 --- a/packages/solid/src/solidrouter.ts +++ b/packages/solid/src/solidrouter.ts @@ -33,11 +33,18 @@ function handleNavigation(location: string): void { return; } + // The solid router integration will be used for both solid and solid start. + // To avoid increasing the api surface with internal properties, we look at + // the sdk metadata. + const metaData = client.getSdkMetadata(); + const { name } = (metaData && metaData.sdk) || {}; + const framework = name && name.includes('solidstart') ? 'solidstart' : 'solid'; + startBrowserTracingNavigationSpan(client, { name: location, attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.solid.solidrouter', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: `auto.navigation.${framework}.solidrouter`, [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', }, }); diff --git a/packages/solid/test/solidrouter.test.tsx b/packages/solid/test/solidrouter.test.tsx index 029b90794b70..44268e6716ab 100644 --- a/packages/solid/test/solidrouter.test.tsx +++ b/packages/solid/test/solidrouter.test.tsx @@ -44,6 +44,11 @@ describe('solidRouterBrowserTracingIntegration', () => { tracesSampleRate: 1, transport: () => createTransport({ recordDroppedEvent: () => undefined }, _ => Promise.resolve({})), stackParser: () => [], + _metadata: { + sdk: { + name: 'sentry.javascript.solid', + }, + }, }); } diff --git a/packages/solid/tsconfig.json b/packages/solid/tsconfig.json index b0eb9ecb6476..6d1756d12826 100644 --- a/packages/solid/tsconfig.json +++ b/packages/solid/tsconfig.json @@ -3,5 +3,7 @@ "include": ["src/**/*"], - "compilerOptions": {} + "compilerOptions": { + "lib": ["DOM", "ES2018"], + } } diff --git a/packages/solidstart/test/client/solidrouter.test.tsx b/packages/solidstart/test/client/solidrouter.test.tsx index d6b161c3a7d9..681b8d7b5ce7 100644 --- a/packages/solidstart/test/client/solidrouter.test.tsx +++ b/packages/solidstart/test/client/solidrouter.test.tsx @@ -44,6 +44,11 @@ describe('solidRouterBrowserTracingIntegration', () => { tracesSampleRate: 1, transport: () => createTransport({ recordDroppedEvent: () => undefined }, _ => Promise.resolve({})), stackParser: () => [], + _metadata: { + sdk: { + name: 'sentry.javascript.solidstart', + }, + }, }); } @@ -138,7 +143,7 @@ describe('solidRouterBrowserTracingIntegration', () => { data: expect.objectContaining({ [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.solid.solidrouter', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.solidstart.solidrouter', }), }), ); @@ -170,7 +175,7 @@ describe('solidRouterBrowserTracingIntegration', () => { data: expect.objectContaining({ [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.solid.solidrouter', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.solidstart.solidrouter', }), }), ); diff --git a/packages/svelte/tsconfig.json b/packages/svelte/tsconfig.json index b0eb9ecb6476..6d1756d12826 100644 --- a/packages/svelte/tsconfig.json +++ b/packages/svelte/tsconfig.json @@ -3,5 +3,7 @@ "include": ["src/**/*"], - "compilerOptions": {} + "compilerOptions": { + "lib": ["DOM", "ES2018"], + } } diff --git a/packages/sveltekit/package.json b/packages/sveltekit/package.json index cbf009831ef0..d2edf5487ed2 100644 --- a/packages/sveltekit/package.json +++ b/packages/sveltekit/package.json @@ -31,7 +31,13 @@ "access": "public" }, "peerDependencies": { - "@sveltejs/kit": "1.x || 2.x" + "@sveltejs/kit": "1.x || 2.x", + "vite": "*" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } }, "dependencies": { "@sentry/core": "8.17.0", diff --git a/packages/types/src/options.ts b/packages/types/src/options.ts index 8b2dbebdd574..d6c407d60bd0 100644 --- a/packages/types/src/options.ts +++ b/packages/types/src/options.ts @@ -89,9 +89,13 @@ export interface ClientOptions = { '14': { ignoredPackages: [ + '@sentry/cloudflare', '@sentry/solidstart', '@sentry/sveltekit', '@sentry/vercel-edge', @@ -40,7 +41,7 @@ const SKIP_TEST_PACKAGES: Record = { ], }, '16': { - ignoredPackages: ['@sentry/vercel-edge', '@sentry/astro'], + ignoredPackages: ['@sentry/cloudflare', '@sentry/vercel-edge', '@sentry/astro'], }, '18': { ignoredPackages: [], diff --git a/yarn.lock b/yarn.lock index a423fa79953f..7cb6273253bc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3825,6 +3825,13 @@ resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-1.4.0.tgz#0bb1292c5e279198912b6ec35649124ba8349b72" integrity sha512-gN6DgyyBxIwoCovAUFJHFWVallb0cLosayDRtNyxU3MDv/atZxSXOWQezfVKBIbgmFPxYWJObd+awvbPYXwwww== +"@cloudflare/kv-asset-handler@0.3.4": + version "0.3.4" + resolved "https://registry.yarnpkg.com/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.3.4.tgz#5cc152847c8ae4d280ec5d7f4f6ba8c976b585c3" + integrity sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q== + dependencies: + mime "^3.0.0" + "@cloudflare/kv-asset-handler@^0.3.1": version "0.3.3" resolved "https://registry.yarnpkg.com/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.3.3.tgz#3c578996f3d00b60debee8178c41600f3b21bc0b" @@ -3832,6 +3839,36 @@ dependencies: mime "^3.0.0" +"@cloudflare/workerd-darwin-64@1.20240701.0": + version "1.20240701.0" + resolved "https://registry.yarnpkg.com/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20240701.0.tgz#064d8ded54443ac8d4181bdb2d93113f7fb63c81" + integrity sha512-XAZa4ZP+qyTn6JQQACCPH09hGZXP2lTnWKkmg5mPwT8EyRzCKLkczAf98vPP5bq7JZD/zORdFWRY0dOTap8zTQ== + +"@cloudflare/workerd-darwin-arm64@1.20240701.0": + version "1.20240701.0" + resolved "https://registry.yarnpkg.com/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20240701.0.tgz#042e49592bf9ef9e74d7f85c885cc3bda356c96c" + integrity sha512-w80ZVAgfH4UwTz7fXZtk7KmS2FzlXniuQm4ku4+cIgRTilBAuKqjpOjwUCbx5g13Gqcm9NuiHce+IDGtobRTIQ== + +"@cloudflare/workerd-linux-64@1.20240701.0": + version "1.20240701.0" + resolved "https://registry.yarnpkg.com/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20240701.0.tgz#5ff73dcd0b0615877baa0ae4fa057ea244e326f3" + integrity sha512-UWLr/Anxwwe/25nGv451MNd2jhREmPt/ws17DJJqTLAx6JxwGWA15MeitAIzl0dbxRFAJa+0+R8ag2WR3F/D6g== + +"@cloudflare/workerd-linux-arm64@1.20240701.0": + version "1.20240701.0" + resolved "https://registry.yarnpkg.com/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20240701.0.tgz#b0e5e5bf00fb41ac94f93f7dea7ffd306f468685" + integrity sha512-3kCnF9kYgov1ggpuWbgpXt4stPOIYtVmPCa7MO2xhhA0TWP6JDUHRUOsnmIgKrvDjXuXqlK16cdg3v+EWsaPJg== + +"@cloudflare/workerd-windows-64@1.20240701.0": + version "1.20240701.0" + resolved "https://registry.yarnpkg.com/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20240701.0.tgz#710583329e7fef26092fdccf021e669434cc6acb" + integrity sha512-6IPGITRAeS67j3BH1rN4iwYWDt47SqJG7KlZJ5bB4UaNAia4mvMBSy/p2p4vA89bbXoDRjMtEvRu7Robu6O7hQ== + +"@cloudflare/workers-types@^4.20240620.0": + version "4.20240620.0" + resolved "https://registry.yarnpkg.com/@cloudflare/workers-types/-/workers-types-4.20240620.0.tgz#1e996c0b81a1dab392f0292bea036fd7bb3b73f3" + integrity sha512-CQD8YS6evRob7LChvIX3gE3zYo0KVgaLDOu1SwNP1BVIS2Sa0b+FC8S1e1hhrNN8/E4chYlVN+FDAgA4KRDUEQ== + "@cnakazawa/watch@^1.0.3": version "1.0.4" resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" @@ -3845,7 +3882,7 @@ resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== -"@cspotcode/source-map-support@^0.8.0": +"@cspotcode/source-map-support@0.8.1", "@cspotcode/source-map-support@^0.8.0": version "0.8.1" resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== @@ -4102,6 +4139,19 @@ broccoli-funnel "^3.0.5" ember-cli-babel "^8.2.0" +"@esbuild-plugins/node-globals-polyfill@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@esbuild-plugins/node-globals-polyfill/-/node-globals-polyfill-0.2.3.tgz#0e4497a2b53c9e9485e149bc92ddb228438d6bcf" + integrity sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw== + +"@esbuild-plugins/node-modules-polyfill@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@esbuild-plugins/node-modules-polyfill/-/node-modules-polyfill-0.2.2.tgz#cefa3dc0bd1c16277a8338b52833420c94987327" + integrity sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA== + dependencies: + escape-string-regexp "^4.0.0" + rollup-plugin-node-polyfills "^0.2.1" + "@esbuild/aix-ppc64@0.20.0": version "0.20.0" resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.0.tgz#509621cca4e67caf0d18561a0c56f8b70237472f" @@ -4117,6 +4167,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f" integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ== +"@esbuild/android-arm64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz#bafb75234a5d3d1b690e7c2956a599345e84a2fd" + integrity sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA== + "@esbuild/android-arm64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz#984b4f9c8d0377443cc2dfcef266d02244593622" @@ -4152,6 +4207,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.15.18.tgz#266d40b8fdcf87962df8af05b76219bc786b4f80" integrity sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw== +"@esbuild/android-arm@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.17.19.tgz#5898f7832c2298bc7d0ab53701c57beb74d78b4d" + integrity sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A== + "@esbuild/android-arm@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.18.20.tgz#fedb265bc3a589c84cc11f810804f234947c3682" @@ -4182,6 +4242,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28" integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg== +"@esbuild/android-x64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.17.19.tgz#658368ef92067866d95fb268719f98f363d13ae1" + integrity sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww== + "@esbuild/android-x64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.18.20.tgz#35cf419c4cfc8babe8893d296cd990e9e9f756f2" @@ -4212,6 +4277,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e" integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA== +"@esbuild/darwin-arm64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz#584c34c5991b95d4d48d333300b1a4e2ff7be276" + integrity sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg== + "@esbuild/darwin-arm64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz#08172cbeccf95fbc383399a7f39cfbddaeb0d7c1" @@ -4242,6 +4312,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a" integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ== +"@esbuild/darwin-x64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz#7751d236dfe6ce136cce343dce69f52d76b7f6cb" + integrity sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw== + "@esbuild/darwin-x64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz#d70d5790d8bf475556b67d0f8b7c5bdff053d85d" @@ -4272,6 +4347,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22" integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw== +"@esbuild/freebsd-arm64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz#cacd171665dd1d500f45c167d50c6b7e539d5fd2" + integrity sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ== + "@esbuild/freebsd-arm64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz#98755cd12707f93f210e2494d6a4b51b96977f54" @@ -4302,6 +4382,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e" integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g== +"@esbuild/freebsd-x64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz#0769456eee2a08b8d925d7c00b79e861cb3162e4" + integrity sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ== + "@esbuild/freebsd-x64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz#c1eb2bff03915f87c29cece4c1a7fa1f423b066e" @@ -4332,6 +4417,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261" integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ== +"@esbuild/linux-arm64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz#38e162ecb723862c6be1c27d6389f48960b68edb" + integrity sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg== + "@esbuild/linux-arm64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz#bad4238bd8f4fc25b5a021280c770ab5fc3a02a0" @@ -4362,6 +4452,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b" integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q== +"@esbuild/linux-arm@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz#1a2cd399c50040184a805174a6d89097d9d1559a" + integrity sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA== + "@esbuild/linux-arm@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz#3e617c61f33508a27150ee417543c8ab5acc73b0" @@ -4392,6 +4487,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9" integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA== +"@esbuild/linux-ia32@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz#e28c25266b036ce1cabca3c30155222841dc035a" + integrity sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ== + "@esbuild/linux-ia32@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz#699391cccba9aee6019b7f9892eb99219f1570a7" @@ -4432,6 +4532,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.15.5.tgz#91aef76d332cdc7c8942b600fa2307f3387e6f82" integrity sha512-UHkDFCfSGTuXq08oQltXxSZmH1TXyWsL+4QhZDWvvLl6mEJQqk3u7/wq1LjhrrAXYIllaTtRSzUXl4Olkf2J8A== +"@esbuild/linux-loong64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz#0f887b8bb3f90658d1a0117283e55dbd4c9dcf72" + integrity sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ== + "@esbuild/linux-loong64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz#e6fccb7aac178dd2ffb9860465ac89d7f23b977d" @@ -4462,6 +4567,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df" integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg== +"@esbuild/linux-mips64el@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz#f5d2a0b8047ea9a5d9f592a178ea054053a70289" + integrity sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A== + "@esbuild/linux-mips64el@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz#eeff3a937de9c2310de30622a957ad1bd9183231" @@ -4492,6 +4602,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe" integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg== +"@esbuild/linux-ppc64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz#876590e3acbd9fa7f57a2c7d86f83717dbbac8c7" + integrity sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg== + "@esbuild/linux-ppc64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz#2f7156bde20b01527993e6881435ad79ba9599fb" @@ -4522,6 +4637,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4" integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w== +"@esbuild/linux-riscv64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz#7f49373df463cd9f41dc34f9b2262d771688bf09" + integrity sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA== + "@esbuild/linux-riscv64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz#6628389f210123d8b4743045af8caa7d4ddfc7a6" @@ -4552,6 +4672,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc" integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA== +"@esbuild/linux-s390x@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz#e2afd1afcaf63afe2c7d9ceacd28ec57c77f8829" + integrity sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q== + "@esbuild/linux-s390x@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz#255e81fb289b101026131858ab99fba63dcf0071" @@ -4582,6 +4707,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de" integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A== +"@esbuild/linux-x64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz#8a0e9738b1635f0c53389e515ae83826dec22aa4" + integrity sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw== + "@esbuild/linux-x64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz#c7690b3417af318a9b6f96df3031a8865176d338" @@ -4612,6 +4742,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0" integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ== +"@esbuild/netbsd-x64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz#c29fb2453c6b7ddef9a35e2c18b37bda1ae5c462" + integrity sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q== + "@esbuild/netbsd-x64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz#30e8cd8a3dded63975e2df2438ca109601ebe0d1" @@ -4642,6 +4777,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047" integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg== +"@esbuild/openbsd-x64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz#95e75a391403cb10297280d524d66ce04c920691" + integrity sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g== + "@esbuild/openbsd-x64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz#7812af31b205055874c8082ea9cf9ab0da6217ae" @@ -4672,6 +4812,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70" integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow== +"@esbuild/sunos-x64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz#722eaf057b83c2575937d3ffe5aeb16540da7273" + integrity sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg== + "@esbuild/sunos-x64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz#d5c275c3b4e73c9b0ecd38d1ca62c020f887ab9d" @@ -4702,6 +4847,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b" integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg== +"@esbuild/win32-arm64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz#9aa9dc074399288bdcdd283443e9aeb6b9552b6f" + integrity sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag== + "@esbuild/win32-arm64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz#73bc7f5a9f8a77805f357fab97f290d0e4820ac9" @@ -4732,6 +4882,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d" integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A== +"@esbuild/win32-ia32@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz#95ad43c62ad62485e210f6299c7b2571e48d2b03" + integrity sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw== + "@esbuild/win32-ia32@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz#ec93cbf0ef1085cc12e71e0d661d20569ff42102" @@ -4762,6 +4917,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b" integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA== +"@esbuild/win32-x64@0.17.19": + version "0.17.19" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz#8cfaf2ff603e9aabb910e9c0558c26cf32744061" + integrity sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA== + "@esbuild/win32-x64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz#786c5f41f043b07afb1af37683d7c33668858f6d" @@ -7895,22 +8055,22 @@ dependencies: "@sentry-internal/rrweb-snapshot" "2.11.0" -"@sentry-internal/rrdom@2.15.0": - version "2.15.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/rrdom/-/rrdom-2.15.0.tgz#1ac070a7a00664b2c5351c8ba13979369024128a" - integrity sha512-LDy2LbmEytIuV9vKTr2dK4iMCTTFTpNW/eJ6IoapB0syYBc4yuUsbH39s/gamxcR5Y7KjkySSh0XkMnCHyV5gg== +"@sentry-internal/rrdom@2.25.0": + version "2.25.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/rrdom/-/rrdom-2.25.0.tgz#4be842f7f4efae383bbd5a9dcbbecc212d378d70" + integrity sha512-YTxGHnCdv6D2JVJ6YFezMsGOHLy7CM8x8qMaY3Yh3QTubFOjdGpcGJGITF/9Lkx+rFVCTdjL32cQu9NUgEJO8g== dependencies: - "@sentry-internal/rrweb-snapshot" "2.15.0" + "@sentry-internal/rrweb-snapshot" "2.25.0" "@sentry-internal/rrweb-snapshot@2.11.0": version "2.11.0" resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb-snapshot/-/rrweb-snapshot-2.11.0.tgz#1af79130604afea989d325465b209ac015b27c9a" integrity sha512-1nP22QlplMNooSNvTh+L30NSZ+E3UcfaJyxXSMLxUjQHTGPyM1VkndxZMmxlKhyR5X+rLbxi/+RvuAcpM43VoA== -"@sentry-internal/rrweb-snapshot@2.15.0": - version "2.15.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb-snapshot/-/rrweb-snapshot-2.15.0.tgz#04c79d3dc723ed80e4f10685d5ebc6c1b90fcf1b" - integrity sha512-g/gqzKab6lQ/YvioIXVWQTaQXrUctepqIgXP7vYvpnU+ZmxmsOVd10gQuryDCSLYt2wQiwkffYyeaP2BVqxbwQ== +"@sentry-internal/rrweb-snapshot@2.25.0": + version "2.25.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb-snapshot/-/rrweb-snapshot-2.25.0.tgz#f20bd20436edac24ed1075b47fc4773894739d97" + integrity sha512-7j90eSGFRS1YWcuo0bXPtV9oDdCQxutilyYbim/I09GA7kx4/d8OG8ryxQl6WWXW+E50x6dEpDsZXWMPkSleEg== "@sentry-internal/rrweb-types@2.11.0": version "2.11.0" @@ -7919,12 +8079,13 @@ dependencies: "@sentry-internal/rrweb-snapshot" "2.11.0" -"@sentry-internal/rrweb-types@2.15.0": - version "2.15.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb-types/-/rrweb-types-2.15.0.tgz#caeabffc227405110946447f30893aa037493b23" - integrity sha512-D3i9+G4h6gLlG/B1lkP3jc3pM84hP2d2WFGrapTBI0bJou822ERD3Wj9KBVPEkwsRM+qDZRqRMrq0PicdAqJAA== +"@sentry-internal/rrweb-types@2.25.0": + version "2.25.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb-types/-/rrweb-types-2.25.0.tgz#61662befc57ed7054a491eb35ad3deda7d66157c" + integrity sha512-sM2YdevhIRxQ/Kr89cfbNBO7/EFhycTmQT0NKg4owdKkIvuuqz1AhbRpMMdpJ4NJnos+h06VPObeXm6rcrffsw== dependencies: - "@sentry-internal/rrweb-snapshot" "2.15.0" + "@sentry-internal/rrweb-snapshot" "2.25.0" + "@types/css-font-loading-module" "0.0.7" "@sentry-internal/rrweb@2.11.0": version "2.11.0" @@ -7940,14 +8101,14 @@ fflate "^0.4.4" mitt "^3.0.0" -"@sentry-internal/rrweb@2.15.0": - version "2.15.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb/-/rrweb-2.15.0.tgz#a38dff464624c7ab421579b5ec626007e10c9da8" - integrity sha512-WO2QJJMJYVcuc8aq6j4YEzNo512FZ2Ro7/04Ip1MYhPI4BpHhn3KI7lRoHvprZeVNYWXyBtiPy7JFehuVCppdw== +"@sentry-internal/rrweb@2.25.0": + version "2.25.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/rrweb/-/rrweb-2.25.0.tgz#0148f1904f1e9549f2c2cae209fe3d3fe891d3ec" + integrity sha512-0tgBI0CFpyO3Z3dw4IjS/D6AnQypro4dquRrcZZzqnMH65Vxw3yytGDtmvE/FzHzGC0vmKFTM+sTkzFY0bo+Bg== dependencies: - "@sentry-internal/rrdom" "2.15.0" - "@sentry-internal/rrweb-snapshot" "2.15.0" - "@sentry-internal/rrweb-types" "2.15.0" + "@sentry-internal/rrdom" "2.25.0" + "@sentry-internal/rrweb-snapshot" "2.25.0" + "@sentry-internal/rrweb-types" "2.25.0" "@types/css-font-loading-module" "0.0.7" "@xstate/fsm" "^1.4.0" base64-arraybuffer "^1.0.1" @@ -9454,7 +9615,17 @@ dependencies: "@types/unist" "*" -"@types/history-4@npm:@types/history@4.7.8", "@types/history-5@npm:@types/history@4.7.8", "@types/history@*": +"@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@*": version "4.7.8" resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA== @@ -9775,7 +9946,15 @@ "@types/history" "^3" "@types/react" "*" -"@types/react-router-4@npm:@types/react-router@5.1.14", "@types/react-router-5@npm:@types/react-router@5.1.14": +"@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": version "5.1.14" resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.14.tgz#e0442f4eb4c446541ad7435d44a97f8fe6df40da" integrity sha512-LAJpqYUaCTMT2anZheoidiIymt8MuX286zoVFPM3DVb23aQBH0mAkFvzpd4LKqiolV8bBtZWT5Qp7hClCNDENw== @@ -11306,6 +11485,13 @@ acorn-walk@^8.1.1: resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== +acorn-walk@^8.2.0: + version "8.3.3" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.3.tgz#9caeac29eefaa0c41e3d4c65137de4d6f34df43e" + integrity sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw== + dependencies: + acorn "^8.11.0" + acorn@8.12.0, acorn@^8.11.0, acorn@^8.6.0: version "8.12.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.0.tgz#1627bfa2e058148036133b8d9b51a700663c294c" @@ -11336,6 +11522,11 @@ acorn@^8.2.4, acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.0, acorn@^8.7.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8" integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== +acorn@^8.8.0: + version "8.12.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" + integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== + acorn@^8.8.1, acorn@^8.8.2: version "8.8.2" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" @@ -11359,11 +11550,6 @@ adjust-sourcemap-loader@^4.0.0: loader-utils "^2.0.0" regex-parser "^2.2.11" -agent-base@5: - version "5.1.1" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c" - integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g== - agent-base@6, agent-base@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" @@ -11809,11 +11995,6 @@ args@^5.0.3: leven "2.1.0" mri "1.1.4" -argv@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/argv/-/argv-0.0.2.tgz#ecbd16f8949b157183711b1bda334f37840185ab" - integrity sha1-7L0W+JSbFXGDcRsb2jNPN4QBhas= - aria-query@5.1.3: version "5.1.3" resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.1.3.tgz#19db27cd101152773631396f7a95a3b58c22c35e" @@ -11965,6 +12146,13 @@ arrify@^2.0.0, arrify@^2.0.1: resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== +as-table@^1.0.36: + version "1.0.55" + resolved "https://registry.yarnpkg.com/as-table/-/as-table-1.0.55.tgz#dc984da3937745de902cea1d45843c01bdbbec4f" + integrity sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ== + dependencies: + printable-characters "^1.0.42" + asap@~2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" @@ -12746,6 +12934,11 @@ bl@^5.0.0: inherits "^2.0.4" readable-stream "^3.4.0" +blake3-wasm@^2.1.5: + version "2.1.5" + resolved "https://registry.yarnpkg.com/blake3-wasm/-/blake3-wasm-2.1.5.tgz#b22dbb84bc9419ed0159caa76af4b1b132e6ba52" + integrity sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g== + blank-object@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/blank-object/-/blank-object-1.0.2.tgz#f990793fbe9a8c8dd013fb3219420bec81d5f4b9" @@ -13954,6 +14147,14 @@ caniuse-lite@^1.0.30001400, caniuse-lite@^1.0.30001406, caniuse-lite@^1.0.300015 resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001614.tgz#f894b4209376a0bf923d67d9c361d96b1dfebe39" integrity sha512-jmZQ1VpmlRwHgdP1/uiKzgiAuGOfLEJsYFP4+GBou/QQ4U6IOJCB4NP1c+1p9RGLpwObcT94jA5/uO+F1vBbog== +capnp-ts@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/capnp-ts/-/capnp-ts-0.7.0.tgz#16fd8e76b667d002af8fcf4bf92bf15d1a7b54a9" + integrity sha512-XKxXAC3HVPv7r674zP0VC3RTXz+/JKhfyw94ljvF80yynK6VkTnqE3jMuN8b3dUVmmc43TjyxjW4KTsmB3c86g== + dependencies: + debug "^4.3.1" + tslib "^2.2.0" + capture-exit@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4" @@ -14410,17 +14611,6 @@ code-red@^1.0.3: estree-walker "^3.0.3" periscopic "^3.1.0" -codecov@^3.6.5: - version "3.8.1" - resolved "https://registry.yarnpkg.com/codecov/-/codecov-3.8.1.tgz#06fe026b75525ed1ce864d4a34f1010c52c51546" - integrity sha512-Qm7ltx1pzLPsliZY81jyaQ80dcNR4/JpcX0IHCIWrHBXgseySqbdbYfkdiXd7o/xmzQpGRVCKGYeTrHUpn6Dcw== - dependencies: - argv "0.0.2" - ignore-walk "3.0.3" - js-yaml "3.14.0" - teeny-request "6.0.1" - urlgrey "0.4.4" - collect-v8-coverage@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" @@ -15396,6 +15586,11 @@ dargs@^7.0.0: resolved "https://registry.yarnpkg.com/dargs/-/dargs-7.0.0.tgz#04015c41de0bcb69ec84050f3d9be0caf8d6d5cc" integrity sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg== +data-uri-to-buffer@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz#d296973d5a4897a5dbe31716d118211921f04770" + integrity sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA== + data-uri-to-buffer@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz#594b8973938c5bc2c33046535785341abc4f3636" @@ -15424,6 +15619,11 @@ date-fns@^2.29.2: resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8" integrity sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA== +date-fns@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-3.6.0.tgz#f20ca4fe94f8b754951b24240676e8618c0206bf" + integrity sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww== + dateformat@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" @@ -17511,6 +17711,34 @@ esbuild@0.15.5: esbuild-windows-64 "0.15.5" esbuild-windows-arm64 "0.15.5" +esbuild@0.17.19: + version "0.17.19" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.17.19.tgz#087a727e98299f0462a3d0bcdd9cd7ff100bd955" + integrity sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw== + optionalDependencies: + "@esbuild/android-arm" "0.17.19" + "@esbuild/android-arm64" "0.17.19" + "@esbuild/android-x64" "0.17.19" + "@esbuild/darwin-arm64" "0.17.19" + "@esbuild/darwin-x64" "0.17.19" + "@esbuild/freebsd-arm64" "0.17.19" + "@esbuild/freebsd-x64" "0.17.19" + "@esbuild/linux-arm" "0.17.19" + "@esbuild/linux-arm64" "0.17.19" + "@esbuild/linux-ia32" "0.17.19" + "@esbuild/linux-loong64" "0.17.19" + "@esbuild/linux-mips64el" "0.17.19" + "@esbuild/linux-ppc64" "0.17.19" + "@esbuild/linux-riscv64" "0.17.19" + "@esbuild/linux-s390x" "0.17.19" + "@esbuild/linux-x64" "0.17.19" + "@esbuild/netbsd-x64" "0.17.19" + "@esbuild/openbsd-x64" "0.17.19" + "@esbuild/sunos-x64" "0.17.19" + "@esbuild/win32-arm64" "0.17.19" + "@esbuild/win32-ia32" "0.17.19" + "@esbuild/win32-x64" "0.17.19" + esbuild@0.20.0: version "0.20.0" resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.20.0.tgz#a7170b63447286cd2ff1f01579f09970e6965da4" @@ -18245,6 +18473,11 @@ exists-sync@^0.1.0: resolved "https://registry.yarnpkg.com/exists-sync/-/exists-sync-0.1.0.tgz#318d545213d2b2a31499e92c35f74c94196a22f7" integrity sha512-qEfFekfBVid4b14FNug/RNY1nv+BADnlzKGHulc+t6ZLqGY4kdHGh1iFha8lnE3sJU/1WzMzKRNxS6EvSakJUg== +exit-hook@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-2.2.1.tgz#007b2d92c6428eda2b76e7016a34351586934593" + integrity sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw== + exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -19324,6 +19557,14 @@ get-port@5.1.1, get-port@^5.1.1: resolved "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193" integrity sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ== +get-source@^2.0.12: + version "2.0.12" + resolved "https://registry.yarnpkg.com/get-source/-/get-source-2.0.12.tgz#0b47d57ea1e53ce0d3a69f4f3d277eb8047da944" + integrity sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w== + dependencies: + data-uri-to-buffer "^2.0.0" + source-map "^0.6.1" + get-stdin@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" @@ -20722,14 +20963,6 @@ https-proxy-agent@5.0.1, https-proxy-agent@^5.0.0, https-proxy-agent@^5.0.1: agent-base "6" debug "4" -https-proxy-agent@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz#702b71fb5520a132a66de1f67541d9e62154d82b" - integrity sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg== - dependencies: - agent-base "5" - debug "4" - https-proxy-agent@^7.0.1: version "7.0.4" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz#8e97b841a029ad8ddc8731f26595bad868cb4168" @@ -20809,7 +21042,7 @@ ignore-by-default@^1.0.1: resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== -ignore-walk@3.0.3, ignore-walk@^3.0.3: +ignore-walk@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37" integrity sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw== @@ -22524,14 +22757,6 @@ js-tokens@^9.0.0: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-9.0.0.tgz#0f893996d6f3ed46df7f0a3b12a03f5fd84223c1" integrity sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ== -js-yaml@3.14.0: - version "3.14.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" - integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - js-yaml@4.1.0, js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" @@ -23747,7 +23972,7 @@ magic-string@0.30.8, magic-string@~0.30.0: dependencies: "@jridgewell/sourcemap-codec" "^1.4.15" -magic-string@^0.25.7: +magic-string@^0.25.3, magic-string@^0.25.7: version "0.25.9" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c" integrity sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ== @@ -24687,6 +24912,24 @@ mini-css-extract-plugin@2.6.1, mini-css-extract-plugin@^2.5.2: dependencies: schema-utils "^4.0.0" +miniflare@3.20240701.0, miniflare@^3.20240701.0: + version "3.20240701.0" + resolved "https://registry.yarnpkg.com/miniflare/-/miniflare-3.20240701.0.tgz#1c23b45baa65ed199da7d94c55d93f69cb4d48d2" + integrity sha512-m9+I+7JNyqDGftCMKp9cK9pCZkK72hAL2mM9IWwhct+ZmucLBA8Uu6+rHQqA5iod86cpwOkrB2PrPA3wx9YNgw== + dependencies: + "@cspotcode/source-map-support" "0.8.1" + acorn "^8.8.0" + acorn-walk "^8.2.0" + capnp-ts "^0.7.0" + exit-hook "^2.2.1" + glob-to-regexp "^0.4.1" + stoppable "^1.1.0" + undici "^5.28.4" + workerd "1.20240701.0" + ws "^8.17.1" + youch "^3.2.2" + zod "^3.22.3" + minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" @@ -25287,7 +25530,7 @@ nan@^2.12.1: resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== -nanoid@^3.3.4, nanoid@^3.3.7: +nanoid@^3.3.3, nanoid@^3.3.4, nanoid@^3.3.7: version "3.3.7" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== @@ -25612,7 +25855,7 @@ node-fetch-native@^1.6.1, node-fetch-native@^1.6.2, node-fetch-native@^1.6.3, no resolved "https://registry.yarnpkg.com/node-fetch-native/-/node-fetch-native-1.6.4.tgz#679fc8fd8111266d47d7e72c379f1bed9acff06e" integrity sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ== -node-fetch@2.6.7, node-fetch@^2.2.0, node-fetch@^2.3.0, node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.7: +node-fetch@2.6.7, node-fetch@^2.3.0, node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.7: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== @@ -27312,6 +27555,11 @@ path-to-regexp@^1.5.3, path-to-regexp@^1.7.0: 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" @@ -28461,6 +28709,11 @@ pretty-ms@^7.0.1: dependencies: parse-ms "^2.1.0" +printable-characters@^1.0.42: + version "1.0.42" + resolved "https://registry.yarnpkg.com/printable-characters/-/printable-characters-1.0.42.tgz#3f18e977a9bd8eb37fcc4ff5659d7be90868b3d8" + integrity sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ== + printf@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/printf/-/printf-0.6.1.tgz#b9afa3d3b55b7f2e8b1715272479fc756ed88650" @@ -28972,7 +29225,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.3.0: +"react-router-6@npm: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== @@ -28987,6 +29240,13 @@ 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" @@ -29737,6 +29997,11 @@ resolve.exports@^1.1.0: resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9" integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== +resolve.exports@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800" + integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== + resolve@1.22.1, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.11.1, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.0, resolve@^1.22.1, resolve@^1.4.0, resolve@^1.5.0: version "1.22.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" @@ -29969,6 +30234,15 @@ rollup-plugin-dts@^6.1.0: optionalDependencies: "@babel/code-frame" "^7.22.13" +rollup-plugin-inject@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rollup-plugin-inject/-/rollup-plugin-inject-3.0.2.tgz#e4233855bfba6c0c12a312fd6649dff9a13ee9f4" + integrity sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w== + dependencies: + estree-walker "^0.6.1" + magic-string "^0.25.3" + rollup-pluginutils "^2.8.1" + rollup-plugin-license@^3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/rollup-plugin-license/-/rollup-plugin-license-3.3.1.tgz#73b68e33477524198d6f3f9befc905f59bf37c53" @@ -29984,6 +30258,13 @@ rollup-plugin-license@^3.3.1: spdx-expression-validate "~2.0.0" spdx-satisfies "~5.0.1" +rollup-plugin-node-polyfills@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/rollup-plugin-node-polyfills/-/rollup-plugin-node-polyfills-0.2.1.tgz#53092a2744837164d5b8a28812ba5f3ff61109fd" + integrity sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA== + dependencies: + rollup-plugin-inject "^3.0.0" + rollup-plugin-sourcemaps@^0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/rollup-plugin-sourcemaps/-/rollup-plugin-sourcemaps-0.6.3.tgz#bf93913ffe056e414419607f1d02780d7ece84ed" @@ -30002,7 +30283,7 @@ rollup-plugin-visualizer@^5.12.0: source-map "^0.7.4" yargs "^17.5.1" -rollup-pluginutils@^2.8.2: +rollup-pluginutils@^2.8.1, rollup-pluginutils@^2.8.2: version "2.8.2" resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e" integrity sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ== @@ -31376,6 +31657,14 @@ stacktrace-parser@^0.1.10: dependencies: type-fest "^0.7.1" +stacktracey@^2.1.8: + version "2.1.8" + resolved "https://registry.yarnpkg.com/stacktracey/-/stacktracey-2.1.8.tgz#bf9916020738ce3700d1323b32bd2c91ea71199d" + integrity sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw== + dependencies: + as-table "^1.0.36" + get-source "^2.0.12" + stagehand@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/stagehand/-/stagehand-1.0.0.tgz#79515e2ad3a02c63f8720c7df9b6077ae14276d9" @@ -31425,6 +31714,11 @@ stop-iteration-iterator@^1.0.0: dependencies: internal-slot "^1.0.4" +stoppable@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/stoppable/-/stoppable-1.1.0.tgz#32da568e83ea488b08e4d7ea2c3bcc9d75015d5b" + integrity sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw== + stream-browserify@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" @@ -31519,7 +31813,16 @@ 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", 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": + 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: 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== @@ -31631,7 +31934,14 @@ stringify-object@^3.2.1: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.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": + 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: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -32086,17 +32396,6 @@ tar@^6.2.0: mkdirp "^1.0.3" yallist "^4.0.0" -teeny-request@6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/teeny-request/-/teeny-request-6.0.1.tgz#9b1f512cef152945827ba7e34f62523a4ce2c5b0" - integrity sha512-TAK0c9a00ELOqLrZ49cFxvPVogMUFaWY8dUsQc/0CuQPGF+BOxOQzXfE413BAk2kLomwNplvdtMpeaeGWmoc2g== - dependencies: - http-proxy-agent "^4.0.0" - https-proxy-agent "^4.0.0" - node-fetch "^2.2.0" - stream-events "^1.0.5" - uuid "^3.3.2" - teeny-request@^7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/teeny-request/-/teeny-request-7.0.1.tgz#bdd41fdffea5f8fbc0d29392cb47bec4f66b2b4c" @@ -32694,6 +32993,11 @@ tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.3 resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.2.tgz#1b6f07185c881557b0ffa84b111a0106989e8338" integrity sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA== +tslib@^2.2.0: + version "2.6.3" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" + integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== + tsutils@^3.21.0: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" @@ -33037,6 +33341,18 @@ unenv@^1.9.0: node-fetch-native "^1.6.1" pathe "^1.1.1" +"unenv@npm:unenv-nightly@1.10.0-1717606461.a117952": + version "1.10.0-1717606461.a117952" + resolved "https://registry.yarnpkg.com/unenv-nightly/-/unenv-nightly-1.10.0-1717606461.a117952.tgz#ff0b97e1e159f84be747271e1d55263b4b3eae7e" + integrity sha512-u3TfBX02WzbHTpaEfWEKwDijDSFAHcgXkayUZ+MVDrjhLFvgAJzFGTSTmwlEhwWi2exyRQey23ah9wELMM6etg== + dependencies: + consola "^3.2.3" + defu "^6.1.4" + mime "^3.0.0" + node-fetch-native "^1.6.4" + pathe "^1.1.2" + ufo "^1.5.3" + unhead@1.9.14: version "1.9.14" resolved "https://registry.yarnpkg.com/unhead/-/unhead-1.9.14.tgz#eb9f602a10072751b80907f00e4346beb4d48b6d" @@ -33505,11 +33821,6 @@ url@^0.11.0: punycode "1.3.2" querystring "0.2.0" -urlgrey@0.4.4: - version "0.4.4" - resolved "https://registry.yarnpkg.com/urlgrey/-/urlgrey-0.4.4.tgz#892fe95960805e85519f1cd4389f2cb4cbb7652f" - integrity sha1-iS/pWWCAXoVRnxzUOJ8stMu3ZS8= - urlpattern-polyfill@8.0.2: version "8.0.2" resolved "https://registry.yarnpkg.com/urlpattern-polyfill/-/urlpattern-polyfill-8.0.2.tgz#99f096e35eff8bf4b5a2aa7d58a1523d6ebc7ce5" @@ -33587,11 +33898,6 @@ uuid@8.3.2, uuid@^8.0.0, uuid@^8.3.1, uuid@^8.3.2: resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== -uuid@^3.3.2: - version "3.4.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" - integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== - uuid@^9.0.0: version "9.0.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5" @@ -34700,6 +35006,17 @@ worker-farm@^1.7.0: dependencies: errno "~0.1.7" +workerd@1.20240701.0: + version "1.20240701.0" + resolved "https://registry.yarnpkg.com/workerd/-/workerd-1.20240701.0.tgz#aaed23a54158bae4faf313c6ed48aefe4b87cd5e" + integrity sha512-qSgNVqauqzNCij9MaJLF2c2ko3AnFioVSIxMSryGbRK+LvtGr9BKBt6JOxCb24DoJASoJDx3pe3DJHBVydUiBg== + optionalDependencies: + "@cloudflare/workerd-darwin-64" "1.20240701.0" + "@cloudflare/workerd-darwin-arm64" "1.20240701.0" + "@cloudflare/workerd-linux-64" "1.20240701.0" + "@cloudflare/workerd-linux-arm64" "1.20240701.0" + "@cloudflare/workerd-windows-64" "1.20240701.0" + workerpool@^3.1.1: version "3.1.2" resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-3.1.2.tgz#b34e79243647decb174b7481ab5b351dc565c426" @@ -34724,7 +35041,40 @@ workerpool@^6.4.0: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.4.0.tgz#f8d5cfb45fde32fa3b7af72ad617c3369567a462" integrity sha512-i3KR1mQMNwY2wx20ozq2EjISGtQWDIfV56We+yGJ5yDs8jTwQiLLaqHlkBHITlCuJnYlVRmXegxFxZg7gqI++A== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@7.0.0, wrap-ansi@^7.0.0: +wrangler@^3.63.2: + version "3.63.2" + resolved "https://registry.yarnpkg.com/wrangler/-/wrangler-3.63.2.tgz#f09ec6f26eb83bdb95a32519df2faec9f1d4c578" + integrity sha512-c7F46JtBGTIQehTOgfGbxfDMYgO9AjC70CXVSohxHiF9ajHz56HEV2k3aowhJJiP3MBB8sJMm8rdG10f5zUs+w== + dependencies: + "@cloudflare/kv-asset-handler" "0.3.4" + "@esbuild-plugins/node-globals-polyfill" "^0.2.3" + "@esbuild-plugins/node-modules-polyfill" "^0.2.2" + blake3-wasm "^2.1.5" + chokidar "^3.5.3" + date-fns "^3.6.0" + esbuild "0.17.19" + miniflare "3.20240701.0" + nanoid "^3.3.3" + path-to-regexp "^6.2.0" + resolve "^1.22.8" + resolve.exports "^2.0.2" + selfsigned "^2.0.1" + source-map "^0.6.1" + unenv "npm:unenv-nightly@1.10.0-1717606461.a117952" + xxhash-wasm "^1.0.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: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -34815,6 +35165,11 @@ ws@^8.17.0: resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== +ws@^8.17.1: + 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" @@ -34858,6 +35213,11 @@ xtend@^4.0.0, xtend@~4.0.1: resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== +xxhash-wasm@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/xxhash-wasm/-/xxhash-wasm-1.0.2.tgz#ecc0f813219b727af4d5f3958ca6becee2f2f1ff" + integrity sha512-ibF0Or+FivM9lNrg+HGJfVX8WJqgo+kCLDc4vx6xMeTce7Aj+DLttKbxxRR/gNLSAelRc1omAPlJ77N/Jem07A== + xxhashjs@~0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/xxhashjs/-/xxhashjs-0.2.2.tgz#8a6251567621a1c46a5ae204da0249c7f8caa9d8" @@ -35012,6 +35372,15 @@ yocto-queue@^1.0.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251" integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g== +youch@^3.2.2: + version "3.3.3" + resolved "https://registry.yarnpkg.com/youch/-/youch-3.3.3.tgz#50cfdf5bc395ce664a5073e31b712ff4a859d928" + integrity sha512-qSFXUk3UZBLfggAW3dJKg0BMblG5biqSF8M34E06o5CSsZtH92u9Hqmj2RzGiHDi64fhe83+4tENFP2DB6t6ZA== + dependencies: + cookie "^0.5.0" + mustache "^4.2.0" + stacktracey "^2.1.8" + zhead@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/zhead/-/zhead-2.2.4.tgz#87cd1e2c3d2f465fa9f43b8db23f9716dfe6bed7" @@ -35026,6 +35395,11 @@ zip-stream@^6.0.1: compress-commons "^6.0.2" readable-stream "^4.0.0" +zod@^3.22.3: + version "3.23.8" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d" + integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g== + zod@^3.22.4: version "3.22.4" resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.4.tgz#f31c3a9386f61b1f228af56faa9255e845cf3fff"