From 1e6fde630bb201549da7dcb7b4ed4201354d31c2 Mon Sep 17 00:00:00 2001 From: Francesco Gringl-Novy Date: Tue, 23 Sep 2025 10:38:11 +0200 Subject: [PATCH 01/12] build: Add `@typescript-eslint/no-unnecessary-type-assertion` rule (#17728) This PR adds the [no-unnecessary-type-assertion](https://typescript-eslint.io/rules/no-unnecessary-type-assertion/) eslint rule. This rule captures unneeded type assertions/conversions. E.g. the following code will be raised: ```js const thing = { name: 'string' }; // no need for !, this def. exists thing!.name // no need to cast to string, as this is already the type thing.name as string; ``` note that this also expects you to use generics where possible instead of casting types, which is nicer anyhow (e.g. `getIntegrationByName(..)` vs `getIntegrationByName(..) as IntegrationType`). --- .../Breadcrumbs/fetch/get/test.ts | 2 +- .../fetch/getWithRequestObj/test.ts | 2 +- .../Breadcrumbs/fetch/post/test.ts | 2 +- .../Breadcrumbs/fetch/statusCode/test.ts | 4 ++-- .../integrations/Breadcrumbs/xhr/get/test.ts | 2 +- .../integrations/Breadcrumbs/xhr/post/test.ts | 2 +- .../Breadcrumbs/xhr/statusCode/test.ts | 4 ++-- .../integrations/graphqlClient/fetch/test.ts | 2 +- .../integrations/graphqlClient/xhr/test.ts | 2 +- .../suites/integrations/webWorker/test.ts | 2 +- .../withCaptureException/test.ts | 8 +++---- .../withCaptureMessage/test.ts | 8 +++---- .../diagnoseSdkConnectivity/test.ts | 2 +- .../requestAnimationFrame/callback/test.ts | 2 +- .../suites/replay/dsc/test.ts | 12 +++++----- .../fetch/captureRequestBody/test.ts | 10 ++++---- .../fetch/captureRequestHeaders/test.ts | 10 ++++---- .../fetch/captureRequestSize/test.ts | 4 ++-- .../fetch/captureResponseBody/test.ts | 8 +++---- .../fetch/captureResponseHeaders/test.ts | 6 ++--- .../fetch/captureResponseSize/test.ts | 6 ++--- .../fetch/captureTimestamps/test.ts | 2 +- .../xhr/captureRequestBody/test.ts | 10 ++++---- .../xhr/captureRequestHeaders/test.ts | 4 ++-- .../xhr/captureRequestSize/test.ts | 4 ++-- .../xhr/captureResponseBody/test.ts | 10 ++++---- .../xhr/captureResponseHeaders/test.ts | 4 ++-- .../xhr/captureResponseSize/test.ts | 4 ++-- .../xhr/captureTimestamps/test.ts | 2 +- .../tracing/metrics/element-timing/test.ts | 10 ++++---- .../suites/transport/offline/queued/test.ts | 2 +- .../utils/helpers.ts | 6 ++--- .../utils/replayHelpers.ts | 2 +- .../utils/wasmHelpers.ts | 2 +- .../utils/runner.ts | 2 +- .../suites/express/multiple-init/server.ts | 2 +- .../node-integration-tests/utils/runner.ts | 2 +- packages/astro/test/server/middleware.test.ts | 4 ++-- packages/aws-serverless/test/sdk.test.ts | 4 ++-- packages/aws-serverless/test/utils.test.ts | 2 +- .../src/metrics/browserMetrics.ts | 10 ++++---- .../src/metrics/web-vitals/getCLS.ts | 2 +- .../src/metrics/web-vitals/getLCP.ts | 2 +- .../web-vitals/lib/InteractionManager.ts | 4 ++-- packages/browser/src/eventbuilder.ts | 2 +- .../src/integrations/globalhandlers.ts | 2 +- .../test/integrations/webWorker.test.ts | 2 +- .../tracing/browserTracingIntegration.test.ts | 4 ++-- packages/cloudflare/src/workflows.ts | 2 +- .../cloudflare/test/durableobject.test.ts | 24 +++++++------------ .../test/integrations/fetch.test.ts | 2 +- packages/cloudflare/test/workflow.test.ts | 2 +- .../core/src/asyncContext/stackStrategy.ts | 2 +- packages/core/src/breadcrumbs.ts | 2 +- packages/core/src/fetch.ts | 2 +- .../core/src/integrations/mcp-server/index.ts | 2 +- .../integrations/mcp-server/methodConfig.ts | 4 ++-- .../core/src/integrations/mcp-server/spans.ts | 4 ++-- packages/core/src/integrations/supabase.ts | 8 +++---- packages/core/src/tracing/sentrySpan.ts | 3 +-- packages/core/src/utils/anthropic-ai/index.ts | 2 +- packages/core/src/utils/debug-logger.ts | 2 +- packages/core/src/utils/eventbuilder.ts | 2 +- packages/core/src/utils/featureFlags.ts | 2 +- packages/core/src/utils/google-genai/index.ts | 2 +- packages/core/src/utils/openai/index.ts | 4 ++-- .../lib/integrations/captureconsole.test.ts | 8 +++---- .../core/test/lib/tracing/idleSpan.test.ts | 12 +++++----- .../deno/src/integrations/normalizepaths.ts | 2 +- .../tests/acceptance/sentry-replay-test.ts | 2 +- packages/eslint-config-sdk/src/base.js | 3 +++ packages/feedback/src/core/integration.ts | 2 +- packages/feedback/src/modal/integration.tsx | 2 +- .../test/gcpfunction/cloud_event.test.ts | 2 +- .../test/gcpfunction/events.test.ts | 2 +- .../test/gcpfunction/http.test.ts | 2 +- .../integrations/google-cloud-http.test.ts | 2 +- .../google-cloud-serverless/test/sdk.test.ts | 2 +- packages/integration-shims/src/Feedback.ts | 4 ++-- packages/integration-shims/src/Replay.ts | 4 ++-- packages/nestjs/src/setup.ts | 2 +- .../templates/routeHandlerWrapperTemplate.ts | 2 +- .../serverComponentWrapperTemplate.ts | 2 +- packages/nextjs/test/config/mocks.ts | 4 ++-- .../node-core/src/integrations/context.ts | 2 +- .../integrations/http/incoming-requests.ts | 2 +- .../test/integrations/context.test.ts | 2 +- .../test/integrations/contextlines.test.ts | 2 +- .../test/integrations/spotlight.test.ts | 2 +- packages/node-core/test/sdk/init.test.ts | 2 +- .../node-core/test/transports/http.test.ts | 4 ++-- .../node-core/test/transports/https.test.ts | 2 +- .../src/integrations/tracing/fastify/index.ts | 2 +- .../integrations/tracing/firebase/firebase.ts | 3 +-- .../tracing/google-genai/instrumentation.ts | 2 +- packages/node/src/utils/redisCache.ts | 4 ++-- packages/node/test/sdk/init.test.ts | 4 ++-- ...alityTransactionsFilterIntegration.test.ts | 8 +++---- packages/remix/src/server/sdk.ts | 2 +- .../test/unit/session/createSession.test.ts | 2 +- .../unit/session/loadOrCreateSession.test.ts | 2 +- .../test/server-common/serverRoute.test.ts | 10 ++++---- .../vercel-edge/test/wintercg-fetch.test.ts | 2 +- 103 files changed, 195 insertions(+), 200 deletions(-) diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/get/test.ts b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/get/test.ts index bcce3b5c4000..6a5024caee03 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/get/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/get/test.ts @@ -23,7 +23,7 @@ sentryTest('captures Breadcrumb for basic GET request', async ({ getLocalTestUrl expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'fetch', type: 'http', diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/getWithRequestObj/test.ts b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/getWithRequestObj/test.ts index c2ba9222a108..fc125817725e 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/getWithRequestObj/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/getWithRequestObj/test.ts @@ -23,7 +23,7 @@ sentryTest('captures Breadcrumb for basic GET request that uses request object', expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'fetch', type: 'http', diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/post/test.ts b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/post/test.ts index 317230a631d4..6658c2628f60 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/post/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/post/test.ts @@ -23,7 +23,7 @@ sentryTest('captures Breadcrumb for POST request', async ({ getLocalTestUrl, pag expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'fetch', type: 'http', diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/statusCode/test.ts b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/statusCode/test.ts index ba957aec42cc..03b9b636d9e9 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/statusCode/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/statusCode/test.ts @@ -19,7 +19,7 @@ sentryTest('captures Breadcrumb with log level for 4xx response code', async ({ expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'fetch', type: 'http', @@ -56,7 +56,7 @@ sentryTest('captures Breadcrumb with log level for 5xx response code', async ({ expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'fetch', type: 'http', diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/get/test.ts b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/get/test.ts index 3c3b41e07ea2..02275b0adf2a 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/get/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/get/test.ts @@ -24,7 +24,7 @@ sentryTest('captures Breadcrumb for basic GET request', async ({ getLocalTestUrl expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'xhr', type: 'http', diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/post/test.ts b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/post/test.ts index f9c018d2ec70..7de33d189f37 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/post/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/post/test.ts @@ -23,7 +23,7 @@ sentryTest('captures Breadcrumb for POST request', async ({ getLocalTestUrl, pag expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'xhr', type: 'http', diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/statusCode/test.ts b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/statusCode/test.ts index 905d00cb85cd..d17613cc1da1 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/statusCode/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/statusCode/test.ts @@ -19,7 +19,7 @@ sentryTest('captures Breadcrumb with log level for 4xx response code', async ({ expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'xhr', type: 'http', @@ -56,7 +56,7 @@ sentryTest('captures Breadcrumb with log level for 5xx response code', async ({ expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'xhr', type: 'http', diff --git a/dev-packages/browser-integration-tests/suites/integrations/graphqlClient/fetch/test.ts b/dev-packages/browser-integration-tests/suites/integrations/graphqlClient/fetch/test.ts index 826e6cef48f5..f3fd78bc0b94 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/graphqlClient/fetch/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/graphqlClient/fetch/test.ts @@ -86,7 +86,7 @@ sentryTest('should update breadcrumbs for GraphQL fetch requests', async ({ getL expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'fetch', type: 'http', diff --git a/dev-packages/browser-integration-tests/suites/integrations/graphqlClient/xhr/test.ts b/dev-packages/browser-integration-tests/suites/integrations/graphqlClient/xhr/test.ts index 5089401441f2..ca9704cc48fe 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/graphqlClient/xhr/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/graphqlClient/xhr/test.ts @@ -86,7 +86,7 @@ sentryTest('should update breadcrumbs for GraphQL XHR requests', async ({ getLoc expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'xhr', type: 'http', diff --git a/dev-packages/browser-integration-tests/suites/integrations/webWorker/test.ts b/dev-packages/browser-integration-tests/suites/integrations/webWorker/test.ts index cc5a8b3c7cf0..bb5adf0ac70a 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/webWorker/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/webWorker/test.ts @@ -4,7 +4,7 @@ import { sentryTest } from '../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest } from '../../../utils/helpers'; sentryTest('Assigns web worker debug IDs when using webWorkerIntegration', async ({ getLocalTestUrl, page }) => { - const bundle = process.env.PW_BUNDLE as string | undefined; + const bundle = process.env.PW_BUNDLE; if (bundle != null && !bundle.includes('esm') && !bundle.includes('cjs')) { sentryTest.skip(); } diff --git a/dev-packages/browser-integration-tests/suites/public-api/captureFeedback/withCaptureException/test.ts b/dev-packages/browser-integration-tests/suites/public-api/captureFeedback/withCaptureException/test.ts index faeff404df6b..99023e472526 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/captureFeedback/withCaptureException/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/captureFeedback/withCaptureException/test.ts @@ -1,17 +1,17 @@ import { expect } from '@playwright/test'; -import type { Event, FeedbackEvent } from '@sentry/core'; +import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; import { getMultipleSentryEnvelopeRequests } from '../../../../utils/helpers'; sentryTest('capture user feedback when captureException is called', async ({ getLocalTestUrl, page }) => { const url = await getLocalTestUrl({ testDir: __dirname }); - const data = (await getMultipleSentryEnvelopeRequests(page, 2, { url })) as (Event | FeedbackEvent)[]; + const data = await getMultipleSentryEnvelopeRequests(page, 2, { url }); expect(data).toHaveLength(2); - const errorEvent = ('exception' in data[0] ? data[0] : data[1]) as Event; - const feedback = ('exception' in data[0] ? data[1] : data[0]) as FeedbackEvent; + const errorEvent = 'exception' in data[0] ? data[0] : data[1]; + const feedback = 'exception' in data[0] ? data[1] : data[0]; expect(feedback.contexts).toEqual( expect.objectContaining({ diff --git a/dev-packages/browser-integration-tests/suites/public-api/captureFeedback/withCaptureMessage/test.ts b/dev-packages/browser-integration-tests/suites/public-api/captureFeedback/withCaptureMessage/test.ts index 4cf2e4165b55..fab1207d9246 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/captureFeedback/withCaptureMessage/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/captureFeedback/withCaptureMessage/test.ts @@ -1,17 +1,17 @@ import { expect } from '@playwright/test'; -import type { Event, FeedbackEvent } from '@sentry/core'; +import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; import { getMultipleSentryEnvelopeRequests } from '../../../../utils/helpers'; sentryTest('capture user feedback when captureMessage is called', async ({ getLocalTestUrl, page }) => { const url = await getLocalTestUrl({ testDir: __dirname }); - const data = (await getMultipleSentryEnvelopeRequests(page, 2, { url })) as (Event | FeedbackEvent)[]; + const data = await getMultipleSentryEnvelopeRequests(page, 2, { url }); expect(data).toHaveLength(2); - const errorEvent = ('exception' in data[0] ? data[0] : data[1]) as Event; - const feedback = ('exception' in data[0] ? data[1] : data[0]) as FeedbackEvent; + const errorEvent = 'exception' in data[0] ? data[0] : data[1]; + const feedback = 'exception' in data[0] ? data[1] : data[0]; expect(feedback.contexts).toEqual( expect.objectContaining({ diff --git a/dev-packages/browser-integration-tests/suites/public-api/diagnoseSdkConnectivity/test.ts b/dev-packages/browser-integration-tests/suites/public-api/diagnoseSdkConnectivity/test.ts index 294e60b34bfd..c18d75464223 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/diagnoseSdkConnectivity/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/diagnoseSdkConnectivity/test.ts @@ -3,7 +3,7 @@ import { sentryTest } from '../../../utils/fixtures'; import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../utils/helpers'; sentryTest('makes a call to sentry.io to diagnose SDK connectivity', async ({ getLocalTestUrl, page }) => { - const bundle = process.env.PW_BUNDLE as string | undefined; + const bundle = process.env.PW_BUNDLE; if (shouldSkipTracingTest() || !!bundle) { // the CDN bundle doesn't export diagnoseSdkConnectivity. So skipping the test for bundles. sentryTest.skip(); diff --git a/dev-packages/browser-integration-tests/suites/public-api/instrumentation/requestAnimationFrame/callback/test.ts b/dev-packages/browser-integration-tests/suites/public-api/instrumentation/requestAnimationFrame/callback/test.ts index 71f2b8d4ab3b..0d50e0705e1b 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/instrumentation/requestAnimationFrame/callback/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/instrumentation/requestAnimationFrame/callback/test.ts @@ -13,7 +13,7 @@ sentryTest( const outsideCtx = window as any; requestAnimationFrame(function () { // @ts-expect-error re-assigning this - resolve({ outsideCtx, requestAnimationFrameCtx: this as any }); + resolve({ outsideCtx, requestAnimationFrameCtx: this }); }); }); })) as any; diff --git a/dev-packages/browser-integration-tests/suites/replay/dsc/test.ts b/dev-packages/browser-integration-tests/suites/replay/dsc/test.ts index f9a6d1041615..ef0882e0206b 100644 --- a/dev-packages/browser-integration-tests/suites/replay/dsc/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/dsc/test.ts @@ -48,7 +48,7 @@ sentryTest( const req0 = await transactionReq; - const envHeader = envelopeRequestParser(req0, 0) as EventEnvelopeHeaders; + const envHeader = envelopeRequestParser(req0, 0); const replay = await getReplaySnapshot(page); expect(replay.session?.id).toBeDefined(); @@ -96,7 +96,7 @@ sentryTest( const req0 = await transactionReq; - const envHeader = envelopeRequestParser(req0, 0) as EventEnvelopeHeaders; + const envHeader = envelopeRequestParser(req0, 0); const replay = await getReplaySnapshot(page); expect(replay.session?.id).toBeDefined(); @@ -148,7 +148,7 @@ sentryTest( const req0 = await transactionReq; - const envHeader = envelopeRequestParser(req0, 0) as EventEnvelopeHeaders; + const envHeader = envelopeRequestParser(req0, 0); const replay = await getReplaySnapshot(page); expect(replay.session?.id).toBeDefined(); @@ -191,7 +191,7 @@ sentryTest( const req0 = await transactionReq; - const envHeader = envelopeRequestParser(req0, 0) as EventEnvelopeHeaders; + const envHeader = envelopeRequestParser(req0, 0); const replay = await getReplaySnapshot(page); @@ -235,7 +235,7 @@ sentryTest('should add replay_id to error DSC while replay is active', async ({ await page.evaluate('window._triggerError(1)'); - const error1Header = envelopeRequestParser(await error1Req, 0) as EventEnvelopeHeaders; + const error1Header = envelopeRequestParser(await error1Req, 0); const replay = await getReplaySnapshot(page); expect(replay.session?.id).toBeDefined(); @@ -260,7 +260,7 @@ sentryTest('should add replay_id to error DSC while replay is active', async ({ await page.waitForFunction('!window.Replay.getReplayId();'); await page.evaluate('window._triggerError(2)'); - const error2Header = envelopeRequestParser(await error2Req, 0) as EventEnvelopeHeaders; + const error2Header = envelopeRequestParser(await error2Req, 0); expect(error2Header.trace).toBeDefined(); expect(error2Header.trace).toEqual({ diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestBody/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestBody/test.ts index e63e2fb285e0..22c756e05bf4 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestBody/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestBody/test.ts @@ -44,7 +44,7 @@ sentryTest('captures text request body', async ({ getLocalTestUrl, page, browser expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'fetch', type: 'http', @@ -114,7 +114,7 @@ sentryTest('captures JSON request body', async ({ getLocalTestUrl, page, browser expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'fetch', type: 'http', @@ -188,7 +188,7 @@ sentryTest('captures non-text request body', async ({ getLocalTestUrl, page, bro expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'fetch', type: 'http', @@ -258,7 +258,7 @@ sentryTest('captures text request body when matching relative URL', async ({ get expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'fetch', type: 'http', @@ -326,7 +326,7 @@ sentryTest('does not capture request body when URL does not match', async ({ get expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'fetch', type: 'http', diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestHeaders/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestHeaders/test.ts index 01afceca077c..bdb2c6d05110 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestHeaders/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestHeaders/test.ts @@ -46,7 +46,7 @@ sentryTest('handles empty/missing request headers', async ({ getLocalTestUrl, pa expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'fetch', type: 'http', @@ -118,7 +118,7 @@ sentryTest('captures request headers as POJO', async ({ getLocalTestUrl, page, b expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'fetch', type: 'http', @@ -198,7 +198,7 @@ sentryTest('captures request headers on Request', async ({ getLocalTestUrl, page expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'fetch', type: 'http', @@ -277,7 +277,7 @@ sentryTest('captures request headers as Headers instance', async ({ getLocalTest expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'fetch', type: 'http', @@ -353,7 +353,7 @@ sentryTest('does not captures request headers if URL does not match', async ({ g expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'fetch', type: 'http', diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestSize/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestSize/test.ts index 5671bdaabc1a..2790f26ac476 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestSize/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureRequestSize/test.ts @@ -45,7 +45,7 @@ sentryTest('captures request body size when body is sent', async ({ getLocalTest expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'fetch', type: 'http', @@ -124,7 +124,7 @@ sentryTest('captures request size from non-text request body', async ({ getLocal expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'fetch', type: 'http', diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseBody/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseBody/test.ts index 4f5926f742ef..a64f1667ec95 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseBody/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseBody/test.ts @@ -44,7 +44,7 @@ sentryTest('captures text response body', async ({ getLocalTestUrl, page, browse expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'fetch', type: 'http', @@ -116,7 +116,7 @@ sentryTest('captures JSON response body', async ({ getLocalTestUrl, page, browse expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'fetch', type: 'http', @@ -188,7 +188,7 @@ sentryTest('captures non-text response body', async ({ getLocalTestUrl, page, br expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'fetch', type: 'http', @@ -260,7 +260,7 @@ sentryTest.skip('does not capture response body when URL does not match', async expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'fetch', type: 'http', diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseHeaders/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseHeaders/test.ts index 86913966aaa1..74f677280657 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseHeaders/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseHeaders/test.ts @@ -43,7 +43,7 @@ sentryTest('handles empty headers', async ({ getLocalTestUrl, page, browserName expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'fetch', type: 'http', @@ -111,7 +111,7 @@ sentryTest('captures response headers', async ({ getLocalTestUrl, page }) => { expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'fetch', type: 'http', @@ -185,7 +185,7 @@ sentryTest('does not capture response headers if URL does not match', async ({ g expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'fetch', type: 'http', diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseSize/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseSize/test.ts index 58375d604dd2..65fcd44d74ad 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseSize/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureResponseSize/test.ts @@ -49,7 +49,7 @@ sentryTest('captures response size from Content-Length header if available', asy expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'fetch', type: 'http', @@ -131,7 +131,7 @@ sentryTest('captures response size without Content-Length header', async ({ getL expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'fetch', type: 'http', @@ -213,7 +213,7 @@ sentryTest('captures response size from non-text response body', async ({ getLoc expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'fetch', type: 'http', diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureTimestamps/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureTimestamps/test.ts index df63c10084b4..715fddecfd34 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureTimestamps/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/fetch/captureTimestamps/test.ts @@ -61,5 +61,5 @@ sentryTest('captures correct timestamps', async ({ getLocalTestUrl, page, browse expect(endTimestamp).toEqual(expect.any(Number)); expect(endTimestamp).toBeGreaterThan(startTimestamp); - expect(eventData!.breadcrumbs![0].timestamp).toBeGreaterThan(startTimestamp); + expect(eventData.breadcrumbs![0].timestamp).toBeGreaterThan(startTimestamp); }); diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestBody/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestBody/test.ts index 6b0e6e88f3e1..90e6ae4ec154 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestBody/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestBody/test.ts @@ -47,7 +47,7 @@ sentryTest('captures text request body', async ({ getLocalTestUrl, page, browser expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'xhr', type: 'http', @@ -119,7 +119,7 @@ sentryTest('captures JSON request body', async ({ getLocalTestUrl, page, browser expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'xhr', type: 'http', @@ -195,7 +195,7 @@ sentryTest('captures non-text request body', async ({ getLocalTestUrl, page, bro expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'xhr', type: 'http', @@ -267,7 +267,7 @@ sentryTest('captures text request body when matching relative URL', async ({ get expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'xhr', type: 'http', @@ -339,7 +339,7 @@ sentryTest('does not capture request body when URL does not match', async ({ get expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'xhr', type: 'http', diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestHeaders/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestHeaders/test.ts index 12f3a59f5fb1..2f40dc9bb469 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestHeaders/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestHeaders/test.ts @@ -51,7 +51,7 @@ sentryTest('captures request headers', async ({ getLocalTestUrl, page, browserNa expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'xhr', type: 'http', @@ -129,7 +129,7 @@ sentryTest('does not capture request headers if URL does not match', async ({ ge expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'xhr', type: 'http', diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestSize/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestSize/test.ts index 2fe599bbe831..48f2f30ebb79 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestSize/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureRequestSize/test.ts @@ -49,7 +49,7 @@ sentryTest('captures request body size when body is sent', async ({ getLocalTest expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'xhr', type: 'http', @@ -131,7 +131,7 @@ sentryTest('captures request size from non-text request body', async ({ getLocal expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'xhr', type: 'http', diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseBody/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseBody/test.ts index 4764d32b16df..f74842f38883 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseBody/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseBody/test.ts @@ -51,7 +51,7 @@ sentryTest('captures text response body', async ({ getLocalTestUrl, page, browse expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'xhr', type: 'http', @@ -127,7 +127,7 @@ sentryTest('captures JSON response body', async ({ getLocalTestUrl, page, browse expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'xhr', type: 'http', @@ -205,7 +205,7 @@ sentryTest('captures JSON response body when responseType=json', async ({ getLoc expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'xhr', type: 'http', @@ -281,7 +281,7 @@ sentryTest('captures non-text response body', async ({ getLocalTestUrl, page, br expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'xhr', type: 'http', @@ -357,7 +357,7 @@ sentryTest('does not capture response body when URL does not match', async ({ ge expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'xhr', type: 'http', diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseHeaders/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseHeaders/test.ts index f4b444455b46..d728c6995592 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseHeaders/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseHeaders/test.ts @@ -54,7 +54,7 @@ sentryTest('captures response headers', async ({ getLocalTestUrl, page, browserN expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'xhr', type: 'http', @@ -136,7 +136,7 @@ sentryTest( expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'xhr', type: 'http', diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseSize/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseSize/test.ts index 5f14ac8b17cd..a3c3ba9746fe 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseSize/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureResponseSize/test.ts @@ -52,7 +52,7 @@ sentryTest( expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'xhr', type: 'http', @@ -223,7 +223,7 @@ sentryTest('captures response size for non-string bodies', async ({ getLocalTest expect(eventData.exception?.values).toHaveLength(1); expect(eventData?.breadcrumbs?.length).toBe(1); - expect(eventData!.breadcrumbs![0]).toEqual({ + expect(eventData.breadcrumbs![0]).toEqual({ timestamp: expect.any(Number), category: 'xhr', type: 'http', diff --git a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureTimestamps/test.ts b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureTimestamps/test.ts index ef4b9aee3c26..40c9462fff21 100644 --- a/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureTimestamps/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/extendNetworkBreadcrumbs/xhr/captureTimestamps/test.ts @@ -68,5 +68,5 @@ sentryTest('captures correct timestamps', async ({ getLocalTestUrl, page, browse expect(endTimestamp).toEqual(expect.any(Number)); expect(endTimestamp).toBeGreaterThan(startTimestamp); - expect(eventData!.breadcrumbs![0].timestamp).toBeGreaterThan(startTimestamp); + expect(eventData.breadcrumbs![0].timestamp).toBeGreaterThan(startTimestamp); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/element-timing/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/element-timing/test.ts index e17cbbbda691..d5dabb5d0ca5 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/element-timing/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/element-timing/test.ts @@ -28,7 +28,7 @@ sentryTest( const imageFastSpan = elementTimingSpans?.find(({ description }) => description === 'element[image-fast]'); const imageFastRenderTime = imageFastSpan?.data['element.render_time']; const imageFastLoadTime = imageFastSpan?.data['element.load_time']; - const duration = imageFastSpan!.timestamp! - imageFastSpan!.start_timestamp!; + const duration = imageFastSpan!.timestamp! - imageFastSpan!.start_timestamp; expect(imageFastSpan).toBeDefined(); expect(imageFastSpan?.data).toEqual({ @@ -58,7 +58,7 @@ sentryTest( const text1Span = elementTimingSpans?.find(({ data }) => data?.['element.identifier'] === 'text1'); const text1RenderTime = text1Span?.data['element.render_time']; const text1LoadTime = text1Span?.data['element.load_time']; - const text1Duration = text1Span!.timestamp! - text1Span!.start_timestamp!; + const text1Duration = text1Span!.timestamp! - text1Span!.start_timestamp; expect(text1Span).toBeDefined(); expect(text1Span?.data).toEqual({ 'sentry.op': 'ui.elementtiming', @@ -109,7 +109,7 @@ sentryTest( }); const imageSlowRenderTime = imageSlowSpan?.data['element.render_time']; const imageSlowLoadTime = imageSlowSpan?.data['element.load_time']; - const imageSlowDuration = imageSlowSpan!.timestamp! - imageSlowSpan!.start_timestamp!; + const imageSlowDuration = imageSlowSpan!.timestamp! - imageSlowSpan!.start_timestamp; expect(imageSlowRenderTime).toBeGreaterThan(1400); expect(imageSlowRenderTime).toBeLessThan(2000); expect(imageSlowLoadTime).toBeGreaterThan(1400); @@ -137,7 +137,7 @@ sentryTest( }); const lazyImageRenderTime = lazyImageSpan?.data['element.render_time']; const lazyImageLoadTime = lazyImageSpan?.data['element.load_time']; - const lazyImageDuration = lazyImageSpan!.timestamp! - lazyImageSpan!.start_timestamp!; + const lazyImageDuration = lazyImageSpan!.timestamp! - lazyImageSpan!.start_timestamp; expect(lazyImageRenderTime).toBeGreaterThan(1000); expect(lazyImageRenderTime).toBeLessThan(1500); expect(lazyImageLoadTime).toBeGreaterThan(1000); @@ -155,7 +155,7 @@ sentryTest( }); const lazyTextRenderTime = lazyTextSpan?.data['element.render_time']; const lazyTextLoadTime = lazyTextSpan?.data['element.load_time']; - const lazyTextDuration = lazyTextSpan!.timestamp! - lazyTextSpan!.start_timestamp!; + const lazyTextDuration = lazyTextSpan!.timestamp! - lazyTextSpan!.start_timestamp; expect(lazyTextRenderTime).toBeGreaterThan(1000); expect(lazyTextRenderTime).toBeLessThan(1500); expect(lazyTextLoadTime).toBe(0); diff --git a/dev-packages/browser-integration-tests/suites/transport/offline/queued/test.ts b/dev-packages/browser-integration-tests/suites/transport/offline/queued/test.ts index 13694a383c54..5df36411011c 100644 --- a/dev-packages/browser-integration-tests/suites/transport/offline/queued/test.ts +++ b/dev-packages/browser-integration-tests/suites/transport/offline/queued/test.ts @@ -37,7 +37,7 @@ sentryTest('should queue and retry events when they fail to send', async ({ getL const eventData = await getMultipleSentryEnvelopeRequests(page, 3, { url, timeout: 10_000 }); // Filter out any client reports - const events = eventData.filter(e => !('discarded_events' in e)) as Event[]; + const events = eventData.filter(e => !('discarded_events' in e)); expect(events).toHaveLength(2); diff --git a/dev-packages/browser-integration-tests/utils/helpers.ts b/dev-packages/browser-integration-tests/utils/helpers.ts index b3f55b4550a0..dd75d2f6ee86 100644 --- a/dev-packages/browser-integration-tests/utils/helpers.ts +++ b/dev-packages/browser-integration-tests/utils/helpers.ts @@ -310,7 +310,7 @@ export async function waitForSession(page: Page): Promise { * @returns `true` if we should skip the tracing test */ export function shouldSkipTracingTest(): boolean { - const bundle = process.env.PW_BUNDLE as string | undefined; + const bundle = process.env.PW_BUNDLE; return bundle != null && !bundle.includes('tracing') && !bundle.includes('esm') && !bundle.includes('cjs'); } @@ -330,7 +330,7 @@ export function shouldSkipFeedbackTest(): boolean { * @returns `true` if we should skip the feature flags test */ export function shouldSkipFeatureFlagsTest(): boolean { - const bundle = process.env.PW_BUNDLE as string | undefined; + const bundle = process.env.PW_BUNDLE; return bundle != null && !bundle.includes('esm') && !bundle.includes('cjs'); } @@ -423,7 +423,7 @@ export async function getMultipleSentryEnvelopeRequests( }, requestParser: (req: Request) => T = envelopeRequestParser as (req: Request) => T, ): Promise { - return getMultipleRequests(page, count, envelopeUrlRegex, requestParser, options) as Promise; + return getMultipleRequests(page, count, envelopeUrlRegex, requestParser, options); } /** diff --git a/dev-packages/browser-integration-tests/utils/replayHelpers.ts b/dev-packages/browser-integration-tests/utils/replayHelpers.ts index f1f18967e69b..86a9e5ba3d78 100644 --- a/dev-packages/browser-integration-tests/utils/replayHelpers.ts +++ b/dev-packages/browser-integration-tests/utils/replayHelpers.ts @@ -431,7 +431,7 @@ export const replayEnvelopeParser = (request: Request | null): unknown[] => { * @returns `true` if we should skip the replay test */ export function shouldSkipReplayTest(): boolean { - const bundle = process.env.PW_BUNDLE as string | undefined; + const bundle = process.env.PW_BUNDLE; return bundle != null && !bundle.includes('replay') && !bundle.includes('esm') && !bundle.includes('cjs'); } diff --git a/dev-packages/browser-integration-tests/utils/wasmHelpers.ts b/dev-packages/browser-integration-tests/utils/wasmHelpers.ts index cb8cf1bcb4b3..5287f5db0436 100644 --- a/dev-packages/browser-integration-tests/utils/wasmHelpers.ts +++ b/dev-packages/browser-integration-tests/utils/wasmHelpers.ts @@ -10,6 +10,6 @@ export function shouldSkipWASMTests(browser: string): boolean { if (browser === 'webkit') { return true; } - const bundle = process.env.PW_BUNDLE as string | undefined; + const bundle = process.env.PW_BUNDLE; return bundle != null; } diff --git a/dev-packages/node-core-integration-tests/utils/runner.ts b/dev-packages/node-core-integration-tests/utils/runner.ts index 273680607ffb..da6184dcbb42 100644 --- a/dev-packages/node-core-integration-tests/utils/runner.ts +++ b/dev-packages/node-core-integration-tests/utils/runner.ts @@ -427,7 +427,7 @@ export function createRunner(...paths: string[]) { ? runDockerCompose(dockerOptions) : Promise.resolve(undefined); - const startup = Promise.all([dockerStartup, serverStartup]) as Promise<[DockerStartup, ServerStartup]>; + const startup = Promise.all([dockerStartup, serverStartup]); startup .then(([dockerChild, [mockServerPort, mockServerClose]]) => { diff --git a/dev-packages/node-integration-tests/suites/express/multiple-init/server.ts b/dev-packages/node-integration-tests/suites/express/multiple-init/server.ts index f9952ce43a9f..0f5bf73d7e5b 100644 --- a/dev-packages/node-integration-tests/suites/express/multiple-init/server.ts +++ b/dev-packages/node-integration-tests/suites/express/multiple-init/server.ts @@ -36,7 +36,7 @@ app.get('/test/init', (_req, res) => { transport: loggingTransport, }); // Set this on initial scope, to ensure it can be inherited - initialCurrentScope.setClient(Sentry.getClient()!); + initialCurrentScope.setClient(Sentry.getClient()); Sentry.addBreadcrumb({ message: 'init breadcrumb' }); Sentry.setTag('init', 'tag'); diff --git a/dev-packages/node-integration-tests/utils/runner.ts b/dev-packages/node-integration-tests/utils/runner.ts index dfe27dc5a4a5..fe6d9a8f8c45 100644 --- a/dev-packages/node-integration-tests/utils/runner.ts +++ b/dev-packages/node-integration-tests/utils/runner.ts @@ -538,7 +538,7 @@ export function createRunner(...paths: string[]) { ? runDockerCompose(dockerOptions) : Promise.resolve(undefined); - const startup = Promise.all([dockerStartup, serverStartup]) as Promise<[DockerStartup, ServerStartup]>; + const startup = Promise.all([dockerStartup, serverStartup]); startup .then(([dockerChild, [mockServerPort, mockServerClose]]) => { diff --git a/packages/astro/test/server/middleware.test.ts b/packages/astro/test/server/middleware.test.ts index 10e314268264..020365dda627 100644 --- a/packages/astro/test/server/middleware.test.ts +++ b/packages/astro/test/server/middleware.test.ts @@ -200,9 +200,9 @@ describe('sentryMiddleware', () => { // @ts-expect-error, a partial ctx object is fine here const resultFromNext = await middleware(ctx, next); + expect(resultFromNext).toBeDefined(); expect(resultFromNext?.headers.get('content-type')).toEqual('text/html'); - - await expect(() => resultFromNext!.text()).rejects.toThrowError(); + await expect(() => resultFromNext?.text()).rejects.toThrowError(); expect(captureExceptionSpy).toHaveBeenCalledWith(error, { mechanism: { handled: false, type: 'auto.middleware.astro' }, diff --git a/packages/aws-serverless/test/sdk.test.ts b/packages/aws-serverless/test/sdk.test.ts index d2047274d744..a3aef6a10b5d 100644 --- a/packages/aws-serverless/test/sdk.test.ts +++ b/packages/aws-serverless/test/sdk.test.ts @@ -19,7 +19,7 @@ const mockScope = { vi.mock('@sentry/node', async () => { // eslint-disable-next-line @typescript-eslint/consistent-type-imports - const original = (await vi.importActual('@sentry/node')) as typeof import('@sentry/node'); + const original = await vi.importActual('@sentry/node'); return { ...original, initWithoutDefaultIntegrations: (options: unknown) => { @@ -459,7 +459,7 @@ describe('AWSLambda', () => { const streamError = new Error('stream error'); const streamingHandler = vi.fn(async (_event, responseStream, _context) => { // Simulate stream error by calling the error listener - const errorListener = (responseStream.on as any).mock.calls.find((call: any[]) => call[0] === 'error')?.[1]; + const errorListener = responseStream.on.mock.calls.find((call: any[]) => call[0] === 'error')?.[1]; if (errorListener) { errorListener(streamError); } diff --git a/packages/aws-serverless/test/utils.test.ts b/packages/aws-serverless/test/utils.test.ts index 8fba98d0add6..2f3516ce8e35 100644 --- a/packages/aws-serverless/test/utils.test.ts +++ b/packages/aws-serverless/test/utils.test.ts @@ -4,7 +4,7 @@ import { eventContextExtractor, getAwsTraceData } from '../src/utils'; const mockExtractContext = vi.fn(); vi.mock('@opentelemetry/api', async () => { // eslint-disable-next-line @typescript-eslint/consistent-type-imports - const actualApi = (await vi.importActual('@opentelemetry/api')) as typeof import('@opentelemetry/api'); + const actualApi = await vi.importActual('@opentelemetry/api'); return { ...actualApi, propagation: { diff --git a/packages/browser-utils/src/metrics/browserMetrics.ts b/packages/browser-utils/src/metrics/browserMetrics.ts index 8b1592408e8a..2c61408c1d76 100644 --- a/packages/browser-utils/src/metrics/browserMetrics.ts +++ b/packages/browser-utils/src/metrics/browserMetrics.ts @@ -473,8 +473,8 @@ export function _addMeasureSpans( // Measurements from third parties can be off, which would create invalid spans, dropping transactions in the process. if (measureStartTimestamp <= measureEndTimestamp) { startAndEndSpan(span, measureStartTimestamp, measureEndTimestamp, { - name: entry.name as string, - op: entry.entryType as string, + name: entry.name, + op: entry.entryType, attributes, }); } @@ -595,9 +595,9 @@ function _getEndPropertyNameForNavigationTiming(event: StartEventName): EndEvent /** Create request and response related spans */ function _addRequest(span: Span, entry: PerformanceNavigationTiming, timeOrigin: number): void { - const requestStartTimestamp = timeOrigin + msToSec(entry.requestStart as number); - const responseEndTimestamp = timeOrigin + msToSec(entry.responseEnd as number); - const responseStartTimestamp = timeOrigin + msToSec(entry.responseStart as number); + const requestStartTimestamp = timeOrigin + msToSec(entry.requestStart); + const responseEndTimestamp = timeOrigin + msToSec(entry.responseEnd); + const responseStartTimestamp = timeOrigin + msToSec(entry.responseStart); if (entry.responseEnd) { // It is possible that we are collecting these metrics when the page hasn't finished loading yet, for example when the HTML slowly streams in. // In this case, ie. when the document request hasn't finished yet, `entry.responseEnd` will be 0. diff --git a/packages/browser-utils/src/metrics/web-vitals/getCLS.ts b/packages/browser-utils/src/metrics/web-vitals/getCLS.ts index 1b4d50a7c44e..c40f993f8ca8 100644 --- a/packages/browser-utils/src/metrics/web-vitals/getCLS.ts +++ b/packages/browser-utils/src/metrics/web-vitals/getCLS.ts @@ -74,7 +74,7 @@ export const onCLS = (onReport: (metric: CLSMetric) => void, opts: ReportOpts = const po = observe('layout-shift', handleEntries); if (po) { - report = bindReporter(onReport, metric, CLSThresholds, opts!.reportAllChanges); + report = bindReporter(onReport, metric, CLSThresholds, opts.reportAllChanges); WINDOW.document?.addEventListener('visibilitychange', () => { if (WINDOW.document?.visibilityState === 'hidden') { diff --git a/packages/browser-utils/src/metrics/web-vitals/getLCP.ts b/packages/browser-utils/src/metrics/web-vitals/getLCP.ts index 0f2f821d9bcc..6eafee698673 100644 --- a/packages/browser-utils/src/metrics/web-vitals/getLCP.ts +++ b/packages/browser-utils/src/metrics/web-vitals/getLCP.ts @@ -52,7 +52,7 @@ export const onLCP = (onReport: (metric: LCPMetric) => void, opts: ReportOpts = const handleEntries = (entries: LCPMetric['entries']) => { // If reportAllChanges is set then call this function for each entry, // otherwise only consider the last one. - if (!opts!.reportAllChanges) { + if (!opts.reportAllChanges) { // eslint-disable-next-line no-param-reassign entries = entries.slice(-1); } diff --git a/packages/browser-utils/src/metrics/web-vitals/lib/InteractionManager.ts b/packages/browser-utils/src/metrics/web-vitals/lib/InteractionManager.ts index 033cdb2cb836..d4aea1683606 100644 --- a/packages/browser-utils/src/metrics/web-vitals/lib/InteractionManager.ts +++ b/packages/browser-utils/src/metrics/web-vitals/lib/InteractionManager.ts @@ -104,7 +104,7 @@ export class InteractionManager { // The least-long of the 10 longest interactions. const minLongestInteraction = this._longestInteractionList.at(-1); - let interaction = this._longestInteractionMap.get(entry.interactionId!); + let interaction = this._longestInteractionMap.get(entry.interactionId); // Only process the entry if it's possibly one of the ten longest, // or if it's part of an existing interaction. @@ -126,7 +126,7 @@ export class InteractionManager { } } else { interaction = { - id: entry.interactionId!, + id: entry.interactionId, entries: [entry], _latency: entry.duration, }; diff --git a/packages/browser/src/eventbuilder.ts b/packages/browser/src/eventbuilder.ts index 67cb4d2578cd..cc0be3378b8d 100644 --- a/packages/browser/src/eventbuilder.ts +++ b/packages/browser/src/eventbuilder.ts @@ -309,7 +309,7 @@ export function eventFromUnknownInput( // If it's a plain object or an instance of `Event` (the built-in JS kind, not this SDK's `Event` type), serialize // it manually. This will allow us to group events based on top-level keys which is much better than creating a new // group on any key/value change. - const objectException = exception as Record; + const objectException = exception; event = eventFromPlainObject(stackParser, objectException, syntheticException, isUnhandledRejection); addExceptionMechanism(event, { synthetic: true, diff --git a/packages/browser/src/integrations/globalhandlers.ts b/packages/browser/src/integrations/globalhandlers.ts index 2aa29731a9b0..fa815db7c15e 100644 --- a/packages/browser/src/integrations/globalhandlers.ts +++ b/packages/browser/src/integrations/globalhandlers.ts @@ -86,7 +86,7 @@ function _installGlobalOnUnhandledRejectionHandler(client: Client): void { return; } - const error = _getUnhandledRejectionError(e as unknown); + const error = _getUnhandledRejectionError(e); const event = isPrimitive(error) ? _eventFromRejectionWithPrimitive(error) diff --git a/packages/browser/test/integrations/webWorker.test.ts b/packages/browser/test/integrations/webWorker.test.ts index eacd2b53344d..4dfea4983949 100644 --- a/packages/browser/test/integrations/webWorker.test.ts +++ b/packages/browser/test/integrations/webWorker.test.ts @@ -117,7 +117,7 @@ describe('webWorkerIntegration', () => { // Extract the message handler from the addEventListener call expect(mockWorker.addEventListener.mock.calls).toBeDefined(); - messageHandler = mockWorker.addEventListener.mock.calls![0]![1]; + messageHandler = mockWorker.addEventListener.mock.calls[0]![1]; }); it('ignores non-Sentry messages', () => { diff --git a/packages/browser/test/tracing/browserTracingIntegration.test.ts b/packages/browser/test/tracing/browserTracingIntegration.test.ts index 7e573cae1866..e3f1060655c2 100644 --- a/packages/browser/test/tracing/browserTracingIntegration.test.ts +++ b/packages/browser/test/tracing/browserTracingIntegration.test.ts @@ -873,7 +873,7 @@ describe('browserTracingIntegration', () => { const idleSpan = getActiveSpan()!; expect(idleSpan).toBeDefined(); - const dynamicSamplingContext = getDynamicSamplingContextFromSpan(idleSpan!); + const dynamicSamplingContext = getDynamicSamplingContextFromSpan(idleSpan); const propagationContext = getCurrentScope().getPropagationContext(); // Span is correct @@ -1010,7 +1010,7 @@ describe('browserTracingIntegration', () => { const idleSpan = getActiveSpan()!; expect(idleSpan).toBeDefined(); - const dynamicSamplingContext = getDynamicSamplingContextFromSpan(idleSpan!); + const dynamicSamplingContext = getDynamicSamplingContextFromSpan(idleSpan); const propagationContext = getCurrentScope().getPropagationContext(); // Span is correct diff --git a/packages/cloudflare/src/workflows.ts b/packages/cloudflare/src/workflows.ts index 336df2abe301..16327ea71ccf 100644 --- a/packages/cloudflare/src/workflows.ts +++ b/packages/cloudflare/src/workflows.ts @@ -192,5 +192,5 @@ export function instrumentWorkflowWithSentry< }, }); }, - }) as C; + }); } diff --git a/packages/cloudflare/test/durableobject.test.ts b/packages/cloudflare/test/durableobject.test.ts index ce794dc7fb69..4d9e2a20fe97 100644 --- a/packages/cloudflare/test/durableobject.test.ts +++ b/packages/cloudflare/test/durableobject.test.ts @@ -23,10 +23,7 @@ describe('instrumentDurableObjectWithSentry', () => { return 'sync-result'; } }; - const obj = Reflect.construct( - instrumentDurableObjectWithSentry(vi.fn().mockReturnValue({}), testClass as any), - [], - ) as any; + const obj = Reflect.construct(instrumentDurableObjectWithSentry(vi.fn().mockReturnValue({}), testClass as any), []); expect(obj.method).toBe(obj.method); const result = obj.method(); @@ -40,10 +37,7 @@ describe('instrumentDurableObjectWithSentry', () => { return 'async-result'; } }; - const obj = Reflect.construct( - instrumentDurableObjectWithSentry(vi.fn().mockReturnValue({}), testClass as any), - [], - ) as any; + const obj = Reflect.construct(instrumentDurableObjectWithSentry(vi.fn().mockReturnValue({}), testClass as any), []); expect(obj.asyncMethod).toBe(obj.asyncMethod); const result = obj.asyncMethod(); @@ -74,13 +68,13 @@ describe('instrumentDurableObjectWithSentry', () => { const instance1 = Reflect.construct(instrumentDurableObjectWithSentry(options, testClass as any), [ mockContext, mockEnv, - ]) as any; + ]); instance1.method(); const instance2 = Reflect.construct(instrumentDurableObjectWithSentry(options, testClass as any), [ mockContext, mockEnv, - ]) as any; + ]); instance2.method(); expect(initCore).nthCalledWith(1, expect.any(Function), expect.objectContaining({ orgId: 1 })); @@ -156,7 +150,7 @@ describe('instrumentDurableObjectWithSentry', () => { }; const options = vi.fn().mockReturnValue({}); const instrumented = instrumentDurableObjectWithSentry(options, testClass as any); - const obj = Reflect.construct(instrumented, []) as any; + const obj = Reflect.construct(instrumented, []); expect(isInstrumented(obj.prototypeMethod)).toBeFalsy(); }); @@ -169,7 +163,7 @@ describe('instrumentDurableObjectWithSentry', () => { }; const options = vi.fn().mockReturnValue({ instrumentPrototypeMethods: false }); const instrumented = instrumentDurableObjectWithSentry(options, testClass as any); - const obj = Reflect.construct(instrumented, []) as any; + const obj = Reflect.construct(instrumented, []); expect(isInstrumented(obj.prototypeMethod)).toBeFalsy(); }); @@ -185,7 +179,7 @@ describe('instrumentDurableObjectWithSentry', () => { }; const options = vi.fn().mockReturnValue({ instrumentPrototypeMethods: true }); const instrumented = instrumentDurableObjectWithSentry(options, testClass as any); - const obj = Reflect.construct(instrumented, []) as any; + const obj = Reflect.construct(instrumented, []); expect(isInstrumented(obj.methodOne)).toBeTruthy(); expect(isInstrumented(obj.methodTwo)).toBeTruthy(); @@ -205,7 +199,7 @@ describe('instrumentDurableObjectWithSentry', () => { }; const options = vi.fn().mockReturnValue({ instrumentPrototypeMethods: ['methodOne', 'methodThree'] }); const instrumented = instrumentDurableObjectWithSentry(options, testClass as any); - const obj = Reflect.construct(instrumented, []) as any; + const obj = Reflect.construct(instrumented, []); expect(isInstrumented(obj.methodOne)).toBeTruthy(); expect(isInstrumented(obj.methodTwo)).toBeFalsy(); @@ -224,7 +218,7 @@ describe('instrumentDurableObjectWithSentry', () => { }; const options = vi.fn().mockReturnValue({ instrumentPrototypeMethods: false }); const instrumented = instrumentDurableObjectWithSentry(options, testClass as any); - const obj = Reflect.construct(instrumented, []) as any; + const obj = Reflect.construct(instrumented, []); // Instance methods should still be instrumented expect(isInstrumented(obj.propertyFunction)).toBeTruthy(); diff --git a/packages/cloudflare/test/integrations/fetch.test.ts b/packages/cloudflare/test/integrations/fetch.test.ts index 03cdbb9bf5a5..11dbaa18916b 100644 --- a/packages/cloudflare/test/integrations/fetch.test.ts +++ b/packages/cloudflare/test/integrations/fetch.test.ts @@ -7,7 +7,7 @@ import { fetchIntegration } from '../../src/integrations/fetch'; class FakeClient extends CloudflareClient { public getIntegrationByName(name: string): T | undefined { - return name === 'Fetch' ? (fetchIntegration() as Integration as T) : undefined; + return name === 'Fetch' ? (fetchIntegration() as T) : undefined; } } diff --git a/packages/cloudflare/test/workflow.test.ts b/packages/cloudflare/test/workflow.test.ts index 2ca2ccd28e46..fa922d7233e0 100644 --- a/packages/cloudflare/test/workflow.test.ts +++ b/packages/cloudflare/test/workflow.test.ts @@ -449,7 +449,7 @@ describe.skipIf(NODE_MAJOR_VERSION < 20)('workflows', () => { expect(mockContext.waitUntil).toHaveBeenCalledTimes(2); expect(mockTransport.send).toHaveBeenCalledTimes(1); - const sendArg = mockTransport.send.mock.calls[0]![0] as any; + const sendArg = mockTransport.send.mock.calls[0]![0]; const items = sendArg[1] as any[]; const rootSpanItem = items.find(i => i[0].type === 'transaction'); expect(rootSpanItem).toBeDefined(); diff --git a/packages/core/src/asyncContext/stackStrategy.ts b/packages/core/src/asyncContext/stackStrategy.ts index 845605be731d..87dc534fc636 100644 --- a/packages/core/src/asyncContext/stackStrategy.ts +++ b/packages/core/src/asyncContext/stackStrategy.ts @@ -135,7 +135,7 @@ function withScope(callback: (scope: Scope) => T): T { } function withSetScope(scope: Scope, callback: (scope: Scope) => T): T { - const stack = getAsyncContextStack() as AsyncContextStack; + const stack = getAsyncContextStack(); return stack.withScope(() => { stack.getStackTop().scope = scope; return callback(scope); diff --git a/packages/core/src/breadcrumbs.ts b/packages/core/src/breadcrumbs.ts index 99deafdd8d2d..d6e15fcd0b02 100644 --- a/packages/core/src/breadcrumbs.ts +++ b/packages/core/src/breadcrumbs.ts @@ -28,7 +28,7 @@ export function addBreadcrumb(breadcrumb: Breadcrumb, hint?: BreadcrumbHint): vo const timestamp = dateTimestampInSeconds(); const mergedBreadcrumb = { timestamp, ...breadcrumb }; const finalBreadcrumb = beforeBreadcrumb - ? (consoleSandbox(() => beforeBreadcrumb(mergedBreadcrumb, hint)) as Breadcrumb | null) + ? consoleSandbox(() => beforeBreadcrumb(mergedBreadcrumb, hint)) : mergedBreadcrumb; if (finalBreadcrumb === null) return; diff --git a/packages/core/src/fetch.ts b/packages/core/src/fetch.ts index 501c29b4ea10..9ab62ec732da 100644 --- a/packages/core/src/fetch.ts +++ b/packages/core/src/fetch.ts @@ -248,7 +248,7 @@ export function _addTracingHeadersToFetchRequest( baggage: string | undefined; traceparent?: string; } = { - ...(originalHeaders as Exclude), + ...originalHeaders, 'sentry-trace': (existingSentryTraceHeader as string | undefined) ?? sentryTrace, baggage: newBaggageHeaders.length > 0 ? newBaggageHeaders.join(',') : undefined, }; diff --git a/packages/core/src/integrations/mcp-server/index.ts b/packages/core/src/integrations/mcp-server/index.ts index 1e16eaf202f3..a1eb8815805a 100644 --- a/packages/core/src/integrations/mcp-server/index.ts +++ b/packages/core/src/integrations/mcp-server/index.ts @@ -64,5 +64,5 @@ export function wrapMcpServerWithSentry(mcpServerInstance: S): wrapAllMCPHandlers(serverInstance); wrappedMcpServerInstances.add(mcpServerInstance); - return mcpServerInstance as S; + return mcpServerInstance; } diff --git a/packages/core/src/integrations/mcp-server/methodConfig.ts b/packages/core/src/integrations/mcp-server/methodConfig.ts index a652cd8cc41a..b57c0f254b63 100644 --- a/packages/core/src/integrations/mcp-server/methodConfig.ts +++ b/packages/core/src/integrations/mcp-server/methodConfig.ts @@ -56,7 +56,7 @@ export function extractTargetInfo( target?: string; attributes: Record; } { - const config = METHOD_CONFIGS[method as keyof typeof METHOD_CONFIGS]; + const config = METHOD_CONFIGS[method]; if (!config) { return { attributes: {} }; } @@ -80,7 +80,7 @@ export function extractTargetInfo( */ export function getRequestArguments(method: string, params: Record): Record { const args: Record = {}; - const config = METHOD_CONFIGS[method as keyof typeof METHOD_CONFIGS]; + const config = METHOD_CONFIGS[method]; if (!config) { return args; diff --git a/packages/core/src/integrations/mcp-server/spans.ts b/packages/core/src/integrations/mcp-server/spans.ts index 9a527046b6f2..fdd4c107ee30 100644 --- a/packages/core/src/integrations/mcp-server/spans.ts +++ b/packages/core/src/integrations/mcp-server/spans.ts @@ -78,7 +78,7 @@ function buildSentryAttributes(type: McpSpanConfig['type']): Record | undefined; + const params = message.params; // Determine span name based on type and OTEL conventions let spanName: string; @@ -172,7 +172,7 @@ export function buildMcpServerSpanConfig( attributes: Record; } { const { method } = jsonRpcMessage; - const params = jsonRpcMessage.params as Record | undefined; + const params = jsonRpcMessage.params; const targetInfo = extractTargetInfo(method, params || {}); const spanName = createSpanName(method, targetInfo.target); diff --git a/packages/core/src/integrations/supabase.ts b/packages/core/src/integrations/supabase.ts index 39dedadc9e04..61005fdad805 100644 --- a/packages/core/src/integrations/supabase.ts +++ b/packages/core/src/integrations/supabase.ts @@ -302,12 +302,12 @@ function instrumentSupabaseAuthClient(supabaseClientInstance: SupabaseClientInst } function instrumentSupabaseClientConstructor(SupabaseClient: unknown): void { - if (isInstrumented((SupabaseClient as unknown as SupabaseClientConstructor).prototype.from)) { + if (isInstrumented((SupabaseClient as SupabaseClientConstructor).prototype.from)) { return; } - (SupabaseClient as unknown as SupabaseClientConstructor).prototype.from = new Proxy( - (SupabaseClient as unknown as SupabaseClientConstructor).prototype.from, + (SupabaseClient as SupabaseClientConstructor).prototype.from = new Proxy( + (SupabaseClient as SupabaseClientConstructor).prototype.from, { apply(target, thisArg, argumentsList) { const rv = Reflect.apply(target, thisArg, argumentsList); @@ -320,7 +320,7 @@ function instrumentSupabaseClientConstructor(SupabaseClient: unknown): void { }, ); - markAsInstrumented((SupabaseClient as unknown as SupabaseClientConstructor).prototype.from); + markAsInstrumented((SupabaseClient as SupabaseClientConstructor).prototype.from); } function instrumentPostgRESTFilterBuilder(PostgRESTFilterBuilder: PostgRESTFilterBuilder['constructor']): void { diff --git a/packages/core/src/tracing/sentrySpan.ts b/packages/core/src/tracing/sentrySpan.ts index af1a1f2b8ffc..9bd98b9741c6 100644 --- a/packages/core/src/tracing/sentrySpan.ts +++ b/packages/core/src/tracing/sentrySpan.ts @@ -24,7 +24,6 @@ import type { } from '../types-hoist/span'; import type { SpanStatus } from '../types-hoist/spanStatus'; import type { TimedEvent } from '../types-hoist/timedEvent'; -import type { TransactionSource } from '../types-hoist/transaction'; import { debug } from '../utils/debug-logger'; import { generateSpanId, generateTraceId } from '../utils/propagationContext'; import { @@ -347,7 +346,7 @@ export class SentrySpan implements Span { const spans = finishedSpans.map(span => spanToJSON(span)).filter(isFullFinishedSpan); - const source = this._attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] as TransactionSource | undefined; + const source = this._attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]; // remove internal root span attributes we don't need to send. /* eslint-disable @typescript-eslint/no-dynamic-delete */ diff --git a/packages/core/src/utils/anthropic-ai/index.ts b/packages/core/src/utils/anthropic-ai/index.ts index f24707c4cc92..8cc44c188671 100644 --- a/packages/core/src/utils/anthropic-ai/index.ts +++ b/packages/core/src/utils/anthropic-ai/index.ts @@ -302,7 +302,7 @@ function createDeepProxy(target: T, currentPath = '', options: } if (value && typeof value === 'object') { - return createDeepProxy(value as object, methodPath, options); + return createDeepProxy(value, methodPath, options); } return value; diff --git a/packages/core/src/utils/debug-logger.ts b/packages/core/src/utils/debug-logger.ts index 6fc9c7a8c865..bbc524729674 100644 --- a/packages/core/src/utils/debug-logger.ts +++ b/packages/core/src/utils/debug-logger.ts @@ -47,7 +47,7 @@ export function consoleSandbox(callback: () => T): T { return callback(); } - const console = GLOBAL_OBJ.console as Console; + const console = GLOBAL_OBJ.console; const wrappedFuncs: Partial void>> = {}; const wrappedLevels = Object.keys(originalConsoleMethods) as ConsoleLevel[]; diff --git a/packages/core/src/utils/eventbuilder.ts b/packages/core/src/utils/eventbuilder.ts index 24889187712f..203d95ccc3a8 100644 --- a/packages/core/src/utils/eventbuilder.ts +++ b/packages/core/src/utils/eventbuilder.ts @@ -102,7 +102,7 @@ function getException( if (isPlainObject(exception)) { const normalizeDepth = client?.getOptions().normalizeDepth; - const extras = { ['__serialized__']: normalizeToSize(exception as Record, normalizeDepth) }; + const extras = { ['__serialized__']: normalizeToSize(exception, normalizeDepth) }; const errorFromProp = getErrorPropertyFromObject(exception); if (errorFromProp) { diff --git a/packages/core/src/utils/featureFlags.ts b/packages/core/src/utils/featureFlags.ts index 3e7be22704b6..4fa3cdc5ac8d 100644 --- a/packages/core/src/utils/featureFlags.ts +++ b/packages/core/src/utils/featureFlags.ts @@ -63,7 +63,7 @@ export function _INTERNAL_insertFlagToScope( if (!scopeContexts.flags) { scopeContexts.flags = { values: [] }; } - const flags = scopeContexts.flags.values as FeatureFlag[]; + const flags = scopeContexts.flags.values; _INTERNAL_insertToFlagBuffer(flags, name, value, maxSize); } diff --git a/packages/core/src/utils/google-genai/index.ts b/packages/core/src/utils/google-genai/index.ts index 13079cb34b49..58d7e2e6b5e6 100644 --- a/packages/core/src/utils/google-genai/index.ts +++ b/packages/core/src/utils/google-genai/index.ts @@ -278,7 +278,7 @@ function createDeepProxy(target: T, currentPath = '', options: return value; }, - }) as T; + }); } /** diff --git a/packages/core/src/utils/openai/index.ts b/packages/core/src/utils/openai/index.ts index d296df840112..4ecfad625062 100644 --- a/packages/core/src/utils/openai/index.ts +++ b/packages/core/src/utils/openai/index.ts @@ -201,7 +201,7 @@ function addRequestAttributes(span: Span, params: Record): void function getOptionsFromIntegration(): OpenAiOptions { const scope = getCurrentScope(); const client = scope.getClient(); - const integration = client?.getIntegrationByName(OPENAI_INTEGRATION_NAME) as OpenAiIntegration | undefined; + const integration = client?.getIntegrationByName(OPENAI_INTEGRATION_NAME); const shouldRecordInputsAndOutputs = integration ? Boolean(client?.getOptions().sendDefaultPii) : false; return { @@ -324,7 +324,7 @@ function createDeepProxy(target: T, currentPath = '', options? } if (value && typeof value === 'object') { - return createDeepProxy(value as object, methodPath, options); + return createDeepProxy(value, methodPath, options); } return value; diff --git a/packages/core/test/lib/integrations/captureconsole.test.ts b/packages/core/test/lib/integrations/captureconsole.test.ts index 8da7c6570d74..2ca059be4f86 100644 --- a/packages/core/test/lib/integrations/captureconsole.test.ts +++ b/packages/core/test/lib/integrations/captureconsole.test.ts @@ -134,7 +134,7 @@ describe('CaptureConsole setup', () => { expect(mockScope.addEventProcessor).toHaveBeenCalledTimes(1); - const addedEventProcessor = (mockScope.addEventProcessor as Mock).mock.calls[0]?.[0]; + const addedEventProcessor = mockScope.addEventProcessor.mock.calls[0]?.[0]; const someEvent: Event = {}; addedEventProcessor(someEvent); @@ -315,7 +315,7 @@ describe('CaptureConsole setup', () => { const someError = new Error('some error'); GLOBAL_OBJ.console.error(someError); - const addedEventProcessor = (mockScope.addEventProcessor as Mock).mock.calls[0]?.[0]; + const addedEventProcessor = mockScope.addEventProcessor.mock.calls[0]?.[0]; const someEvent: Event = { exception: { values: [{}], @@ -339,7 +339,7 @@ describe('CaptureConsole setup', () => { const someError = new Error('some error'); GLOBAL_OBJ.console.error(someError); - const addedEventProcessor = (mockScope.addEventProcessor as Mock).mock.calls[0]?.[0]; + const addedEventProcessor = mockScope.addEventProcessor.mock.calls[0]?.[0]; const someEvent: Event = { exception: { values: [{}], @@ -363,7 +363,7 @@ describe('CaptureConsole setup', () => { const someError = new Error('some error'); GLOBAL_OBJ.console.error(someError); - const addedEventProcessor = (mockScope.addEventProcessor as Mock).mock.calls[0]?.[0]; + const addedEventProcessor = mockScope.addEventProcessor.mock.calls[0]?.[0]; const someEvent: Event = { exception: { values: [{}], diff --git a/packages/core/test/lib/tracing/idleSpan.test.ts b/packages/core/test/lib/tracing/idleSpan.test.ts index 677428c941cd..3e930872945d 100644 --- a/packages/core/test/lib/tracing/idleSpan.test.ts +++ b/packages/core/test/lib/tracing/idleSpan.test.ts @@ -47,7 +47,7 @@ describe('startIdleSpan', () => { expect(getActiveSpan()).toBe(idleSpan); - idleSpan!.end(); + idleSpan.end(); vi.runAllTimers(); expect(getActiveSpan()).toBe(undefined); @@ -142,7 +142,7 @@ describe('startIdleSpan', () => { vi.advanceTimersByTime(TRACING_DEFAULTS.idleTimeout + 1); vi.runOnlyPendingTimers(); - expect(spanToJSON(idleSpan!).data).toEqual( + expect(spanToJSON(idleSpan).data).toEqual( expect.objectContaining({ foo: 'bar', }), @@ -693,7 +693,7 @@ describe('startIdleSpan', () => { vi.runAllTimers(); - expect(spanToJSON(idleSpan!).timestamp).toBe(1100); + expect(spanToJSON(idleSpan).timestamp).toBe(1100); }); it('trims end to final timeout', () => { @@ -713,7 +713,7 @@ describe('startIdleSpan', () => { vi.runAllTimers(); - expect(spanToJSON(idleSpan!).timestamp).toBe(1030); + expect(spanToJSON(idleSpan).timestamp).toBe(1030); }); it('keeps lower span endTime than highest child span end', () => { @@ -733,8 +733,8 @@ describe('startIdleSpan', () => { vi.runAllTimers(); - expect(spanToJSON(idleSpan!).timestamp).toBeLessThan(999_999_999); - expect(spanToJSON(idleSpan!).timestamp).toBeGreaterThan(1060); + expect(spanToJSON(idleSpan).timestamp).toBeLessThan(999_999_999); + expect(spanToJSON(idleSpan).timestamp).toBeGreaterThan(1060); }); }); }); diff --git a/packages/deno/src/integrations/normalizepaths.ts b/packages/deno/src/integrations/normalizepaths.ts index 4e7e599fb4d3..6a2bc6cd95aa 100644 --- a/packages/deno/src/integrations/normalizepaths.ts +++ b/packages/deno/src/integrations/normalizepaths.ts @@ -18,7 +18,7 @@ function appRootFromErrorStack(error: Error): string | undefined { .replace(/\\/g, '/') // replace all `\` instances with `/` .split('/') .filter(seg => seg !== ''), // remove empty segments - ) as string[][]; + ); const firstPath = paths[0]; diff --git a/packages/ember/tests/acceptance/sentry-replay-test.ts b/packages/ember/tests/acceptance/sentry-replay-test.ts index 87b8d849b799..ef9d318b2c6a 100644 --- a/packages/ember/tests/acceptance/sentry-replay-test.ts +++ b/packages/ember/tests/acceptance/sentry-replay-test.ts @@ -16,7 +16,7 @@ module('Acceptance | Sentry Session Replay', function (hooks) { Sentry.getClient()?.getIntegrationByName>('Replay'); assert.ok(integration); - const replay = integration!['_replay'] as ReturnType['_replay']; + const replay = integration!['_replay']; assert.true(replay.isEnabled()); assert.false(replay.isPaused()); diff --git a/packages/eslint-config-sdk/src/base.js b/packages/eslint-config-sdk/src/base.js index 52334507ac8b..5b77056bfbb2 100644 --- a/packages/eslint-config-sdk/src/base.js +++ b/packages/eslint-config-sdk/src/base.js @@ -59,6 +59,9 @@ module.exports = { // We want to use optional chaining, where possible, to safe bytes '@typescript-eslint/prefer-optional-chain': 'error', + // Disallow unnecessary type assertions/conversions + '@typescript-eslint/no-unnecessary-type-assertion': 'error', + // Private and protected members of a class should be prefixed with a leading underscore. // typeLike declarations (class, interface, typeAlias, enum, typeParameter) should be // PascalCase. diff --git a/packages/feedback/src/core/integration.ts b/packages/feedback/src/core/integration.ts index 87ab62cfec1b..9ce0feac921f 100644 --- a/packages/feedback/src/core/integration.ts +++ b/packages/feedback/src/core/integration.ts @@ -186,7 +186,7 @@ export const buildFeedbackIntegration = ({ _shadow = host.attachShadow({ mode: 'open' }); _shadow.appendChild(createMainStyles(options)); } - return _shadow as ShadowRoot; + return _shadow; }; const _loadAndRenderDialog = async ( diff --git a/packages/feedback/src/modal/integration.tsx b/packages/feedback/src/modal/integration.tsx index 5bdf4362509d..0e3f8a637149 100644 --- a/packages/feedback/src/modal/integration.tsx +++ b/packages/feedback/src/modal/integration.tsx @@ -24,7 +24,7 @@ export const feedbackModalIntegration = ((): FeedbackModalIntegration => { name: 'FeedbackModal', setupOnce() {}, createDialog: ({ options, screenshotIntegration, sendFeedback, shadow }) => { - const shadowRoot = shadow as unknown as ShadowRoot; + const shadowRoot = shadow as ShadowRoot; const userKey = options.useSentryUser; const user = getUser(); diff --git a/packages/google-cloud-serverless/test/gcpfunction/cloud_event.test.ts b/packages/google-cloud-serverless/test/gcpfunction/cloud_event.test.ts index 0cb6d502e934..8e7d80ee95b0 100644 --- a/packages/google-cloud-serverless/test/gcpfunction/cloud_event.test.ts +++ b/packages/google-cloud-serverless/test/gcpfunction/cloud_event.test.ts @@ -17,7 +17,7 @@ const mockSpan = { vi.mock('@sentry/node', async () => { // eslint-disable-next-line @typescript-eslint/consistent-type-imports - const original = (await vi.importActual('@sentry/node')) as typeof import('@sentry/node'); + const original = await vi.importActual('@sentry/node'); return { ...original, startSpanManual: (...args: unknown[]) => { diff --git a/packages/google-cloud-serverless/test/gcpfunction/events.test.ts b/packages/google-cloud-serverless/test/gcpfunction/events.test.ts index 7cb41a3c36e9..0dc7198ccdcf 100644 --- a/packages/google-cloud-serverless/test/gcpfunction/events.test.ts +++ b/packages/google-cloud-serverless/test/gcpfunction/events.test.ts @@ -18,7 +18,7 @@ const mockSpan = { vi.mock('@sentry/node', async () => { // eslint-disable-next-line @typescript-eslint/consistent-type-imports - const original = (await vi.importActual('@sentry/node')) as typeof import('@sentry/node'); + const original = await vi.importActual('@sentry/node'); return { ...original, startSpanManual: (...args: unknown[]) => { diff --git a/packages/google-cloud-serverless/test/gcpfunction/http.test.ts b/packages/google-cloud-serverless/test/gcpfunction/http.test.ts index 73cd262144e0..ce327c79cd54 100644 --- a/packages/google-cloud-serverless/test/gcpfunction/http.test.ts +++ b/packages/google-cloud-serverless/test/gcpfunction/http.test.ts @@ -20,7 +20,7 @@ const mockSpan = { vi.mock('@sentry/node', async () => { // eslint-disable-next-line @typescript-eslint/consistent-type-imports - const original = (await vi.importActual('@sentry/node')) as typeof import('@sentry/node'); + const original = await vi.importActual('@sentry/node'); return { ...original, init: (options: unknown) => { diff --git a/packages/google-cloud-serverless/test/integrations/google-cloud-http.test.ts b/packages/google-cloud-serverless/test/integrations/google-cloud-http.test.ts index 72a96e0b8e86..ce9ccee0a4ea 100644 --- a/packages/google-cloud-serverless/test/integrations/google-cloud-http.test.ts +++ b/packages/google-cloud-serverless/test/integrations/google-cloud-http.test.ts @@ -14,7 +14,7 @@ const mockStartInactiveSpan = vi.fn(spanArgs => ({ ...spanArgs })); vi.mock('@sentry/node', async () => { // eslint-disable-next-line @typescript-eslint/consistent-type-imports - const original = (await vi.importActual('@sentry/node')) as typeof import('@sentry/node'); + const original = await vi.importActual('@sentry/node'); return { ...original, startInactiveSpan: (ctx: unknown) => { diff --git a/packages/google-cloud-serverless/test/sdk.test.ts b/packages/google-cloud-serverless/test/sdk.test.ts index 9759ac2a5a43..a98cd8af47e6 100644 --- a/packages/google-cloud-serverless/test/sdk.test.ts +++ b/packages/google-cloud-serverless/test/sdk.test.ts @@ -5,7 +5,7 @@ const mockInit = vi.fn(); vi.mock('@sentry/node', async () => { // eslint-disable-next-line @typescript-eslint/consistent-type-imports - const original = (await vi.importActual('@sentry/node')) as typeof import('@sentry/node'); + const original = await vi.importActual('@sentry/node'); return { ...original, init: (options: unknown) => { diff --git a/packages/integration-shims/src/Feedback.ts b/packages/integration-shims/src/Feedback.ts index 327b2d40401f..dd56bd273717 100644 --- a/packages/integration-shims/src/Feedback.ts +++ b/packages/integration-shims/src/Feedback.ts @@ -22,10 +22,10 @@ export const feedbackIntegrationShim = Object.assign( return { name: 'Feedback', - ...(FEEDBACK_INTEGRATION_METHODS.reduce((acc, method) => { + ...FEEDBACK_INTEGRATION_METHODS.reduce((acc, method) => { acc[method] = FAKE_FUNCTION; return acc; - }, {} as FeedbackSpecificMethods) as FeedbackSpecificMethods), + }, {} as FeedbackSpecificMethods), }; }, { diff --git a/packages/integration-shims/src/Replay.ts b/packages/integration-shims/src/Replay.ts index 6774b9308c23..eee5cfbb2ef7 100644 --- a/packages/integration-shims/src/Replay.ts +++ b/packages/integration-shims/src/Replay.ts @@ -21,9 +21,9 @@ export function replayIntegrationShim(_options: unknown): ReplayIntegration { return { name: 'Replay', - ...(REPLAY_INTEGRATION_METHODS.reduce((acc, method) => { + ...REPLAY_INTEGRATION_METHODS.reduce((acc, method) => { acc[method] = FAKE_FUNCTION; return acc; - }, {} as ReplaySpecificMethods) as ReplaySpecificMethods), + }, {} as ReplaySpecificMethods), }; } diff --git a/packages/nestjs/src/setup.ts b/packages/nestjs/src/setup.ts index cc6d65514b77..2d4255df9b3f 100644 --- a/packages/nestjs/src/setup.ts +++ b/packages/nestjs/src/setup.ts @@ -54,7 +54,7 @@ class SentryTracingInterceptor implements NestInterceptor { } if (context.getType() === 'http') { - const req = context.switchToHttp().getRequest() as FastifyRequest | ExpressRequest; + const req = context.switchToHttp().getRequest(); if ('routeOptions' in req && req.routeOptions?.url) { // fastify case getIsolationScope().setTransactionName(`${(req.method || 'GET').toUpperCase()} ${req.routeOptions.url}`); diff --git a/packages/nextjs/src/config/templates/routeHandlerWrapperTemplate.ts b/packages/nextjs/src/config/templates/routeHandlerWrapperTemplate.ts index d8e931e06451..3a05c97d5f5c 100644 --- a/packages/nextjs/src/config/templates/routeHandlerWrapperTemplate.ts +++ b/packages/nextjs/src/config/templates/routeHandlerWrapperTemplate.ts @@ -41,7 +41,7 @@ function wrapHandler(handler: T, method: 'GET' | 'POST' | 'PUT' | 'PATCH' | ' // We try-catch here just in case the API around `requestAsyncStorage` changes unexpectedly since it is not public API try { - const requestAsyncStore = requestAsyncStorage?.getStore() as ReturnType; + const requestAsyncStore = requestAsyncStorage?.getStore(); headers = requestAsyncStore?.headers; } catch { /** empty */ diff --git a/packages/nextjs/src/config/templates/serverComponentWrapperTemplate.ts b/packages/nextjs/src/config/templates/serverComponentWrapperTemplate.ts index f29df45d3542..7a1320c3517f 100644 --- a/packages/nextjs/src/config/templates/serverComponentWrapperTemplate.ts +++ b/packages/nextjs/src/config/templates/serverComponentWrapperTemplate.ts @@ -46,7 +46,7 @@ if (typeof serverComponent === 'function') { // We try-catch here just in `requestAsyncStorage` is undefined since it may not be defined try { - const requestAsyncStore = requestAsyncStorage?.getStore() as ReturnType; + const requestAsyncStore = requestAsyncStorage?.getStore(); sentryTraceHeader = requestAsyncStore?.headers.get('sentry-trace') ?? undefined; baggageHeader = requestAsyncStore?.headers.get('baggage') ?? undefined; headers = requestAsyncStore?.headers; diff --git a/packages/nextjs/test/config/mocks.ts b/packages/nextjs/test/config/mocks.ts index 2aa174877beb..0e9c48146585 100644 --- a/packages/nextjs/test/config/mocks.ts +++ b/packages/nextjs/test/config/mocks.ts @@ -16,7 +16,7 @@ vi.mock('os'); // function also lets us restore the original when we do want to test // `getUserConfigFile()`. // eslint-disable-next-line @typescript-eslint/consistent-type-imports -const fsReal = (await vi.importActual('fs')) as typeof import('fs'); +const fsReal = await vi.importActual('fs'); export const realExistsSync = fsReal.existsSync; export const mockExistsSync = (path: fs.PathLike): ReturnType => { if ( @@ -37,7 +37,7 @@ export const exitsSync = vi.spyOn(fs, 'existsSync').mockImplementation(mockExist // Make it so that all temporary folders, either created directly by tests or by the code they're testing, will go into // one spot that we know about, which we can then clean up when we're done // eslint-disable-next-line @typescript-eslint/consistent-type-imports -const osReal = (await vi.importActual('os')) as typeof import('os'); +const osReal = await vi.importActual('os'); const realTmpdir = osReal.tmpdir; // Including the random number ensures that even if multiple test files using these mocks are running at once, they have diff --git a/packages/node-core/src/integrations/context.ts b/packages/node-core/src/integrations/context.ts index aca94a70db7a..cad8a1c4a443 100644 --- a/packages/node-core/src/integrations/context.ts +++ b/packages/node-core/src/integrations/context.ts @@ -389,7 +389,7 @@ async function getLinuxInfo(): Promise { // usually quite small, this should not allocate too much memory and we only // hold on to it for a very short amount of time. const distroPath = join('/etc', distroFile.name); - const contents = ((await readFileAsync(distroPath, { encoding: 'utf-8' })) as string).toLowerCase(); + const contents = (await readFileAsync(distroPath, { encoding: 'utf-8' })).toLowerCase(); // Some Linux distributions store their release information in the same file // (e.g. RHEL and Centos). In those cases, we scan the file for an diff --git a/packages/node-core/src/integrations/http/incoming-requests.ts b/packages/node-core/src/integrations/http/incoming-requests.ts index 57588d0ac16e..e2de19f77582 100644 --- a/packages/node-core/src/integrations/http/incoming-requests.ts +++ b/packages/node-core/src/integrations/http/incoming-requests.ts @@ -489,7 +489,7 @@ function getContentLength(headers: IncomingHttpHeaders): number | null { const contentLengthHeader = headers['content-length']; if (contentLengthHeader === undefined) return null; - const contentLength = parseInt(contentLengthHeader as string, 10); + const contentLength = parseInt(contentLengthHeader, 10); if (isNaN(contentLength)) return null; return contentLength; diff --git a/packages/node-core/test/integrations/context.test.ts b/packages/node-core/test/integrations/context.test.ts index a22bfcf6ec34..c6ca3ad6a4da 100644 --- a/packages/node-core/test/integrations/context.test.ts +++ b/packages/node-core/test/integrations/context.test.ts @@ -5,7 +5,7 @@ import { conditionalTest } from '../helpers/conditional'; vi.mock('node:os', async () => { // eslint-disable-next-line @typescript-eslint/consistent-type-imports - const original = (await vi.importActual('node:os')) as typeof import('node:os'); + const original = await vi.importActual('node:os'); return { ...original, uptime: original.uptime, diff --git a/packages/node-core/test/integrations/contextlines.test.ts b/packages/node-core/test/integrations/contextlines.test.ts index 2965acded28f..332cd9b2e51e 100644 --- a/packages/node-core/test/integrations/contextlines.test.ts +++ b/packages/node-core/test/integrations/contextlines.test.ts @@ -13,7 +13,7 @@ import { getError } from '../helpers/error'; vi.mock('node:fs', async () => { // eslint-disable-next-line @typescript-eslint/consistent-type-imports - const original = (await vi.importActual('node:fs')) as typeof import('node:fs'); + const original = await vi.importActual('node:fs'); return { ...original, createReadStream: original.createReadStream, diff --git a/packages/node-core/test/integrations/spotlight.test.ts b/packages/node-core/test/integrations/spotlight.test.ts index 19d52ebcdb21..6d8e6b0bcfc2 100644 --- a/packages/node-core/test/integrations/spotlight.test.ts +++ b/packages/node-core/test/integrations/spotlight.test.ts @@ -8,7 +8,7 @@ import { getDefaultNodeClientOptions } from '../helpers/getDefaultNodeClientOpti vi.mock('node:http', async () => { // eslint-disable-next-line @typescript-eslint/consistent-type-imports - const original = (await vi.importActual('node:http')) as typeof import('node:http'); + const original = await vi.importActual('node:http'); return { ...original, request: original.request, diff --git a/packages/node-core/test/sdk/init.test.ts b/packages/node-core/test/sdk/init.test.ts index c0c574ab2b35..4262bffb7cda 100644 --- a/packages/node-core/test/sdk/init.test.ts +++ b/packages/node-core/test/sdk/init.test.ts @@ -101,7 +101,7 @@ describe('init()', () => { expect(mockDefaultIntegrations[0]?.setupOnce as Mock).toHaveBeenCalledTimes(1); expect(mockDefaultIntegrations[1]?.setupOnce as Mock).toHaveBeenCalledTimes(0); - expect(newIntegration.setupOnce as Mock).toHaveBeenCalledTimes(1); + expect(newIntegration.setupOnce).toHaveBeenCalledTimes(1); }); }); diff --git a/packages/node-core/test/transports/http.test.ts b/packages/node-core/test/transports/http.test.ts index 0f01fdc5639a..e33bcb542bbd 100644 --- a/packages/node-core/test/transports/http.test.ts +++ b/packages/node-core/test/transports/http.test.ts @@ -14,7 +14,7 @@ import { makeNodeTransport } from '../../src/transports'; vi.mock('@sentry/core', async () => { // eslint-disable-next-line @typescript-eslint/consistent-type-imports - const actualCore = (await vi.importActual('@sentry/core')) as typeof import('@sentry/core'); + const actualCore = await vi.importActual('@sentry/core'); return { ...actualCore, createTransport: vi.fn().mockImplementation(actualCore.createTransport), @@ -23,7 +23,7 @@ vi.mock('@sentry/core', async () => { vi.mock('node:http', async () => { // eslint-disable-next-line @typescript-eslint/consistent-type-imports - const original = (await vi.importActual('node:http')) as typeof import('node:http'); + const original = await vi.importActual('node:http'); return { ...original, request: original.request, diff --git a/packages/node-core/test/transports/https.test.ts b/packages/node-core/test/transports/https.test.ts index bb4147a20133..ec9fc0aa8204 100644 --- a/packages/node-core/test/transports/https.test.ts +++ b/packages/node-core/test/transports/https.test.ts @@ -10,7 +10,7 @@ import testServerCerts from './test-server-certs'; vi.mock('@sentry/core', async () => { // eslint-disable-next-line @typescript-eslint/consistent-type-imports - const actualCore = (await vi.importActual('@sentry/core')) as typeof import('@sentry/core'); + const actualCore = await vi.importActual('@sentry/core'); return { ...actualCore, createTransport: vi.fn().mockImplementation(actualCore.createTransport), diff --git a/packages/node/src/integrations/tracing/fastify/index.ts b/packages/node/src/integrations/tracing/fastify/index.ts index 084c50a6957e..0ad069ea1f4e 100644 --- a/packages/node/src/integrations/tracing/fastify/index.ts +++ b/packages/node/src/integrations/tracing/fastify/index.ts @@ -101,7 +101,7 @@ function getFastifyIntegration(): ReturnType | undef if (!client) { return undefined; } else { - return client.getIntegrationByName(INTEGRATION_NAME) as ReturnType | undefined; + return client.getIntegrationByName(INTEGRATION_NAME); } } diff --git a/packages/node/src/integrations/tracing/firebase/firebase.ts b/packages/node/src/integrations/tracing/firebase/firebase.ts index 9f2abbfe31fd..649a7089289b 100644 --- a/packages/node/src/integrations/tracing/firebase/firebase.ts +++ b/packages/node/src/integrations/tracing/firebase/firebase.ts @@ -1,4 +1,3 @@ -import type { Span } from '@opentelemetry/api'; import type { IntegrationFn } from '@sentry/core'; import { defineIntegration, SEMANTIC_ATTRIBUTE_SENTRY_OP } from '@sentry/core'; import { addOriginToSpan, generateInstrumentOnce } from '@sentry/node-core'; @@ -8,7 +7,7 @@ const INTEGRATION_NAME = 'Firebase'; const config: FirebaseInstrumentationConfig = { firestoreSpanCreationHook: span => { - addOriginToSpan(span as Span, 'auto.firebase.otel.firestore'); + addOriginToSpan(span, 'auto.firebase.otel.firestore'); span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'db.query'); }, diff --git a/packages/node/src/integrations/tracing/google-genai/instrumentation.ts b/packages/node/src/integrations/tracing/google-genai/instrumentation.ts index cfdb68973be6..e604e0c99cd3 100644 --- a/packages/node/src/integrations/tracing/google-genai/instrumentation.ts +++ b/packages/node/src/integrations/tracing/google-genai/instrumentation.ts @@ -69,7 +69,7 @@ export class SentryGoogleGenAiInstrumentation extends InstrumentationBase(input: NestedArray): T[] { const flattenHelper = (input: NestedArray): void => { input.forEach((el: T | NestedArray) => { if (Array.isArray(el)) { - flattenHelper(el as NestedArray); + flattenHelper(el); } else { - result.push(el as T); + result.push(el); } }); }; diff --git a/packages/node/test/sdk/init.test.ts b/packages/node/test/sdk/init.test.ts index 422bbdc924f4..da6680fc622f 100644 --- a/packages/node/test/sdk/init.test.ts +++ b/packages/node/test/sdk/init.test.ts @@ -111,7 +111,7 @@ describe('init()', () => { expect(mockDefaultIntegrations[0]?.setupOnce as Mock).toHaveBeenCalledTimes(1); expect(mockDefaultIntegrations[1]?.setupOnce as Mock).toHaveBeenCalledTimes(0); - expect(newIntegration.setupOnce as Mock).toHaveBeenCalledTimes(1); + expect(newIntegration.setupOnce).toHaveBeenCalledTimes(1); expect(mockAutoPerformanceIntegrations).toHaveBeenCalledTimes(0); }); @@ -133,7 +133,7 @@ describe('init()', () => { expect(mockIntegrations[0]?.setupOnce as Mock).toHaveBeenCalledTimes(1); expect(mockIntegrations[1]?.setupOnce as Mock).toHaveBeenCalledTimes(1); - expect(autoPerformanceIntegration.setupOnce as Mock).toHaveBeenCalledTimes(1); + expect(autoPerformanceIntegration.setupOnce).toHaveBeenCalledTimes(1); expect(mockAutoPerformanceIntegrations).toHaveBeenCalledTimes(1); const client = getClient(); diff --git a/packages/react-router/test/server/lowQualityTransactionsFilterIntegration.test.ts b/packages/react-router/test/server/lowQualityTransactionsFilterIntegration.test.ts index 4f43b48dcad9..7edd75c9e996 100644 --- a/packages/react-router/test/server/lowQualityTransactionsFilterIntegration.test.ts +++ b/packages/react-router/test/server/lowQualityTransactionsFilterIntegration.test.ts @@ -1,4 +1,4 @@ -import type { Event, EventType, Integration } from '@sentry/core'; +import type { Event, EventType } from '@sentry/core'; import * as SentryCore from '@sentry/core'; import * as SentryNode from '@sentry/node'; import { afterEach, describe, expect, it, vi } from 'vitest'; @@ -20,7 +20,7 @@ describe('Low Quality Transactions Filter Integration', () => { ['@id/ requests', 'GET /@id/some-id'], ['manifest requests', 'GET /__manifest?p=%2Fperformance%2Fserver-action'], ])('%s', (description, transaction) => { - const integration = lowQualityTransactionsFilterIntegration({ debug: true }) as Integration; + const integration = lowQualityTransactionsFilterIntegration({ debug: true }); const event = { type: 'transaction' as EventType, transaction, @@ -40,7 +40,7 @@ describe('Low Quality Transactions Filter Integration', () => { ['API endpoints', 'POST /data'], ['app routes', 'GET /projects/123'], ])('%s', (description, transaction) => { - const integration = lowQualityTransactionsFilterIntegration({}) as Integration; + const integration = lowQualityTransactionsFilterIntegration({}); const event = { type: 'transaction' as EventType, transaction, @@ -53,7 +53,7 @@ describe('Low Quality Transactions Filter Integration', () => { }); it('does not affect non-transaction events', () => { - const integration = lowQualityTransactionsFilterIntegration({}) as Integration; + const integration = lowQualityTransactionsFilterIntegration({}); const event = { type: 'error' as EventType, transaction: 'GET /node_modules/some-package/index.js', diff --git a/packages/remix/src/server/sdk.ts b/packages/remix/src/server/sdk.ts index 145cb8a66b47..f191a336cfbb 100644 --- a/packages/remix/src/server/sdk.ts +++ b/packages/remix/src/server/sdk.ts @@ -18,7 +18,7 @@ export function getRemixDefaultIntegrations(options: RemixOptions): Integration[ ...getDefaultNodeIntegrations(options as NodeOptions).filter(integration => integration.name !== 'Http'), httpIntegration(), remixIntegration(), - ].filter(int => int) as Integration[]; + ].filter(int => int); } /** Initializes Sentry Remix SDK on Node. */ diff --git a/packages/replay-internal/test/unit/session/createSession.test.ts b/packages/replay-internal/test/unit/session/createSession.test.ts index 53fbbbdb59e5..a30f13401502 100644 --- a/packages/replay-internal/test/unit/session/createSession.test.ts +++ b/packages/replay-internal/test/unit/session/createSession.test.ts @@ -12,7 +12,7 @@ vi.mock('./../../../src/session/saveSession'); vi.mock('@sentry/core', async () => { return { - ...((await vi.importActual('@sentry/core')) as { string: unknown }), + ...(await vi.importActual('@sentry/core')), uuid4: vi.fn(() => 'test_session_id'), }; }); diff --git a/packages/replay-internal/test/unit/session/loadOrCreateSession.test.ts b/packages/replay-internal/test/unit/session/loadOrCreateSession.test.ts index b59c14aa4e4d..273d401a7afc 100644 --- a/packages/replay-internal/test/unit/session/loadOrCreateSession.test.ts +++ b/packages/replay-internal/test/unit/session/loadOrCreateSession.test.ts @@ -14,7 +14,7 @@ import type { SessionOptions } from '../../../src/types'; vi.mock('@sentry/core', async () => { return { - ...((await vi.importActual('@sentry/core')) as { string: unknown }), + ...(await vi.importActual('@sentry/core')), uuid4: vi.fn(() => 'test_session_uuid'), }; }); diff --git a/packages/sveltekit/test/server-common/serverRoute.test.ts b/packages/sveltekit/test/server-common/serverRoute.test.ts index 2ec3ef2cf2d2..1edfb6da6ebd 100644 --- a/packages/sveltekit/test/server-common/serverRoute.test.ts +++ b/packages/sveltekit/test/server-common/serverRoute.test.ts @@ -31,7 +31,7 @@ describe('wrapServerRouteWithSentry', () => { it('assigns the route id as name if available', () => { const wrappedRouteHandler = wrapServerRouteWithSentry(originalRouteHandler); - wrappedRouteHandler(getRequestEventMock() as RequestEvent); + wrappedRouteHandler(getRequestEventMock()); expect(startSpanSpy).toHaveBeenCalledWith( { @@ -81,7 +81,7 @@ describe('wrapServerRouteWithSentry', () => { }); await expect(async () => { - await wrappedRouteHandler(getRequestEventMock() as RequestEvent); + await wrappedRouteHandler(getRequestEventMock()); }).rejects.toThrowError('Server Route Error'); expect(captureExceptionSpy).toHaveBeenCalledWith(error, { @@ -95,7 +95,7 @@ describe('wrapServerRouteWithSentry', () => { }); await expect(async () => { - await wrappedRouteHandler(getRequestEventMock() as RequestEvent); + await wrappedRouteHandler(getRequestEventMock()); }).rejects.toThrow(); expect(captureExceptionSpy).toHaveBeenCalledWith( @@ -115,7 +115,7 @@ describe('wrapServerRouteWithSentry', () => { }); await expect(async () => { - await wrappedRouteHandler(getRequestEventMock() as RequestEvent); + await wrappedRouteHandler(getRequestEventMock()); }).rejects.toThrow(); expect(captureExceptionSpy).not.toHaveBeenCalled(); @@ -127,7 +127,7 @@ describe('wrapServerRouteWithSentry', () => { }); await expect(async () => { - await wrappedRouteHandler(getRequestEventMock() as RequestEvent); + await wrappedRouteHandler(getRequestEventMock()); }).rejects.toThrow(); expect(captureExceptionSpy).not.toHaveBeenCalled(); diff --git a/packages/vercel-edge/test/wintercg-fetch.test.ts b/packages/vercel-edge/test/wintercg-fetch.test.ts index cb5506a71e05..a413cc7e93da 100644 --- a/packages/vercel-edge/test/wintercg-fetch.test.ts +++ b/packages/vercel-edge/test/wintercg-fetch.test.ts @@ -8,7 +8,7 @@ import { winterCGFetchIntegration } from '../src/integrations/wintercg-fetch'; class FakeClient extends VercelEdgeClient { public getIntegrationByName(name: string): T | undefined { - return name === 'WinterCGFetch' ? (winterCGFetchIntegration() as Integration as T) : undefined; + return name === 'WinterCGFetch' ? (winterCGFetchIntegration() as T) : undefined; } } From 75e502f4529a7f16e0404dba974b35dc6cf3d6df Mon Sep 17 00:00:00 2001 From: Francesco Gringl-Novy Date: Tue, 23 Sep 2025 12:56:42 +0200 Subject: [PATCH 02/12] build: Update TS target to `es2020` everywhere (#17709) We already officially only support es2020, so we may as well also use this as lib & target everywhere. --- .../browser-integration-tests/tsconfig.json | 2 +- .../cloudflare-integration-tests/package.json | 2 +- .../cloudflare-integration-tests/tsconfig.json | 3 ++- .../node-core-integration-tests/tsconfig.json | 2 +- .../node-core-integration-tests/tsconfig.test.json | 2 +- dev-packages/node-integration-tests/tsconfig.json | 2 +- .../node-integration-tests/tsconfig.test.json | 2 +- packages/angular/tsconfig.ngc.json | 4 ++-- packages/aws-serverless/tsconfig.json | 2 +- packages/browser-utils/tsconfig.json | 2 +- packages/browser/tsconfig.json | 2 +- packages/cloudflare/package.json | 2 +- packages/core/tsconfig.test.json | 2 +- packages/gatsby/tsconfig.json | 2 +- packages/google-cloud-serverless/tsconfig.json | 2 +- packages/nextjs/tsconfig.test.json | 2 +- packages/node-core/tsconfig.json | 2 +- packages/node-native/tsconfig.json | 2 +- packages/node/tsconfig.json | 2 +- packages/pino-transport/tsconfig.json | 2 +- packages/profiling-node/tsconfig.json | 2 +- packages/react/tsconfig.json | 2 +- packages/remix/test/integration/tsconfig.json | 4 ++-- packages/remix/tsconfig.test.json | 4 ++-- packages/replay-canvas/tsconfig.json | 2 +- packages/replay-canvas/tsconfig.test.json | 2 +- packages/replay-internal/tsconfig.json | 2 +- packages/replay-worker/tsconfig.json | 2 +- packages/solid/tsconfig.json | 2 +- packages/svelte/tsconfig.json | 2 +- packages/tanstackstart-react/tsconfig.json | 2 +- packages/tanstackstart/tsconfig.json | 2 +- packages/types/tsconfig.json | 2 +- packages/typescript/tsconfig.json | 4 ++-- packages/vercel-edge/tsconfig.json | 2 +- packages/vue/tsconfig.json | 2 +- packages/wasm/tsconfig.json | 2 +- yarn.lock | 13 ++++--------- 38 files changed, 46 insertions(+), 50 deletions(-) diff --git a/dev-packages/browser-integration-tests/tsconfig.json b/dev-packages/browser-integration-tests/tsconfig.json index ecc3b11d5e32..05605881f5d6 100644 --- a/dev-packages/browser-integration-tests/tsconfig.json +++ b/dev-packages/browser-integration-tests/tsconfig.json @@ -2,7 +2,7 @@ "extends": "../../tsconfig.json", "compilerOptions": { - "lib": ["dom", "es2019"], + "lib": ["dom", "ES2020"], "moduleResolution": "node", "noEmit": true, "strict": true, diff --git a/dev-packages/cloudflare-integration-tests/package.json b/dev-packages/cloudflare-integration-tests/package.json index 23b9f99da2f8..3b9bbcce5931 100644 --- a/dev-packages/cloudflare-integration-tests/package.json +++ b/dev-packages/cloudflare-integration-tests/package.json @@ -16,7 +16,7 @@ "@sentry/cloudflare": "10.14.0" }, "devDependencies": { - "@cloudflare/workers-types": "^4.20250708.0", + "@cloudflare/workers-types": "^4.20250922.0", "@sentry-internal/test-utils": "10.14.0", "vitest": "^3.2.4", "wrangler": "4.22.0" diff --git a/dev-packages/cloudflare-integration-tests/tsconfig.json b/dev-packages/cloudflare-integration-tests/tsconfig.json index 07f106daf370..b93dc5f57c50 100644 --- a/dev-packages/cloudflare-integration-tests/tsconfig.json +++ b/dev-packages/cloudflare-integration-tests/tsconfig.json @@ -7,6 +7,7 @@ // Although this seems wrong to include `DOM` here, it's necessary to make // global fetch available in tests in lower Node versions. "lib": ["ES2020"], - "esModuleInterop": true + "esModuleInterop": true, + "types": ["@cloudflare/workers-types"] } } diff --git a/dev-packages/node-core-integration-tests/tsconfig.json b/dev-packages/node-core-integration-tests/tsconfig.json index 1cd6c0aec734..a554f62a0fc6 100644 --- a/dev-packages/node-core-integration-tests/tsconfig.json +++ b/dev-packages/node-core-integration-tests/tsconfig.json @@ -6,7 +6,7 @@ "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"], + "lib": ["DOM", "es2020"], // package-specific options "esModuleInterop": true, "types": ["node"] diff --git a/dev-packages/node-core-integration-tests/tsconfig.test.json b/dev-packages/node-core-integration-tests/tsconfig.test.json index 45a6e39b0054..89f44d610021 100644 --- a/dev-packages/node-core-integration-tests/tsconfig.test.json +++ b/dev-packages/node-core-integration-tests/tsconfig.test.json @@ -6,7 +6,7 @@ "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"], + "lib": ["DOM", "es2020"], // should include all types from `./tsconfig.json` plus types for all test frameworks used "types": ["node"] diff --git a/dev-packages/node-integration-tests/tsconfig.json b/dev-packages/node-integration-tests/tsconfig.json index 1cd6c0aec734..a554f62a0fc6 100644 --- a/dev-packages/node-integration-tests/tsconfig.json +++ b/dev-packages/node-integration-tests/tsconfig.json @@ -6,7 +6,7 @@ "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"], + "lib": ["DOM", "es2020"], // package-specific options "esModuleInterop": true, "types": ["node"] diff --git a/dev-packages/node-integration-tests/tsconfig.test.json b/dev-packages/node-integration-tests/tsconfig.test.json index 45a6e39b0054..89f44d610021 100644 --- a/dev-packages/node-integration-tests/tsconfig.test.json +++ b/dev-packages/node-integration-tests/tsconfig.test.json @@ -6,7 +6,7 @@ "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"], + "lib": ["DOM", "es2020"], // should include all types from `./tsconfig.json` plus types for all test frameworks used "types": ["node"] diff --git a/packages/angular/tsconfig.ngc.json b/packages/angular/tsconfig.ngc.json index df29c7ffdfee..2e86b8e3a6b4 100644 --- a/packages/angular/tsconfig.ngc.json +++ b/packages/angular/tsconfig.ngc.json @@ -5,9 +5,9 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "target": "es2018", + "target": "es2020", "declarationMap": false, - "lib": ["DOM", "ES2018"], + "lib": ["DOM", "es2020"], "baseUrl": "./" }, "angularCompilerOptions": { diff --git a/packages/aws-serverless/tsconfig.json b/packages/aws-serverless/tsconfig.json index fd68e15254db..ea4b60f7b1ae 100644 --- a/packages/aws-serverless/tsconfig.json +++ b/packages/aws-serverless/tsconfig.json @@ -5,7 +5,7 @@ "compilerOptions": { // package-specific options - "target": "ES2018", + "target": "es2020", "resolveJsonModule": true } } diff --git a/packages/browser-utils/tsconfig.json b/packages/browser-utils/tsconfig.json index 36891917c5cc..fd54f069790c 100644 --- a/packages/browser-utils/tsconfig.json +++ b/packages/browser-utils/tsconfig.json @@ -4,6 +4,6 @@ "include": ["src/**/*"], "compilerOptions": { - "lib": ["DOM", "ES2018"] + "lib": ["DOM", "es2020"] } } diff --git a/packages/browser/tsconfig.json b/packages/browser/tsconfig.json index 1ac927bde013..b80e9ddbfaa5 100644 --- a/packages/browser/tsconfig.json +++ b/packages/browser/tsconfig.json @@ -4,6 +4,6 @@ "include": ["src/**/*", "test/loader.js"], "compilerOptions": { - "lib": ["DOM", "ES2018", "WebWorker"] + "lib": ["DOM", "es2020", "WebWorker"] } } diff --git a/packages/cloudflare/package.json b/packages/cloudflare/package.json index 50727b5e9937..f1796797bc30 100644 --- a/packages/cloudflare/package.json +++ b/packages/cloudflare/package.json @@ -61,7 +61,7 @@ } }, "devDependencies": { - "@cloudflare/workers-types": "4.20250620.0", + "@cloudflare/workers-types": "4.20250922.0", "@types/node": "^18.19.1", "wrangler": "4.22.0" }, diff --git a/packages/core/tsconfig.test.json b/packages/core/tsconfig.test.json index 0db9ad3bf16c..5a80d11f7055 100644 --- a/packages/core/tsconfig.test.json +++ b/packages/core/tsconfig.test.json @@ -4,7 +4,7 @@ "include": ["test/**/*", "vite.config.ts"], "compilerOptions": { - "lib": ["DOM", "ES2018"], + "lib": ["DOM", "es2020"], "module": "ESNext", // support dynamic import() // should include all types from `./tsconfig.json` plus types for all test frameworks used "types": ["node"] diff --git a/packages/gatsby/tsconfig.json b/packages/gatsby/tsconfig.json index 77d5f63b9345..756f55907955 100644 --- a/packages/gatsby/tsconfig.json +++ b/packages/gatsby/tsconfig.json @@ -4,7 +4,7 @@ "include": ["src/**/*"], "compilerOptions": { - "lib": ["DOM", "ES2018"], + "lib": ["DOM", "es2020"], // package-specific options "jsx": "react" } diff --git a/packages/google-cloud-serverless/tsconfig.json b/packages/google-cloud-serverless/tsconfig.json index a2731860dfa0..483551a2af71 100644 --- a/packages/google-cloud-serverless/tsconfig.json +++ b/packages/google-cloud-serverless/tsconfig.json @@ -5,7 +5,7 @@ "compilerOptions": { // package-specific options - "target": "ES2018", + "target": "es2020", "resolveJsonModule": true } } diff --git a/packages/nextjs/tsconfig.test.json b/packages/nextjs/tsconfig.test.json index ecd411a65dc3..633c4212a0e9 100644 --- a/packages/nextjs/tsconfig.test.json +++ b/packages/nextjs/tsconfig.test.json @@ -9,7 +9,7 @@ // require for top-level await "module": "Node16", - "target": "es2017", + "target": "es2020", // other package-specific, test-specific options "lib": ["DOM", "ESNext"] diff --git a/packages/node-core/tsconfig.json b/packages/node-core/tsconfig.json index b9683a850600..64d6f3a1b9e0 100644 --- a/packages/node-core/tsconfig.json +++ b/packages/node-core/tsconfig.json @@ -4,7 +4,7 @@ "include": ["src/**/*"], "compilerOptions": { - "lib": ["es2018", "es2020.string"], + "lib": ["es2020"], "module": "Node16" } } diff --git a/packages/node-native/tsconfig.json b/packages/node-native/tsconfig.json index 29acbf3f36e9..b1109af19fdf 100644 --- a/packages/node-native/tsconfig.json +++ b/packages/node-native/tsconfig.json @@ -2,7 +2,7 @@ "extends": "../../tsconfig.json", "compilerOptions": { "module": "esnext", - "lib": ["es2018"], + "lib": ["es2020"], "outDir": "build", "types": ["node"] }, diff --git a/packages/node/tsconfig.json b/packages/node/tsconfig.json index b9683a850600..64d6f3a1b9e0 100644 --- a/packages/node/tsconfig.json +++ b/packages/node/tsconfig.json @@ -4,7 +4,7 @@ "include": ["src/**/*"], "compilerOptions": { - "lib": ["es2018", "es2020.string"], + "lib": ["es2020"], "module": "Node16" } } diff --git a/packages/pino-transport/tsconfig.json b/packages/pino-transport/tsconfig.json index b9683a850600..64d6f3a1b9e0 100644 --- a/packages/pino-transport/tsconfig.json +++ b/packages/pino-transport/tsconfig.json @@ -4,7 +4,7 @@ "include": ["src/**/*"], "compilerOptions": { - "lib": ["es2018", "es2020.string"], + "lib": ["es2020"], "module": "Node16" } } diff --git a/packages/profiling-node/tsconfig.json b/packages/profiling-node/tsconfig.json index 29acbf3f36e9..b1109af19fdf 100644 --- a/packages/profiling-node/tsconfig.json +++ b/packages/profiling-node/tsconfig.json @@ -2,7 +2,7 @@ "extends": "../../tsconfig.json", "compilerOptions": { "module": "esnext", - "lib": ["es2018"], + "lib": ["es2020"], "outDir": "build", "types": ["node"] }, diff --git a/packages/react/tsconfig.json b/packages/react/tsconfig.json index 67260f799e86..41ff3c42258e 100644 --- a/packages/react/tsconfig.json +++ b/packages/react/tsconfig.json @@ -4,7 +4,7 @@ "include": ["src/**/*"], "compilerOptions": { - "lib": ["DOM", "ES2018"], + "lib": ["DOM", "es2020"], // package-specific options "esModuleInterop": true, "jsx": "react" diff --git a/packages/remix/test/integration/tsconfig.json b/packages/remix/test/integration/tsconfig.json index 1ab42867a9dc..e269d26457d3 100644 --- a/packages/remix/test/integration/tsconfig.json +++ b/packages/remix/test/integration/tsconfig.json @@ -1,13 +1,13 @@ { "include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"], "compilerOptions": { - "lib": ["DOM", "DOM.Iterable", "ES2019"], + "lib": ["DOM", "DOM.Iterable", "ES2020"], "isolatedModules": true, "esModuleInterop": true, "jsx": "react-jsx", "moduleResolution": "node", "resolveJsonModule": true, - "target": "ES2019", + "target": "es2020", "strict": true, "allowJs": true, "forceConsistentCasingInFileNames": true, diff --git a/packages/remix/tsconfig.test.json b/packages/remix/tsconfig.test.json index bc444a444917..f62d7ff34d09 100644 --- a/packages/remix/tsconfig.test.json +++ b/packages/remix/tsconfig.test.json @@ -4,11 +4,11 @@ "include": ["test/**/*", "vitest.config.ts", "vitest.config.unit.ts"], "compilerOptions": { - "lib": ["DOM", "ES2018"], + "lib": ["DOM", "es2020"], "types": ["node"], // Required for top-level await in tests "module": "Node16", - "target": "ES2017", + "target": "es2020", "esModuleInterop": true } diff --git a/packages/replay-canvas/tsconfig.json b/packages/replay-canvas/tsconfig.json index cd1b8207ea06..4f9aae5f3994 100644 --- a/packages/replay-canvas/tsconfig.json +++ b/packages/replay-canvas/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "lib": ["DOM", "ES2018"], + "lib": ["DOM", "es2020"], "module": "esnext" }, "include": ["src/**/*.ts"] diff --git a/packages/replay-canvas/tsconfig.test.json b/packages/replay-canvas/tsconfig.test.json index f4e8a1624f08..ccc44803a599 100644 --- a/packages/replay-canvas/tsconfig.test.json +++ b/packages/replay-canvas/tsconfig.test.json @@ -4,7 +4,7 @@ "include": ["test/**/*.ts", "vite.config.ts"], "compilerOptions": { - "lib": ["DOM", "ES2018"], + "lib": ["DOM", "es2020"], "types": ["node"], "esModuleInterop": true, "allowJs": true, diff --git a/packages/replay-internal/tsconfig.json b/packages/replay-internal/tsconfig.json index cd1b8207ea06..4f9aae5f3994 100644 --- a/packages/replay-internal/tsconfig.json +++ b/packages/replay-internal/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "lib": ["DOM", "ES2018"], + "lib": ["DOM", "es2020"], "module": "esnext" }, "include": ["src/**/*.ts"] diff --git a/packages/replay-worker/tsconfig.json b/packages/replay-worker/tsconfig.json index f3db9c003516..24cce469ccc3 100644 --- a/packages/replay-worker/tsconfig.json +++ b/packages/replay-worker/tsconfig.json @@ -4,7 +4,7 @@ "module": "esnext", "lib": ["webworker", "scripthost"], "esModuleInterop": true, - "target": "es2018", + "target": "es2020", "strictPropertyInitialization": false }, "include": ["src/**/*.ts"] diff --git a/packages/solid/tsconfig.json b/packages/solid/tsconfig.json index 36891917c5cc..fd54f069790c 100644 --- a/packages/solid/tsconfig.json +++ b/packages/solid/tsconfig.json @@ -4,6 +4,6 @@ "include": ["src/**/*"], "compilerOptions": { - "lib": ["DOM", "ES2018"] + "lib": ["DOM", "es2020"] } } diff --git a/packages/svelte/tsconfig.json b/packages/svelte/tsconfig.json index 36891917c5cc..fd54f069790c 100644 --- a/packages/svelte/tsconfig.json +++ b/packages/svelte/tsconfig.json @@ -4,6 +4,6 @@ "include": ["src/**/*"], "compilerOptions": { - "lib": ["DOM", "ES2018"] + "lib": ["DOM", "es2020"] } } diff --git a/packages/tanstackstart-react/tsconfig.json b/packages/tanstackstart-react/tsconfig.json index 20cf507e5203..ff4cadba841a 100644 --- a/packages/tanstackstart-react/tsconfig.json +++ b/packages/tanstackstart-react/tsconfig.json @@ -2,7 +2,7 @@ "extends": "../../tsconfig.json", "include": ["src/**/*"], "compilerOptions": { - "lib": ["es2018", "es2020.string"], + "lib": ["es2020"], "module": "Node16" } } diff --git a/packages/tanstackstart/tsconfig.json b/packages/tanstackstart/tsconfig.json index 20cf507e5203..ff4cadba841a 100644 --- a/packages/tanstackstart/tsconfig.json +++ b/packages/tanstackstart/tsconfig.json @@ -2,7 +2,7 @@ "extends": "../../tsconfig.json", "include": ["src/**/*"], "compilerOptions": { - "lib": ["es2018", "es2020.string"], + "lib": ["es2020"], "module": "Node16" } } diff --git a/packages/types/tsconfig.json b/packages/types/tsconfig.json index 89a9b9e0e2fe..e712bc6f77a7 100644 --- a/packages/types/tsconfig.json +++ b/packages/types/tsconfig.json @@ -4,6 +4,6 @@ "include": ["src/**/*"], "compilerOptions": { - "lib": ["es2018"] + "lib": ["es2020"] } } diff --git a/packages/typescript/tsconfig.json b/packages/typescript/tsconfig.json index 1f6d405fb00b..8dd1e93e2c6e 100644 --- a/packages/typescript/tsconfig.json +++ b/packages/typescript/tsconfig.json @@ -7,7 +7,7 @@ "importHelpers": true, "inlineSources": true, "isolatedModules": true, - "lib": ["ES2018"], + "lib": ["es2020"], "moduleResolution": "node", "noErrorTruncation": true, "noFallthroughCasesInSwitch": true, @@ -18,7 +18,7 @@ "sourceMap": true, "strict": true, "strictBindCallApply": false, - "target": "es2018", + "target": "es2020", "noUncheckedIndexedAccess": true } } diff --git a/packages/vercel-edge/tsconfig.json b/packages/vercel-edge/tsconfig.json index fc8caeb42e65..685e6c58c06b 100644 --- a/packages/vercel-edge/tsconfig.json +++ b/packages/vercel-edge/tsconfig.json @@ -7,7 +7,7 @@ // Note: using `dom` here is inaccurate for the vercel-edge runtime, but needed // because @edge-runtime/types does not type things like fetch or RequestInit // ref: https://github.com/vercel/edge-runtime/issues/506 - "lib": ["DOM", "ES2018"], + "lib": ["DOM", "es2020"], "types": ["@edge-runtime/types"] } } diff --git a/packages/vue/tsconfig.json b/packages/vue/tsconfig.json index 36891917c5cc..fd54f069790c 100644 --- a/packages/vue/tsconfig.json +++ b/packages/vue/tsconfig.json @@ -4,6 +4,6 @@ "include": ["src/**/*"], "compilerOptions": { - "lib": ["DOM", "ES2018"] + "lib": ["DOM", "es2020"] } } diff --git a/packages/wasm/tsconfig.json b/packages/wasm/tsconfig.json index 36891917c5cc..fd54f069790c 100644 --- a/packages/wasm/tsconfig.json +++ b/packages/wasm/tsconfig.json @@ -4,6 +4,6 @@ "include": ["src/**/*"], "compilerOptions": { - "lib": ["DOM", "ES2018"] + "lib": ["DOM", "es2020"] } } diff --git a/yarn.lock b/yarn.lock index 46e96703fc7e..0dfdce6d5106 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2701,15 +2701,10 @@ resolved "https://registry.yarnpkg.com/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20250617.0.tgz#6b4397fcf01c7b8a547152761cc3bcd63e173a58" integrity sha512-XWM/6sagDrO0CYDKhXhPjM23qusvIN1ju9ZEml6gOQs8tNOFnq6Cn6X9FAmnyapRFCGUSEC3HZYJAm7zwVKaMA== -"@cloudflare/workers-types@4.20250620.0": - version "4.20250620.0" - resolved "https://registry.yarnpkg.com/@cloudflare/workers-types/-/workers-types-4.20250620.0.tgz#a22e635a631212963b84e315191614b20c4ad317" - integrity sha512-EVvRB/DJEm6jhdKg+A4Qm4y/ry1cIvylSgSO3/f/Bv161vldDRxaXM2YoQQWFhLOJOw0qtrHsKOD51KYxV1XCw== - -"@cloudflare/workers-types@^4.20250708.0": - version "4.20250726.0" - resolved "https://registry.yarnpkg.com/@cloudflare/workers-types/-/workers-types-4.20250726.0.tgz#2bcd78bc5e26aa222d4a8f8cf9edb8f5f3427bb3" - integrity sha512-NtM1yVBKJFX4LgSoZkVU0EDhWWvSb1vt6REO+uMYZRgx1HAfQz9GDN6bBB0B+fm2ZIxzt6FzlDbmrXpGJ2M/4Q== +"@cloudflare/workers-types@4.20250922.0", "@cloudflare/workers-types@^4.20250922.0": + version "4.20250922.0" + resolved "https://registry.yarnpkg.com/@cloudflare/workers-types/-/workers-types-4.20250922.0.tgz#a159fbf3bb785fa85b473ecfaa8c501525827885" + integrity sha512-BaqlKnVc0Xzqm9xt3TC4v0yB9EHy5vVqpiWz+DAsbEmdcpUbqdBschvI9502p6FgFbZElD7XcxTEeViXLsoO0A== "@cnakazawa/watch@^1.0.3": version "1.0.4" From 73f356d87db02a1cad0c7ab25702146f64128380 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Tue, 23 Sep 2025 14:00:07 +0200 Subject: [PATCH 03/12] feat(browser): Add `setActiveSpanInBrowser` to set an active span in the browser (#17714) This PR adds a long-requested feature to the browser SDKs (only!): Making an inactive span active. We do this to enable use cases where having a span only being active in the callback is not practical (see https://github.com/getsentry/sentry-javascript/issues/13495 for examples). There are a couple of caveats to this feature: - This on purpose is only exported from the browser SDKs. We cannot support this in Node, due to OTel not allowing for a similar hack. Frankly, it also makes no sense in Node-based SDKs. - Calling `setActiveSpanInBrowser ` on a nested child span, will make that child span the active span as long as it is active. However, due to `parentSpanIsAlwaysRootSpan` defaulting to `true` any child span of the active child span, will still be parented to the root span. By setting `parentSpanIsAlwaysRootSpan: false`, the span hierarchy is respected and child spans are correctly parented to the active span. Note that this cannot be guaranteed to work perfectly, due to missing async context in the browser. See tests for the `parentSpanIsAlwaysRootSpan` behaviour. - A span once set active, cannot be set as inactive again. It will remain active until it is ended or until another span is set active. In the latter case, once that span ends, the initial span will be set as active again until it ends. This is reflected in the types where we by design only allow `Span` to be passed to `setActiveSpanInBrowser `. Technically, `setActiveSpanInBrowser` uses `_setSpanForScope` which I decided to re-export from core as `_INTERNAL_setSpanForScope`, similarly to how we do it with logs APIs. ### Usage This example shows one of the most frequent use cases where having an active, callback-unbound span is useful: ```js function instrumentMyRouter() { let routeSpan; on('routeStart', (from, to) => { routeSpan = Sentry.startInactiveSpan({name: `/${from} -> /${to}`}); Sentry. setActiveSpanInBrowser(rootSpan); }); // any span started in the meantime (e.g. fetch requests) will be // automatically parented to `routeSpan` on('routeEnd', () => { // automatically removes the span from the scope routeSpan.end(); }) } ``` closes https://github.com/getsentry/sentry-javascript/issues/13495 --- .../tracing/setSpanActive/default/init.js | 8 ++ .../tracing/setSpanActive/default/subject.js | 14 +++ .../tracing/setSpanActive/default/test.ts | 35 ++++++++ .../nested-parentAlwaysRoot/init.js | 9 ++ .../nested-parentAlwaysRoot/subject.js | 22 +++++ .../nested-parentAlwaysRoot/test.ts | 57 ++++++++++++ .../tracing/setSpanActive/nested/init.js | 8 ++ .../tracing/setSpanActive/nested/subject.js | 22 +++++ .../tracing/setSpanActive/nested/test.ts | 52 +++++++++++ .../index.bundle.tracing.replay.feedback.ts | 1 + .../src/index.bundle.tracing.replay.ts | 2 +- packages/browser/src/index.bundle.tracing.ts | 1 + packages/browser/src/index.ts | 2 + packages/browser/src/tracing/setActiveSpan.ts | 64 +++++++++++++ .../test/tracing/setActiveSpan.test.ts | 90 +++++++++++++++++++ packages/core/src/index.ts | 1 + 16 files changed, 387 insertions(+), 1 deletion(-) create mode 100644 dev-packages/browser-integration-tests/suites/tracing/setSpanActive/default/init.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/setSpanActive/default/subject.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/setSpanActive/default/test.ts create mode 100644 dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested-parentAlwaysRoot/init.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested-parentAlwaysRoot/subject.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested-parentAlwaysRoot/test.ts create mode 100644 dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested/init.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested/subject.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested/test.ts create mode 100644 packages/browser/src/tracing/setActiveSpan.ts create mode 100644 packages/browser/test/tracing/setActiveSpan.test.ts diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/default/init.js b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/default/init.js new file mode 100644 index 000000000000..7c200c542c56 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/default/init.js @@ -0,0 +1,8 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + tracesSampleRate: 1, +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/default/subject.js b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/default/subject.js new file mode 100644 index 000000000000..0ce39588eb1b --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/default/subject.js @@ -0,0 +1,14 @@ +const checkoutSpan = Sentry.startInactiveSpan({ name: 'checkout-flow' }); +Sentry.setActiveSpanInBrowser(checkoutSpan); + +Sentry.startSpan({ name: 'checkout-step-1' }, () => { + Sentry.startSpan({ name: 'checkout-step-1-1' }, () => { + // ... ` + }); +}); + +Sentry.startSpan({ name: 'checkout-step-2' }, () => { + // ... ` +}); + +checkoutSpan.end(); diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/default/test.ts b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/default/test.ts new file mode 100644 index 000000000000..080ebbfa6e84 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/default/test.ts @@ -0,0 +1,35 @@ +import { expect } from '@playwright/test'; +import { sentryTest } from '../../../../utils/fixtures'; +import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; + +sentryTest('sets an inactive span active and adds child spans to it', async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const req = waitForTransactionRequest(page, e => e.transaction === 'checkout-flow'); + + const url = await getLocalTestUrl({ testDir: __dirname }); + await page.goto(url); + + const checkoutEvent = envelopeRequestParser(await req); + const checkoutSpanId = checkoutEvent.contexts?.trace?.span_id; + expect(checkoutSpanId).toMatch(/[a-f0-9]{16}/); + + expect(checkoutEvent.spans).toHaveLength(3); + + const checkoutStep1 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-1'); + const checkoutStep11 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-1-1'); + const checkoutStep2 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-2'); + + expect(checkoutStep1).toBeDefined(); + expect(checkoutStep11).toBeDefined(); + expect(checkoutStep2).toBeDefined(); + + expect(checkoutStep1?.parent_span_id).toBe(checkoutSpanId); + expect(checkoutStep2?.parent_span_id).toBe(checkoutSpanId); + + // despite 1-1 being called within 1, it's still parented to the root span + // due to this being default behaviour in browser environments + expect(checkoutStep11?.parent_span_id).toBe(checkoutSpanId); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested-parentAlwaysRoot/init.js b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested-parentAlwaysRoot/init.js new file mode 100644 index 000000000000..375a16cbf005 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested-parentAlwaysRoot/init.js @@ -0,0 +1,9 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + tracesSampleRate: 1, + parentSpanIsAlwaysRootSpan: false, +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested-parentAlwaysRoot/subject.js b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested-parentAlwaysRoot/subject.js new file mode 100644 index 000000000000..dc601cbf4d30 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested-parentAlwaysRoot/subject.js @@ -0,0 +1,22 @@ +const checkoutSpan = Sentry.startInactiveSpan({ name: 'checkout-flow' }); +Sentry.setActiveSpanInBrowser(checkoutSpan); + +Sentry.startSpan({ name: 'checkout-step-1' }, () => {}); + +const checkoutStep2 = Sentry.startInactiveSpan({ name: 'checkout-step-2' }); +Sentry.setActiveSpanInBrowser(checkoutStep2); + +Sentry.startSpan({ name: 'checkout-step-2-1' }, () => { + // ... ` +}); +checkoutStep2.end(); + +Sentry.startSpan({ name: 'checkout-step-3' }, () => {}); + +checkoutSpan.end(); + +Sentry.startSpan({ name: 'post-checkout' }, () => { + Sentry.startSpan({ name: 'post-checkout-1' }, () => { + // ... ` + }); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested-parentAlwaysRoot/test.ts b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested-parentAlwaysRoot/test.ts new file mode 100644 index 000000000000..2270b470123b --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested-parentAlwaysRoot/test.ts @@ -0,0 +1,57 @@ +import { expect } from '@playwright/test'; +import { sentryTest } from '../../../../utils/fixtures'; +import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; + +sentryTest( + 'nested calls to setActiveSpanInBrowser with parentSpanIsAlwaysRootSpan=false result in correct parenting', + async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const req = waitForTransactionRequest(page, e => e.transaction === 'checkout-flow'); + const postCheckoutReq = waitForTransactionRequest(page, e => e.transaction === 'post-checkout'); + + const url = await getLocalTestUrl({ testDir: __dirname }); + await page.goto(url); + + const checkoutEvent = envelopeRequestParser(await req); + const postCheckoutEvent = envelopeRequestParser(await postCheckoutReq); + + const checkoutSpanId = checkoutEvent.contexts?.trace?.span_id; + const postCheckoutSpanId = postCheckoutEvent.contexts?.trace?.span_id; + + expect(checkoutSpanId).toMatch(/[a-f0-9]{16}/); + expect(postCheckoutSpanId).toMatch(/[a-f0-9]{16}/); + + expect(checkoutEvent.spans).toHaveLength(4); + expect(postCheckoutEvent.spans).toHaveLength(1); + + const checkoutStep1 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-1'); + const checkoutStep2 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-2'); + const checkoutStep21 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-2-1'); + const checkoutStep3 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-3'); + + expect(checkoutStep1).toBeDefined(); + expect(checkoutStep2).toBeDefined(); + expect(checkoutStep21).toBeDefined(); + expect(checkoutStep3).toBeDefined(); + + expect(checkoutStep1?.parent_span_id).toBe(checkoutSpanId); + expect(checkoutStep2?.parent_span_id).toBe(checkoutSpanId); + + // with parentSpanIsAlwaysRootSpan=false, 2-1 is parented to 2 because + // 2 was the active span when 2-1 was started + expect(checkoutStep21?.parent_span_id).toBe(checkoutStep2?.span_id); + + // since the parent of three is `checkoutSpan`, we correctly reset + // the active span to `checkoutSpan` after 2 ended + expect(checkoutStep3?.parent_span_id).toBe(checkoutSpanId); + + // post-checkout trace is started as a new trace because ending checkoutSpan removes the active + // span on the scope + const postCheckoutStep1 = postCheckoutEvent.spans?.find(s => s.description === 'post-checkout-1'); + expect(postCheckoutStep1).toBeDefined(); + expect(postCheckoutStep1?.parent_span_id).toBe(postCheckoutSpanId); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested/init.js b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested/init.js new file mode 100644 index 000000000000..7c200c542c56 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested/init.js @@ -0,0 +1,8 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + tracesSampleRate: 1, +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested/subject.js b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested/subject.js new file mode 100644 index 000000000000..dc601cbf4d30 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested/subject.js @@ -0,0 +1,22 @@ +const checkoutSpan = Sentry.startInactiveSpan({ name: 'checkout-flow' }); +Sentry.setActiveSpanInBrowser(checkoutSpan); + +Sentry.startSpan({ name: 'checkout-step-1' }, () => {}); + +const checkoutStep2 = Sentry.startInactiveSpan({ name: 'checkout-step-2' }); +Sentry.setActiveSpanInBrowser(checkoutStep2); + +Sentry.startSpan({ name: 'checkout-step-2-1' }, () => { + // ... ` +}); +checkoutStep2.end(); + +Sentry.startSpan({ name: 'checkout-step-3' }, () => {}); + +checkoutSpan.end(); + +Sentry.startSpan({ name: 'post-checkout' }, () => { + Sentry.startSpan({ name: 'post-checkout-1' }, () => { + // ... ` + }); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested/test.ts b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested/test.ts new file mode 100644 index 000000000000..094bb0ed3dd8 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested/test.ts @@ -0,0 +1,52 @@ +import { expect } from '@playwright/test'; +import { sentryTest } from '../../../../utils/fixtures'; +import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; + +sentryTest( + 'nested calls to setActiveSpanInBrowser still parent to root span by default', + async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const req = waitForTransactionRequest(page, e => e.transaction === 'checkout-flow'); + const postCheckoutReq = waitForTransactionRequest(page, e => e.transaction === 'post-checkout'); + + const url = await getLocalTestUrl({ testDir: __dirname }); + await page.goto(url); + + const checkoutEvent = envelopeRequestParser(await req); + const postCheckoutEvent = envelopeRequestParser(await postCheckoutReq); + + const checkoutSpanId = checkoutEvent.contexts?.trace?.span_id; + const postCheckoutSpanId = postCheckoutEvent.contexts?.trace?.span_id; + + expect(checkoutSpanId).toMatch(/[a-f0-9]{16}/); + expect(postCheckoutSpanId).toMatch(/[a-f0-9]{16}/); + + expect(checkoutEvent.spans).toHaveLength(4); + expect(postCheckoutEvent.spans).toHaveLength(1); + + const checkoutStep1 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-1'); + const checkoutStep2 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-2'); + const checkoutStep21 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-2-1'); + const checkoutStep3 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-3'); + + expect(checkoutStep1).toBeDefined(); + expect(checkoutStep2).toBeDefined(); + expect(checkoutStep21).toBeDefined(); + expect(checkoutStep3).toBeDefined(); + + expect(checkoutStep1?.parent_span_id).toBe(checkoutSpanId); + expect(checkoutStep2?.parent_span_id).toBe(checkoutSpanId); + expect(checkoutStep3?.parent_span_id).toBe(checkoutSpanId); + + // despite 2-1 being called within 2 AND setting 2 as active span, it's still parented to the + // root span due to this being default behaviour in browser environments + expect(checkoutStep21?.parent_span_id).toBe(checkoutSpanId); + + const postCheckoutStep1 = postCheckoutEvent.spans?.find(s => s.description === 'post-checkout-1'); + expect(postCheckoutStep1).toBeDefined(); + expect(postCheckoutStep1?.parent_span_id).toBe(postCheckoutSpanId); + }, +); diff --git a/packages/browser/src/index.bundle.tracing.replay.feedback.ts b/packages/browser/src/index.bundle.tracing.replay.feedback.ts index fc805c82a4e5..7aa4b3ae778c 100644 --- a/packages/browser/src/index.bundle.tracing.replay.feedback.ts +++ b/packages/browser/src/index.bundle.tracing.replay.feedback.ts @@ -22,6 +22,7 @@ export { startBrowserTracingNavigationSpan, startBrowserTracingPageLoadSpan, } from './tracing/browserTracingIntegration'; +export { setActiveSpanInBrowser } from './tracing/setActiveSpan'; export { reportPageLoaded } from './tracing/reportPageLoaded'; diff --git a/packages/browser/src/index.bundle.tracing.replay.ts b/packages/browser/src/index.bundle.tracing.replay.ts index f77d2774c36e..3dc858d69cb5 100644 --- a/packages/browser/src/index.bundle.tracing.replay.ts +++ b/packages/browser/src/index.bundle.tracing.replay.ts @@ -22,8 +22,8 @@ export { startBrowserTracingNavigationSpan, startBrowserTracingPageLoadSpan, } from './tracing/browserTracingIntegration'; - export { reportPageLoaded } from './tracing/reportPageLoaded'; +export { setActiveSpanInBrowser } from './tracing/setActiveSpan'; export { feedbackIntegrationShim as feedbackAsyncIntegration, feedbackIntegrationShim as feedbackIntegration }; diff --git a/packages/browser/src/index.bundle.tracing.ts b/packages/browser/src/index.bundle.tracing.ts index c32e806f1de8..62259b92ce7e 100644 --- a/packages/browser/src/index.bundle.tracing.ts +++ b/packages/browser/src/index.bundle.tracing.ts @@ -22,6 +22,7 @@ export { startBrowserTracingNavigationSpan, startBrowserTracingPageLoadSpan, } from './tracing/browserTracingIntegration'; +export { setActiveSpanInBrowser } from './tracing/setActiveSpan'; export { reportPageLoaded } from './tracing/reportPageLoaded'; diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index f2a3e7dc179c..5e9924fe6da5 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -40,6 +40,8 @@ export { startBrowserTracingPageLoadSpan, } from './tracing/browserTracingIntegration'; export { reportPageLoaded } from './tracing/reportPageLoaded'; +export { setActiveSpanInBrowser } from './tracing/setActiveSpan'; + export type { RequestInstrumentationOptions } from './tracing/request'; export { registerSpanErrorInstrumentation, diff --git a/packages/browser/src/tracing/setActiveSpan.ts b/packages/browser/src/tracing/setActiveSpan.ts new file mode 100644 index 000000000000..5e3b537d4b6d --- /dev/null +++ b/packages/browser/src/tracing/setActiveSpan.ts @@ -0,0 +1,64 @@ +import type { Span } from '@sentry/core'; +import { _INTERNAL_setSpanForScope, getActiveSpan, getCurrentScope } from '@sentry/core'; + +/** + * Sets an inactive span active on the current scope. + * + * This is useful in browser applications, if you want to create a span that cannot be finished + * within its callback. Any spans started while the given span is active, will be children of the span. + * + * If there already was an active span on the scope prior to calling this function, it is replaced + * with the given span and restored after the span ended. Otherwise, the span will simply be + * removed, resulting in no active span on the scope. + * + * IMPORTANT: This function can ONLY be used in the browser! Calling this function in a server + * environment (for example in a server-side rendered component) will result in undefined behaviour + * and is not supported. + * You MUST call `span.end()` manually, otherwise the span will never be finished. + * + * @example + * ```js + * let checkoutSpan; + * + * on('checkoutStarted', () => { + * checkoutSpan = Sentry.startInactiveSpan({ name: 'checkout-flow' }); + * Sentry.setActiveSpanInBrowser(checkoutSpan); + * }) + * + * // during this time, any spans started will be children of `checkoutSpan`: + * Sentry.startSpan({ name: 'checkout-step-1' }, () => { + * // ... ` + * }) + * + * on('checkoutCompleted', () => { + * checkoutSpan?.end(); + * }) + * ``` + * + * @param span - the span to set active + */ +export function setActiveSpanInBrowser(span: Span): void { + const maybePreviousActiveSpan = getActiveSpan(); + + // If the span is already active, there's no need to double-patch or set it again. + // This also guards against users (for whatever reason) calling setActiveSpanInBrowser on SDK-started + // idle spans like pageload or navigation spans. These will already be handled correctly by the SDK. + // For nested situations, we have to double-patch to ensure we restore the correct previous span (see tests) + if (maybePreviousActiveSpan === span) { + return; + } + + const scope = getCurrentScope(); + + // Putting a small patch onto the span.end method to ensure we + // remove the span from the scope when it ends. + // eslint-disable-next-line @typescript-eslint/unbound-method + span.end = new Proxy(span.end, { + apply(target, thisArg, args: Parameters) { + _INTERNAL_setSpanForScope(scope, maybePreviousActiveSpan); + return Reflect.apply(target, thisArg, args); + }, + }); + + _INTERNAL_setSpanForScope(scope, span); +} diff --git a/packages/browser/test/tracing/setActiveSpan.test.ts b/packages/browser/test/tracing/setActiveSpan.test.ts new file mode 100644 index 000000000000..d3c7ea79cf67 --- /dev/null +++ b/packages/browser/test/tracing/setActiveSpan.test.ts @@ -0,0 +1,90 @@ +import { getActiveSpan, SentrySpan } from '@sentry/core'; +import { describe, expect, it } from 'vitest'; +import { setActiveSpanInBrowser } from '../../src'; + +describe('setActiveSpanInBrowser', () => { + it('sets the passed span active the current scope', () => { + const span = new SentrySpan({ name: 'test' }); + setActiveSpanInBrowser(span); + expect(getActiveSpan()).toBe(span); + + span.end(); + expect(getActiveSpan()).toBeUndefined(); + }); + + it('handles multiple calls to setActiveSpanInBrowser', () => { + const span = new SentrySpan({ name: 'test' }); + setActiveSpanInBrowser(span); + setActiveSpanInBrowser(span); + setActiveSpanInBrowser(span); + expect(getActiveSpan()).toBe(span); + + span.end(); + expect(getActiveSpan()).toBeUndefined(); + }); + + it('handles changing active span while span is running', () => { + const span = new SentrySpan({ name: 'test' }); + setActiveSpanInBrowser(span); + + expect(getActiveSpan()).toBe(span); + + const span2 = new SentrySpan({ name: 'test2' }); + setActiveSpanInBrowser(span2); + expect(getActiveSpan()).toBe(span2); + + span2.end(); + expect(getActiveSpan()).toBe(span); + + span.end(); + expect(getActiveSpan()).toBeUndefined(); + }); + + it('handles multiple span.end calls', () => { + const span = new SentrySpan({ name: 'test' }); + setActiveSpanInBrowser(span); + setActiveSpanInBrowser(span); + + expect(getActiveSpan()).toBe(span); + + const span2 = new SentrySpan({ name: 'test2' }); + setActiveSpanInBrowser(span2); + expect(getActiveSpan()).toBe(span2); + + span2.end(); + span2.end(); + span2.end(); + expect(getActiveSpan()).toBe(span); + + span.end(); + span.end(); + expect(getActiveSpan()).toBeUndefined(); + }); + + it('handles nested activation of the same span', () => { + const span1 = new SentrySpan({ name: 'test1', sampled: true }); + const span2 = new SentrySpan({ name: 'test2', sampled: true }); + expect(span1.isRecording()).toBe(true); + expect(span2.isRecording()).toBe(true); + + setActiveSpanInBrowser(span1); + expect(getActiveSpan()).toBe(span1); + + setActiveSpanInBrowser(span2); + expect(getActiveSpan()).toBe(span2); + + setActiveSpanInBrowser(span1); + expect(getActiveSpan()).toBe(span1); + + span2.end(); + expect(getActiveSpan()).toBe(span1); + expect(span2.isRecording()).toBe(false); + expect(span1.isRecording()).toBe(true); + + span1.end(); + expect(getActiveSpan()).toBeUndefined(); + + expect(span1.isRecording()).toBe(false); + expect(span2.isRecording()).toBe(false); + }); +}); diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 8a5566948f6e..e0daefd54d76 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -81,6 +81,7 @@ export { spanTimeInputToSeconds, updateSpanName, } from './utils/spanUtils'; +export { _setSpanForScope as _INTERNAL_setSpanForScope } from './utils/spanOnScope'; export { parseSampleRate } from './utils/parseSampleRate'; export { applySdkMetadata } from './utils/sdkMetadata'; export { getTraceData } from './utils/traceData'; From 01fa69d5f2f39d1c8efa1239891bec021d1247d2 Mon Sep 17 00:00:00 2001 From: Yuki Karibayashi Date: Tue, 23 Sep 2025 23:03:58 +0900 Subject: [PATCH 04/12] feat(node): Add instrumentation for hono handler (#17428) ## Summary This PR enhances the Hono integration by adding comprehensive handler instrumentation, error handling capabilities, and thorough test coverage. The changes build upon the basic Hono integration to provide a complete tracing and error monitoring solution. ## New Features - Handler Instrumentation: Added instrumentation for Hono handlers and middleware, providing detailed tracing capabilities - Error Handler: Implemented setupHonoErrorHandler() function to capture and report errors to Sentry with configurable error filtering - Public API: Added Hono integration to the main package exports, making it available as @sentry/node - Tracing Module: Included Hono integration in the tracing integrations index ## Bug Fixes - CJS Compatibility: Fixed an issue where applying patches failed in CommonJS environments - Type Corrections: Fixed incorrect MiddlewareHandler type definition to ensure proper TypeScript support ## Implementation Details - Instrumentation: Created HonoInstrumentation class that wraps Hono middleware handlers via class extension instead of function replacement for better compatibility - Type Definitions: Added comprehensive TypeScript type definitions vendored from Hono's official types - Constants: Defined Hono-specific attribute names for OpenTelemetry integration - CJS Compatibility: Fixed patching issues in CommonJS environments ## Testing - Integration Tests: Added comprehensive test suite covering: - ESM and CJS compatibility - Multiple HTTP methods (GET, POST, PUT, DELETE, PATCH) - Various route patterns (sync/async, different paths) - Middleware and handler instrumentation verification - Error handling scenarios - Span attribute validation ## Related Issue close #15260 --- .../node-integration-tests/package.json | 2 + .../suites/tracing/hono/instrument.mjs | 9 + .../suites/tracing/hono/scenario.mjs | 196 ++++++++++++++ .../suites/tracing/hono/test.ts | 251 ++++++++++++++++++ .../node-integration-tests/utils/runner.ts | 6 +- packages/astro/src/index.server.ts | 2 + packages/aws-serverless/src/index.ts | 2 + packages/bun/src/index.ts | 2 + packages/google-cloud-serverless/src/index.ts | 2 + .../node-core/src/utils/ensureIsWrapped.ts | 2 +- packages/node/src/index.ts | 1 + .../integrations/tracing/hono/constants.ts | 13 + .../src/integrations/tracing/hono/index.ts | 123 ++++++++- .../tracing/hono/instrumentation.ts | 193 +++++++++++++- .../src/integrations/tracing/hono/types.ts | 5 +- .../node/src/integrations/tracing/index.ts | 3 + yarn.lock | 10 + 17 files changed, 801 insertions(+), 21 deletions(-) create mode 100644 dev-packages/node-integration-tests/suites/tracing/hono/instrument.mjs create mode 100644 dev-packages/node-integration-tests/suites/tracing/hono/scenario.mjs create mode 100644 dev-packages/node-integration-tests/suites/tracing/hono/test.ts create mode 100644 packages/node/src/integrations/tracing/hono/constants.ts diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index bf183115df5f..e8ee0afb9f5b 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -27,6 +27,7 @@ "@aws-sdk/client-s3": "^3.552.0", "@google/genai": "^1.20.0", "@hapi/hapi": "^21.3.10", + "@hono/node-server": "^1.19.4", "@nestjs/common": "11.1.3", "@nestjs/core": "11.1.3", "@nestjs/platform-express": "11.1.3", @@ -49,6 +50,7 @@ "express": "^4.21.1", "generic-pool": "^3.9.0", "graphql": "^16.3.0", + "hono": "^4.9.8", "http-terminator": "^3.2.0", "ioredis": "^5.4.1", "kafkajs": "2.2.4", diff --git a/dev-packages/node-integration-tests/suites/tracing/hono/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/hono/instrument.mjs new file mode 100644 index 000000000000..46a27dd03b74 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/hono/instrument.mjs @@ -0,0 +1,9 @@ +import * as Sentry from '@sentry/node'; +import { loggingTransport } from '@sentry-internal/node-integration-tests'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, + transport: loggingTransport, +}); diff --git a/dev-packages/node-integration-tests/suites/tracing/hono/scenario.mjs b/dev-packages/node-integration-tests/suites/tracing/hono/scenario.mjs new file mode 100644 index 000000000000..7ef73c3d22c3 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/hono/scenario.mjs @@ -0,0 +1,196 @@ +import { serve } from '@hono/node-server'; +import * as Sentry from '@sentry/node'; +import { sendPortToRunner } from '@sentry-internal/node-core-integration-tests'; +import { Hono } from 'hono'; +import { HTTPException } from 'hono/http-exception'; + +const app = new Hono(); + +Sentry.setupHonoErrorHandler(app); + +// Global middleware to capture all requests +app.use(async function global(c, next) { + await next(); +}); + +const basePaths = ['/sync', '/async']; +const methods = ['get', 'post', 'put', 'delete', 'patch']; + +basePaths.forEach(basePath => { + // Sub-path middleware to capture all requests under the basePath + app.use(`${basePath}/*`, async function base(c, next) { + await next(); + }); + + const baseApp = new Hono(); + methods.forEach(method => { + baseApp[method]('/', c => { + const response = c.text('response 200'); + if (basePath === '/sync') return response; + return Promise.resolve(response); + }); + + baseApp[method]( + '/middleware', + // anonymous middleware + async (c, next) => { + await next(); + }, + c => { + const response = c.text('response 200'); + if (basePath === '/sync') return response; + return Promise.resolve(response); + }, + ); + + // anonymous middleware + baseApp[method]('/middleware/separately', async (c, next) => { + await next(); + }); + + baseApp[method]('/middleware/separately', async c => { + const response = c.text('response 200'); + if (basePath === '/sync') return response; + return Promise.resolve(response); + }); + + baseApp.all('/all', c => { + const response = c.text('response 200'); + if (basePath === '/sync') return response; + return Promise.resolve(response); + }); + + baseApp.all( + '/all/middleware', + // anonymous middleware + async (c, next) => { + await next(); + }, + c => { + const response = c.text('response 200'); + if (basePath === '/sync') return response; + return Promise.resolve(response); + }, + ); + + // anonymous middleware + baseApp.all('/all/middleware/separately', async (c, next) => { + await next(); + }); + + baseApp.all('/all/middleware/separately', async c => { + const response = c.text('response 200'); + if (basePath === '/sync') return response; + return Promise.resolve(response); + }); + + baseApp.on(method, '/on', c => { + const response = c.text('response 200'); + if (basePath === '/sync') return response; + return Promise.resolve(response); + }); + + baseApp.on( + method, + '/on/middleware', + // anonymous middleware + async (c, next) => { + await next(); + }, + c => { + const response = c.text('response 200'); + if (basePath === '/sync') return response; + return Promise.resolve(response); + }, + ); + + // anonymous middleware + baseApp.on(method, '/on/middleware/separately', async (c, next) => { + await next(); + }); + + baseApp.on(method, '/on/middleware/separately', async c => { + const response = c.text('response 200'); + if (basePath === '/sync') return response; + return Promise.resolve(response); + }); + + baseApp[method]('/401', () => { + const response = new HTTPException(401, { message: 'response 401' }); + if (basePath === '/sync') throw response; + return Promise.reject(response); + }); + + baseApp.all('/all/401', () => { + const response = new HTTPException(401, { message: 'response 401' }); + if (basePath === '/sync') throw response; + return Promise.reject(response); + }); + + baseApp.on(method, '/on/401', () => { + const response = new HTTPException(401, { message: 'response 401' }); + if (basePath === '/sync') throw response; + return Promise.reject(response); + }); + + baseApp[method]('/402', () => { + const response = new HTTPException(402, { message: 'response 402' }); + if (basePath === '/sync') throw response; + return Promise.reject(response); + }); + + baseApp.all('/all/402', () => { + const response = new HTTPException(402, { message: 'response 402' }); + if (basePath === '/sync') throw response; + return Promise.reject(response); + }); + + baseApp.on(method, '/on/402', () => { + const response = new HTTPException(402, { message: 'response 402' }); + if (basePath === '/sync') throw response; + return Promise.reject(response); + }); + + baseApp[method]('/403', () => { + const response = new HTTPException(403, { message: 'response 403' }); + if (basePath === '/sync') throw response; + return Promise.reject(response); + }); + + baseApp.all('/all/403', () => { + const response = new HTTPException(403, { message: 'response 403' }); + if (basePath === '/sync') throw response; + return Promise.reject(response); + }); + + baseApp.on(method, '/on/403', () => { + const response = new HTTPException(403, { message: 'response 403' }); + if (basePath === '/sync') throw response; + return Promise.reject(response); + }); + + baseApp[method]('/500', () => { + const response = new HTTPException(500, { message: 'response 500' }); + if (basePath === '/sync') throw response; + return Promise.reject(response); + }); + + baseApp.all('/all/500', () => { + const response = new HTTPException(500, { message: 'response 500' }); + if (basePath === '/sync') throw response; + return Promise.reject(response); + }); + + baseApp.on(method, '/on/500', () => { + const response = new HTTPException(500, { message: 'response 500' }); + if (basePath === '/sync') throw response; + return Promise.reject(response); + }); + }); + + app.route(basePath, baseApp); +}); + +const port = 8787; +serve({ fetch: app.fetch, port }); +sendPortToRunner(port); diff --git a/dev-packages/node-integration-tests/suites/tracing/hono/test.ts b/dev-packages/node-integration-tests/suites/tracing/hono/test.ts new file mode 100644 index 000000000000..67d0ff8b56fb --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/hono/test.ts @@ -0,0 +1,251 @@ +import { afterAll, describe, expect } from 'vitest'; +import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner'; + +describe('hono tracing', () => { + afterAll(() => { + cleanupChildProcesses(); + }); + + createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => { + describe.each(['/sync', '/async'] as const)('when using %s route', route => { + describe.each(['get', 'post', 'put', 'delete', 'patch'] as const)('when using %s method', method => { + describe.each(['/', '/all', '/on'])('when using %s path', path => { + test('should handle transaction', async () => { + const runner = createRunner() + .expect({ + transaction: { + transaction: `${method.toUpperCase()} ${route}${path === '/' ? '' : path}`, + spans: expect.arrayContaining([ + expect.objectContaining({ + data: expect.objectContaining({ + 'hono.name': 'sentryRequestMiddleware', + 'hono.type': 'middleware', + }), + description: 'sentryRequestMiddleware', + op: 'middleware.hono', + origin: 'auto.http.otel.hono', + }), + expect.objectContaining({ + data: expect.objectContaining({ + 'hono.name': 'sentryErrorMiddleware', + 'hono.type': 'middleware', + }), + description: 'sentryErrorMiddleware', + op: 'middleware.hono', + origin: 'auto.http.otel.hono', + }), + expect.objectContaining({ + data: expect.objectContaining({ + 'hono.name': 'global', + 'hono.type': 'middleware', + }), + description: 'global', + op: 'middleware.hono', + origin: 'auto.http.otel.hono', + }), + expect.objectContaining({ + data: expect.objectContaining({ + 'hono.name': 'base', + 'hono.type': 'middleware', + }), + description: 'base', + op: 'middleware.hono', + origin: 'auto.http.otel.hono', + }), + expect.objectContaining({ + data: expect.objectContaining({ + 'hono.name': `${route}${path === '/' ? '' : path}`, + 'hono.type': 'request_handler', + }), + description: `${route}${path === '/' ? '' : path}`, + op: 'request_handler.hono', + origin: 'auto.http.otel.hono', + }), + ]), + }, + }) + .start(); + runner.makeRequest(method, `${route}${path === '/' ? '' : path}`); + await runner.completed(); + }); + + test('should handle transaction with anonymous middleware', async () => { + const runner = createRunner() + .expect({ + transaction: { + transaction: `${method.toUpperCase()} ${route}${path === '/' ? '' : path}/middleware`, + spans: expect.arrayContaining([ + expect.objectContaining({ + data: expect.objectContaining({ + 'hono.name': 'sentryRequestMiddleware', + 'hono.type': 'middleware', + }), + description: 'sentryRequestMiddleware', + op: 'middleware.hono', + origin: 'auto.http.otel.hono', + }), + expect.objectContaining({ + data: expect.objectContaining({ + 'hono.name': 'sentryErrorMiddleware', + 'hono.type': 'middleware', + }), + description: 'sentryErrorMiddleware', + op: 'middleware.hono', + origin: 'auto.http.otel.hono', + }), + expect.objectContaining({ + data: expect.objectContaining({ + 'hono.name': 'global', + 'hono.type': 'middleware', + }), + description: 'global', + op: 'middleware.hono', + origin: 'auto.http.otel.hono', + }), + expect.objectContaining({ + data: expect.objectContaining({ + 'hono.name': 'base', + 'hono.type': 'middleware', + }), + description: 'base', + op: 'middleware.hono', + origin: 'auto.http.otel.hono', + }), + expect.objectContaining({ + data: expect.objectContaining({ + 'hono.name': 'anonymous', + 'hono.type': 'middleware', + }), + description: 'anonymous', + op: 'middleware.hono', + origin: 'auto.http.otel.hono', + }), + expect.objectContaining({ + data: expect.objectContaining({ + 'hono.name': `${route}${path === '/' ? '' : path}/middleware`, + 'hono.type': 'request_handler', + }), + description: `${route}${path === '/' ? '' : path}/middleware`, + op: 'request_handler.hono', + origin: 'auto.http.otel.hono', + }), + ]), + }, + }) + .start(); + runner.makeRequest(method, `${route}${path === '/' ? '' : path}/middleware`); + await runner.completed(); + }); + + test('should handle transaction with separate middleware', async () => { + const runner = createRunner() + .expect({ + transaction: { + transaction: `${method.toUpperCase()} ${route}${path === '/' ? '' : path}/middleware/separately`, + spans: expect.arrayContaining([ + expect.objectContaining({ + data: expect.objectContaining({ + 'hono.name': 'sentryRequestMiddleware', + 'hono.type': 'middleware', + }), + description: 'sentryRequestMiddleware', + op: 'middleware.hono', + origin: 'auto.http.otel.hono', + }), + expect.objectContaining({ + data: expect.objectContaining({ + 'hono.name': 'sentryErrorMiddleware', + 'hono.type': 'middleware', + }), + description: 'sentryErrorMiddleware', + op: 'middleware.hono', + origin: 'auto.http.otel.hono', + }), + expect.objectContaining({ + data: expect.objectContaining({ + 'hono.name': 'global', + 'hono.type': 'middleware', + }), + description: 'global', + op: 'middleware.hono', + origin: 'auto.http.otel.hono', + }), + expect.objectContaining({ + data: expect.objectContaining({ + 'hono.name': 'base', + 'hono.type': 'middleware', + }), + description: 'base', + op: 'middleware.hono', + origin: 'auto.http.otel.hono', + }), + expect.objectContaining({ + data: expect.objectContaining({ + 'hono.name': 'anonymous', + 'hono.type': 'middleware', + }), + description: 'anonymous', + op: 'middleware.hono', + origin: 'auto.http.otel.hono', + }), + expect.objectContaining({ + data: expect.objectContaining({ + 'hono.name': `${route}${path === '/' ? '' : path}/middleware/separately`, + 'hono.type': 'request_handler', + }), + description: `${route}${path === '/' ? '' : path}/middleware/separately`, + op: 'request_handler.hono', + origin: 'auto.http.otel.hono', + }), + ]), + }, + }) + .start(); + runner.makeRequest(method, `${route}${path === '/' ? '' : path}/middleware/separately`); + await runner.completed(); + }); + + test('should handle returned errors for %s path', async () => { + const runner = createRunner() + .ignore('transaction') + .expect({ + event: { + exception: { + values: [ + { + mechanism: { + type: 'auto.middleware.hono', + handled: false, + }, + type: 'Error', + value: 'response 500', + }, + ], + }, + }, + }) + .start(); + runner.makeRequest(method, `${route}${path === '/' ? '' : path}/500`, { expectError: true }); + await runner.completed(); + }); + + test.each(['/401', '/402', '/403', '/does-not-exist'])( + 'should ignores error %s path by default', + async (subPath: string) => { + const runner = createRunner() + .expect({ + transaction: { + transaction: `${method.toUpperCase()} ${route}`, + }, + }) + .start(); + runner.makeRequest(method, `${route}${path === '/' ? '' : path}${subPath}`, { expectError: true }); + runner.makeRequest(method, route); + await runner.completed(); + }, + ); + }); + }); + }); + }); +}); diff --git a/dev-packages/node-integration-tests/utils/runner.ts b/dev-packages/node-integration-tests/utils/runner.ts index fe6d9a8f8c45..b0c6467fd75a 100644 --- a/dev-packages/node-integration-tests/utils/runner.ts +++ b/dev-packages/node-integration-tests/utils/runner.ts @@ -167,7 +167,7 @@ type StartResult = { getLogs(): string[]; getPort(): number | undefined; makeRequest( - method: 'get' | 'post', + method: 'get' | 'post' | 'put' | 'delete' | 'patch', path: string, options?: { headers?: Record; data?: BodyInit; expectError?: boolean }, ): Promise; @@ -655,7 +655,7 @@ export function createRunner(...paths: string[]) { return scenarioServerPort; }, makeRequest: async function ( - method: 'get' | 'post', + method: 'get' | 'post' | 'put' | 'delete' | 'patch', path: string, options: { headers?: Record; data?: BodyInit; expectError?: boolean } = {}, ): Promise { @@ -674,7 +674,7 @@ export function createRunner(...paths: string[]) { if (process.env.DEBUG) log('making request', method, url, headers, body); try { - const res = await fetch(url, { headers, method, body }); + const res = await fetch(url, { headers, method: method.toUpperCase(), body }); if (!res.ok) { if (!expectError) { diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts index de4079c4b5c4..d39cb5e4484d 100644 --- a/packages/astro/src/index.server.ts +++ b/packages/astro/src/index.server.ts @@ -64,6 +64,7 @@ export { winterCGHeadersToDict, graphqlIntegration, hapiIntegration, + honoIntegration, httpIntegration, // eslint-disable-next-line deprecation/deprecation inboundFiltersIntegration, @@ -116,6 +117,7 @@ export { setupConnectErrorHandler, setupExpressErrorHandler, setupHapiErrorHandler, + setupHonoErrorHandler, setupKoaErrorHandler, setUser, spanToBaggageHeader, diff --git a/packages/aws-serverless/src/index.ts b/packages/aws-serverless/src/index.ts index 0cbe5879b02e..cfab7b72754b 100644 --- a/packages/aws-serverless/src/index.ts +++ b/packages/aws-serverless/src/index.ts @@ -111,6 +111,8 @@ export { createSentryWinstonTransport, hapiIntegration, setupHapiErrorHandler, + honoIntegration, + setupHonoErrorHandler, spotlightIntegration, initOpenTelemetry, spanToJSON, diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index b1c4854e5026..68a1e2b6d6ff 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -130,6 +130,8 @@ export { prismaIntegration, hapiIntegration, setupHapiErrorHandler, + honoIntegration, + setupHonoErrorHandler, spotlightIntegration, initOpenTelemetry, spanToJSON, diff --git a/packages/google-cloud-serverless/src/index.ts b/packages/google-cloud-serverless/src/index.ts index fc0fe353b919..ac0f41079017 100644 --- a/packages/google-cloud-serverless/src/index.ts +++ b/packages/google-cloud-serverless/src/index.ts @@ -109,6 +109,8 @@ export { prismaIntegration, hapiIntegration, setupHapiErrorHandler, + honoIntegration, + setupHonoErrorHandler, spotlightIntegration, initOpenTelemetry, spanToJSON, diff --git a/packages/node-core/src/utils/ensureIsWrapped.ts b/packages/node-core/src/utils/ensureIsWrapped.ts index a73c087ebaf2..921d01da8207 100644 --- a/packages/node-core/src/utils/ensureIsWrapped.ts +++ b/packages/node-core/src/utils/ensureIsWrapped.ts @@ -9,7 +9,7 @@ import { isCjs } from './detection'; */ export function ensureIsWrapped( maybeWrappedFunction: unknown, - name: 'express' | 'connect' | 'fastify' | 'hapi' | 'koa', + name: 'express' | 'connect' | 'fastify' | 'hapi' | 'koa' | 'hono', ): void { const clientOptions = getClient()?.getOptions(); if ( diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 853ec8dbac2f..67e00660c2a1 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -15,6 +15,7 @@ export { postgresIntegration } from './integrations/tracing/postgres'; export { postgresJsIntegration } from './integrations/tracing/postgresjs'; export { prismaIntegration } from './integrations/tracing/prisma'; export { hapiIntegration, setupHapiErrorHandler } from './integrations/tracing/hapi'; +export { honoIntegration, setupHonoErrorHandler } from './integrations/tracing/hono'; export { koaIntegration, setupKoaErrorHandler } from './integrations/tracing/koa'; export { connectIntegration, setupConnectErrorHandler } from './integrations/tracing/connect'; export { knexIntegration } from './integrations/tracing/knex'; diff --git a/packages/node/src/integrations/tracing/hono/constants.ts b/packages/node/src/integrations/tracing/hono/constants.ts new file mode 100644 index 000000000000..5814f5e950f2 --- /dev/null +++ b/packages/node/src/integrations/tracing/hono/constants.ts @@ -0,0 +1,13 @@ +export const AttributeNames = { + HONO_TYPE: 'hono.type', + HONO_NAME: 'hono.name', +} as const; + +export type AttributeNames = (typeof AttributeNames)[keyof typeof AttributeNames]; + +export const HonoTypes = { + MIDDLEWARE: 'middleware', + REQUEST_HANDLER: 'request_handler', +} as const; + +export type HonoTypes = (typeof HonoTypes)[keyof typeof HonoTypes]; diff --git a/packages/node/src/integrations/tracing/hono/index.ts b/packages/node/src/integrations/tracing/hono/index.ts index 8876d26b829e..4249871b8acf 100644 --- a/packages/node/src/integrations/tracing/hono/index.ts +++ b/packages/node/src/integrations/tracing/hono/index.ts @@ -1,11 +1,62 @@ -import type { IntegrationFn } from '@sentry/core'; -import { defineIntegration } from '@sentry/core'; -import { generateInstrumentOnce } from '@sentry/node-core'; +import { ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_ROUTE } from '@opentelemetry/semantic-conventions'; +import type { IntegrationFn, Span } from '@sentry/core'; +import { + captureException, + debug, + defineIntegration, + getDefaultIsolationScope, + getIsolationScope, + httpRequestToRequestData, + SEMANTIC_ATTRIBUTE_SENTRY_OP, + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + spanToJSON, +} from '@sentry/core'; +import { ensureIsWrapped, generateInstrumentOnce } from '@sentry/node-core'; +import { DEBUG_BUILD } from '../../../debug-build'; +import { AttributeNames } from './constants'; import { HonoInstrumentation } from './instrumentation'; +import type { Context, MiddlewareHandler, MiddlewareHandlerInterface, Next } from './types'; const INTEGRATION_NAME = 'Hono'; -export const instrumentHono = generateInstrumentOnce(INTEGRATION_NAME, () => new HonoInstrumentation()); +function addHonoSpanAttributes(span: Span): void { + const attributes = spanToJSON(span).data; + const type = attributes[AttributeNames.HONO_TYPE]; + if (attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] || !type) { + return; + } + + span.setAttributes({ + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.otel.hono', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: `${type}.hono`, + }); + + const name = attributes[AttributeNames.HONO_NAME]; + if (typeof name === 'string') { + span.updateName(name); + } + + if (getIsolationScope() === getDefaultIsolationScope()) { + DEBUG_BUILD && debug.warn('Isolation scope is default isolation scope - skipping setting transactionName'); + return; + } + + const route = attributes[ATTR_HTTP_ROUTE]; + const method = attributes[ATTR_HTTP_REQUEST_METHOD]; + if (typeof route === 'string' && typeof method === 'string') { + getIsolationScope().setTransactionName(`${method} ${route}`); + } +} + +export const instrumentHono = generateInstrumentOnce( + INTEGRATION_NAME, + () => + new HonoInstrumentation({ + responseHook: span => { + addHonoSpanAttributes(span); + }, + }), +); const _honoIntegration = (() => { return { @@ -33,3 +84,67 @@ const _honoIntegration = (() => { * ``` */ export const honoIntegration = defineIntegration(_honoIntegration); + +interface HonoHandlerOptions { + /** + * Callback method deciding whether error should be captured and sent to Sentry + * @param error Captured Hono error + */ + shouldHandleError: (context: Context) => boolean; +} + +function honoRequestHandler(): MiddlewareHandler { + return async function sentryRequestMiddleware(context: Context, next: Next): Promise { + const normalizedRequest = httpRequestToRequestData(context.req); + getIsolationScope().setSDKProcessingMetadata({ normalizedRequest }); + await next(); + }; +} + +function defaultShouldHandleError(context: Context): boolean { + const statusCode = context.res.status; + return statusCode >= 500; +} + +function honoErrorHandler(options?: Partial): MiddlewareHandler { + return async function sentryErrorMiddleware(context: Context, next: Next): Promise { + await next(); + + const shouldHandleError = options?.shouldHandleError || defaultShouldHandleError; + if (shouldHandleError(context)) { + (context.res as { sentry?: string }).sentry = captureException(context.error, { + mechanism: { + type: 'auto.middleware.hono', + handled: false, + }, + }); + } + }; +} + +/** + * Add a Hono error handler to capture errors to Sentry. + * + * @param app The Hono instances + * @param options Configuration options for the handler + * + * @example + * ```javascript + * const Sentry = require('@sentry/node'); + * const { Hono } = require("hono"); + * + * const app = new Hono(); + * + * Sentry.setupHonoErrorHandler(app); + * + * // Add your routes, etc. + * ``` + */ +export function setupHonoErrorHandler( + app: { use: MiddlewareHandlerInterface }, + options?: Partial, +): void { + app.use(honoRequestHandler()); + app.use(honoErrorHandler(options)); + ensureIsWrapped(app.use, 'hono'); +} diff --git a/packages/node/src/integrations/tracing/hono/instrumentation.ts b/packages/node/src/integrations/tracing/hono/instrumentation.ts index 81e062560051..9a55eaac2776 100644 --- a/packages/node/src/integrations/tracing/hono/instrumentation.ts +++ b/packages/node/src/integrations/tracing/hono/instrumentation.ts @@ -1,15 +1,39 @@ +import type { Span } from '@opentelemetry/api'; +import { context, SpanStatusCode, trace } from '@opentelemetry/api'; +import type { InstrumentationConfig } from '@opentelemetry/instrumentation'; import { InstrumentationBase, InstrumentationNodeModuleDefinition } from '@opentelemetry/instrumentation'; -import type { HandlerInterface, Hono, HonoInstance, MiddlewareHandlerInterface, OnHandlerInterface } from './types'; +import { isThenable } from '@sentry/core'; +import { AttributeNames, HonoTypes } from './constants'; +import type { + Context, + Handler, + HandlerInterface, + Hono, + HonoInstance, + MiddlewareHandler, + MiddlewareHandlerInterface, + Next, + OnHandlerInterface, +} from './types'; const PACKAGE_NAME = '@sentry/instrumentation-hono'; const PACKAGE_VERSION = '0.0.1'; +export interface HonoResponseHookFunction { + (span: Span): void; +} + +export interface HonoInstrumentationConfig extends InstrumentationConfig { + /** Function for adding custom span attributes from the response */ + responseHook?: HonoResponseHookFunction; +} + /** * Hono instrumentation for OpenTelemetry */ -export class HonoInstrumentation extends InstrumentationBase { - public constructor() { - super(PACKAGE_NAME, PACKAGE_VERSION, {}); +export class HonoInstrumentation extends InstrumentationBase { + public constructor(config: HonoInstrumentationConfig = {}) { + super(PACKAGE_NAME, PACKAGE_VERSION, config); } /** @@ -28,7 +52,7 @@ export class HonoInstrumentation extends InstrumentationBase { // eslint-disable-next-line @typescript-eslint/no-this-alias const instrumentation = this; - moduleExports.Hono = class HonoWrapper extends moduleExports.Hono { + class WrappedHono extends moduleExports.Hono { public constructor(...args: unknown[]) { super(...args); @@ -42,7 +66,15 @@ export class HonoInstrumentation extends InstrumentationBase { instrumentation._wrap(this, 'on', instrumentation._patchOnHandler()); instrumentation._wrap(this, 'use', instrumentation._patchMiddlewareHandler()); } - }; + } + + try { + moduleExports.Hono = WrappedHono; + } catch { + // This is a workaround for environments where direct assignment is not allowed. + return { ...moduleExports, Hono: WrappedHono }; + } + return moduleExports; } @@ -50,10 +82,28 @@ export class HonoInstrumentation extends InstrumentationBase { * Patches the route handler to instrument it. */ private _patchHandler(): (original: HandlerInterface) => HandlerInterface { + // eslint-disable-next-line @typescript-eslint/no-this-alias + const instrumentation = this; + return function (original: HandlerInterface) { return function wrappedHandler(this: HonoInstance, ...args: unknown[]) { - // TODO: Add OpenTelemetry tracing logic here - return original.apply(this, args); + if (typeof args[0] === 'string') { + const path = args[0]; + if (args.length === 1) { + return original.apply(this, [path]); + } + + const handlers = args.slice(1); + return original.apply(this, [ + path, + ...handlers.map(handler => instrumentation._wrapHandler(handler as Handler | MiddlewareHandler)), + ]); + } + + return original.apply( + this, + args.map(handler => instrumentation._wrapHandler(handler as Handler | MiddlewareHandler)), + ); }; }; } @@ -62,10 +112,16 @@ export class HonoInstrumentation extends InstrumentationBase { * Patches the 'on' handler to instrument it. */ private _patchOnHandler(): (original: OnHandlerInterface) => OnHandlerInterface { + // eslint-disable-next-line @typescript-eslint/no-this-alias + const instrumentation = this; + return function (original: OnHandlerInterface) { return function wrappedHandler(this: HonoInstance, ...args: unknown[]) { - // TODO: Add OpenTelemetry tracing logic here - return original.apply(this, args); + const handlers = args.slice(2); + return original.apply(this, [ + ...args.slice(0, 2), + ...handlers.map(handler => instrumentation._wrapHandler(handler as Handler | MiddlewareHandler)), + ]); }; }; } @@ -74,11 +130,124 @@ export class HonoInstrumentation extends InstrumentationBase { * Patches the middleware handler to instrument it. */ private _patchMiddlewareHandler(): (original: MiddlewareHandlerInterface) => MiddlewareHandlerInterface { + // eslint-disable-next-line @typescript-eslint/no-this-alias + const instrumentation = this; + return function (original: MiddlewareHandlerInterface) { return function wrappedHandler(this: HonoInstance, ...args: unknown[]) { - // TODO: Add OpenTelemetry tracing logic here - return original.apply(this, args); + if (typeof args[0] === 'string') { + const path = args[0]; + if (args.length === 1) { + return original.apply(this, [path]); + } + + const handlers = args.slice(1); + return original.apply(this, [ + path, + ...handlers.map(handler => instrumentation._wrapHandler(handler as MiddlewareHandler)), + ]); + } + + return original.apply( + this, + args.map(handler => instrumentation._wrapHandler(handler as MiddlewareHandler)), + ); }; }; } + + /** + * Wraps a handler or middleware handler to apply instrumentation. + */ + private _wrapHandler(handler: Handler | MiddlewareHandler): Handler | MiddlewareHandler { + // eslint-disable-next-line @typescript-eslint/no-this-alias + const instrumentation = this; + + return function (this: unknown, c: Context, next: Next) { + if (!instrumentation.isEnabled()) { + return handler.apply(this, [c, next]); + } + + const path = c.req.path; + const span = instrumentation.tracer.startSpan(path); + + return context.with(trace.setSpan(context.active(), span), () => { + return instrumentation._safeExecute( + () => { + const result = handler.apply(this, [c, next]); + if (isThenable(result)) { + return result.then(result => { + const type = instrumentation._determineHandlerType(result); + span.setAttributes({ + [AttributeNames.HONO_TYPE]: type, + [AttributeNames.HONO_NAME]: type === HonoTypes.REQUEST_HANDLER ? path : handler.name || 'anonymous', + }); + instrumentation.getConfig().responseHook?.(span); + return result; + }); + } else { + const type = instrumentation._determineHandlerType(result); + span.setAttributes({ + [AttributeNames.HONO_TYPE]: type, + [AttributeNames.HONO_NAME]: type === HonoTypes.REQUEST_HANDLER ? path : handler.name || 'anonymous', + }); + instrumentation.getConfig().responseHook?.(span); + return result; + } + }, + () => span.end(), + error => { + instrumentation._handleError(span, error); + span.end(); + }, + ); + }); + }; + } + + /** + * Safely executes a function and handles errors. + */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private _safeExecute(execute: () => any, onSuccess: () => void, onFailure: (error: unknown) => void): () => any { + try { + const result = execute(); + + if (isThenable(result)) { + result.then( + () => onSuccess(), + (error: unknown) => onFailure(error), + ); + } else { + onSuccess(); + } + + return result; + } catch (error: unknown) { + onFailure(error); + throw error; + } + } + + /** + * Determines the handler type based on the result. + * @param result + * @private + */ + private _determineHandlerType(result: unknown): HonoTypes { + return result === undefined ? HonoTypes.MIDDLEWARE : HonoTypes.REQUEST_HANDLER; + } + + /** + * Handles errors by setting the span status and recording the exception. + */ + private _handleError(span: Span, error: unknown): void { + if (error instanceof Error) { + span.setStatus({ + code: SpanStatusCode.ERROR, + message: error.message, + }); + span.recordException(error); + } + } } diff --git a/packages/node/src/integrations/tracing/hono/types.ts b/packages/node/src/integrations/tracing/hono/types.ts index 3d7e057859f1..9873f80afa66 100644 --- a/packages/node/src/integrations/tracing/hono/types.ts +++ b/packages/node/src/integrations/tracing/hono/types.ts @@ -1,11 +1,14 @@ // Vendored from: https://github.com/honojs/hono/blob/855e5b1adbf685bf4b3e6b76573aa7cb0a108d04/src/request.ts#L30 export type HonoRequest = { path: string; + method: string; }; // Vendored from: https://github.com/honojs/hono/blob/855e5b1adbf685bf4b3e6b76573aa7cb0a108d04/src/context.ts#L291 export type Context = { req: HonoRequest; + res: Response; + error: Error | undefined; }; // Vendored from: https://github.com/honojs/hono/blob/855e5b1adbf685bf4b3e6b76573aa7cb0a108d04/src/types.ts#L36C1-L36C39 @@ -15,7 +18,7 @@ export type Next = () => Promise; export type Handler = (c: Context, next: Next) => Promise | Response; // Vendored from: https://github.com/honojs/hono/blob/855e5b1adbf685bf4b3e6b76573aa7cb0a108d04/src/types.ts#L80 -export type MiddlewareHandler = (c: Context, next: Next) => Promise | Response | void; +export type MiddlewareHandler = (c: Context, next: Next) => Promise; // Vendored from: https://github.com/honojs/hono/blob/855e5b1adbf685bf4b3e6b76573aa7cb0a108d04/src/types.ts#L109 export type HandlerInterface = { diff --git a/packages/node/src/integrations/tracing/index.ts b/packages/node/src/integrations/tracing/index.ts index e4dd84fc266e..dd9d9ac8df2b 100644 --- a/packages/node/src/integrations/tracing/index.ts +++ b/packages/node/src/integrations/tracing/index.ts @@ -10,6 +10,7 @@ import { genericPoolIntegration, instrumentGenericPool } from './genericPool'; import { googleGenAIIntegration, instrumentGoogleGenAI } from './google-genai'; import { graphqlIntegration, instrumentGraphql } from './graphql'; import { hapiIntegration, instrumentHapi } from './hapi'; +import { honoIntegration, instrumentHono } from './hono'; import { instrumentKafka, kafkaIntegration } from './kafka'; import { instrumentKoa, koaIntegration } from './koa'; import { instrumentLruMemoizer, lruMemoizerIntegration } from './lrumemoizer'; @@ -33,6 +34,7 @@ export function getAutoPerformanceIntegrations(): Integration[] { expressIntegration(), fastifyIntegration(), graphqlIntegration(), + honoIntegration(), mongoIntegration(), mongooseIntegration(), mysqlIntegration(), @@ -70,6 +72,7 @@ export function getOpenTelemetryInstrumentationToPreload(): (((options?: any) => instrumentFastify, instrumentFastifyV3, instrumentHapi, + instrumentHono, instrumentKafka, instrumentKoa, instrumentLruMemoizer, diff --git a/yarn.lock b/yarn.lock index 0dfdce6d5106..299b1415d89a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4591,6 +4591,11 @@ "@hapi/bourne" "^3.0.0" "@hapi/hoek" "^11.0.2" +"@hono/node-server@^1.19.4": + version "1.19.4" + resolved "https://registry.yarnpkg.com/@hono/node-server/-/node-server-1.19.4.tgz#2721cda094f7c080ee985494ac3e074f16c503eb" + integrity sha512-AWKQZ/YkHUBSHeL/5Ld8FWgUs6wFf4TxGYxqp9wLZxRdFuHBpXmgOq+CuDoL4vllkZLzovCf5HBJnypiy3EtHA== + "@humanwhocodes/config-array@^0.5.0": version "0.5.0" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9" @@ -18599,6 +18604,11 @@ homedir-polyfill@^1.0.1: dependencies: parse-passwd "^1.0.0" +hono@^4.9.8: + version "4.9.8" + resolved "https://registry.yarnpkg.com/hono/-/hono-4.9.8.tgz#1710981135ec775fe26fab5ea6535b403e92bcc3" + integrity sha512-JW8Bb4RFWD9iOKxg5PbUarBYGM99IcxFl2FPBo2gSJO11jjUDqlP1Bmfyqt8Z/dGhIQ63PMA9LdcLefXyIasyg== + hookable@^5.5.3: version "5.5.3" resolved "https://registry.yarnpkg.com/hookable/-/hookable-5.5.3.tgz#6cfc358984a1ef991e2518cb9ed4a778bbd3215d" From 16ee36a83ef3cc4ca4d0f682ef9e382d7398d571 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Tue, 23 Sep 2025 16:27:43 +0200 Subject: [PATCH 05/12] chore: Add external contributor to CHANGELOG.md (#17745) This PR adds the external contributor to the CHANGELOG.md file, so that they are credited for their contribution. See #17428 Co-authored-by: s1gr1d <32902192+s1gr1d@users.noreply.github.com> --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 319465691cd5..6350f087f454 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +Work in this release was contributed by @Karibash. Thank you for your contribution! + ## 10.14.0 ### Important Changes From c084bd6949b60a1d3b2b9ac8b6340f86e3c84b30 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Tue, 23 Sep 2025 16:33:38 +0200 Subject: [PATCH 06/12] ref(nextjs): Display build compatibility warning for webpack (#17746) --- .../nextjs/src/config/withSentryConfig.ts | 12 + .../test/config/withSentryConfig.test.ts | 273 ++++++++++++++++++ 2 files changed, 285 insertions(+) diff --git a/packages/nextjs/src/config/withSentryConfig.ts b/packages/nextjs/src/config/withSentryConfig.ts index 201c27dc5d0a..ddf761998e50 100644 --- a/packages/nextjs/src/config/withSentryConfig.ts +++ b/packages/nextjs/src/config/withSentryConfig.ts @@ -275,6 +275,18 @@ function getFinalConfigObject( } } + // webpack case + if ( + userSentryOptions.useRunAfterProductionCompileHook && + !supportsProductionCompileHook(nextJsVersion ?? '') && + !isTurbopack + ) { + // eslint-disable-next-line no-console + console.warn( + '[@sentry/nextjs] The configured `useRunAfterProductionCompileHook` option is not compatible with your current Next.js version. This option is only supported on Next.js version 15.4.1 or later. Will not run source map and release management logic.', + ); + } + // If not explicitly set, turbopack uses the runAfterProductionCompile hook (as there are no alternatives), webpack does not. const shouldUseRunAfterProductionCompileHook = userSentryOptions?.useRunAfterProductionCompileHook ?? (isTurbopack ? true : false); diff --git a/packages/nextjs/test/config/withSentryConfig.test.ts b/packages/nextjs/test/config/withSentryConfig.test.ts index 8fc0a81e3dd4..b437e73dfe75 100644 --- a/packages/nextjs/test/config/withSentryConfig.test.ts +++ b/packages/nextjs/test/config/withSentryConfig.test.ts @@ -1173,4 +1173,277 @@ describe('withSentryConfig', () => { consoleWarnSpy.mockRestore(); }); }); + + describe('useRunAfterProductionCompileHook warning logic', () => { + const originalTurbopack = process.env.TURBOPACK; + + afterEach(() => { + vi.restoreAllMocks(); + process.env.TURBOPACK = originalTurbopack; + }); + + it('warns when useRunAfterProductionCompileHook is enabled with unsupported Next.js version in webpack mode', () => { + delete process.env.TURBOPACK; // Ensure webpack mode + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.0'); // Unsupported version + vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(false); + const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + const sentryOptions = { + useRunAfterProductionCompileHook: true, + }; + + materializeFinalNextConfig(exportedNextConfig, undefined, sentryOptions); + + expect(consoleWarnSpy).toHaveBeenCalledWith( + '[@sentry/nextjs] The configured `useRunAfterProductionCompileHook` option is not compatible with your current Next.js version. This option is only supported on Next.js version 15.4.1 or later. Will not run source map and release management logic.', + ); + + consoleWarnSpy.mockRestore(); + }); + + it('does not warn when useRunAfterProductionCompileHook is enabled with supported Next.js version in webpack mode', () => { + delete process.env.TURBOPACK; // Ensure webpack mode + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.1'); // Supported version + vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(true); + const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + const sentryOptions = { + useRunAfterProductionCompileHook: true, + }; + + materializeFinalNextConfig(exportedNextConfig, undefined, sentryOptions); + + expect(consoleWarnSpy).not.toHaveBeenCalledWith( + expect.stringContaining('The configured `useRunAfterProductionCompileHook` option is not compatible'), + ); + + consoleWarnSpy.mockRestore(); + }); + + it('does not warn when useRunAfterProductionCompileHook is disabled with unsupported Next.js version in webpack mode', () => { + delete process.env.TURBOPACK; // Ensure webpack mode + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.0'); // Unsupported version + vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(false); + const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + const sentryOptions = { + useRunAfterProductionCompileHook: false, + }; + + materializeFinalNextConfig(exportedNextConfig, undefined, sentryOptions); + + expect(consoleWarnSpy).not.toHaveBeenCalledWith( + expect.stringContaining('The configured `useRunAfterProductionCompileHook` option is not compatible'), + ); + + consoleWarnSpy.mockRestore(); + }); + + it('does not warn when useRunAfterProductionCompileHook is undefined with unsupported Next.js version in webpack mode', () => { + delete process.env.TURBOPACK; // Ensure webpack mode + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.0'); // Unsupported version + vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(false); + const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + const sentryOptions = {}; // useRunAfterProductionCompileHook is undefined + + materializeFinalNextConfig(exportedNextConfig, undefined, sentryOptions); + + expect(consoleWarnSpy).not.toHaveBeenCalledWith( + expect.stringContaining('The configured `useRunAfterProductionCompileHook` option is not compatible'), + ); + + consoleWarnSpy.mockRestore(); + }); + + it('does not warn when useRunAfterProductionCompileHook is enabled with unsupported Next.js version in turbopack mode', () => { + process.env.TURBOPACK = '1'; // Ensure turbopack mode + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.0'); // Unsupported version + vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(false); + const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + const sentryOptions = { + useRunAfterProductionCompileHook: true, + }; + + materializeFinalNextConfig(exportedNextConfig, undefined, sentryOptions); + + // Should not warn about useRunAfterProductionCompileHook incompatibility in turbopack mode + // (though it may warn about turbopack version compatibility) + expect(consoleWarnSpy).not.toHaveBeenCalledWith( + expect.stringContaining('The configured `useRunAfterProductionCompileHook` option is not compatible'), + ); + + consoleWarnSpy.mockRestore(); + }); + + it('warns with different unsupported Next.js versions', () => { + delete process.env.TURBOPACK; // Ensure webpack mode + const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + const sentryOptions = { + useRunAfterProductionCompileHook: true, + }; + + // Test with 15.3.9 + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.3.9'); + vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(false); + + materializeFinalNextConfig(exportedNextConfig, undefined, sentryOptions); + + expect(consoleWarnSpy).toHaveBeenCalledWith( + '[@sentry/nextjs] The configured `useRunAfterProductionCompileHook` option is not compatible with your current Next.js version. This option is only supported on Next.js version 15.4.1 or later. Will not run source map and release management logic.', + ); + + consoleWarnSpy.mockClear(); + + // Test with 14.2.0 + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('14.2.0'); + vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(false); + + materializeFinalNextConfig(exportedNextConfig, undefined, sentryOptions); + + expect(consoleWarnSpy).toHaveBeenCalledWith( + '[@sentry/nextjs] The configured `useRunAfterProductionCompileHook` option is not compatible with your current Next.js version. This option is only supported on Next.js version 15.4.1 or later. Will not run source map and release management logic.', + ); + + consoleWarnSpy.mockClear(); + + // Test with canary version that's unsupported + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.0-canary.42'); + vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(false); + + materializeFinalNextConfig(exportedNextConfig, undefined, sentryOptions); + + expect(consoleWarnSpy).toHaveBeenCalledWith( + '[@sentry/nextjs] The configured `useRunAfterProductionCompileHook` option is not compatible with your current Next.js version. This option is only supported on Next.js version 15.4.1 or later. Will not run source map and release management logic.', + ); + + consoleWarnSpy.mockRestore(); + }); + + it('does not warn with supported Next.js versions', () => { + delete process.env.TURBOPACK; // Ensure webpack mode + const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + const sentryOptions = { + useRunAfterProductionCompileHook: true, + }; + + // Test with 15.4.1 + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.1'); + vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(true); + + materializeFinalNextConfig(exportedNextConfig, undefined, sentryOptions); + + expect(consoleWarnSpy).not.toHaveBeenCalledWith( + expect.stringContaining('The configured `useRunAfterProductionCompileHook` option is not compatible'), + ); + + consoleWarnSpy.mockClear(); + + // Test with 15.5.0 + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.5.0'); + vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(true); + + materializeFinalNextConfig(exportedNextConfig, undefined, sentryOptions); + + expect(consoleWarnSpy).not.toHaveBeenCalledWith( + expect.stringContaining('The configured `useRunAfterProductionCompileHook` option is not compatible'), + ); + + consoleWarnSpy.mockClear(); + + // Test with 16.0.0 + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('16.0.0'); + vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(true); + + materializeFinalNextConfig(exportedNextConfig, undefined, sentryOptions); + + expect(consoleWarnSpy).not.toHaveBeenCalledWith( + expect.stringContaining('The configured `useRunAfterProductionCompileHook` option is not compatible'), + ); + + consoleWarnSpy.mockClear(); + + // Test with supported canary version + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.1-canary.1'); + vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(true); + + materializeFinalNextConfig(exportedNextConfig, undefined, sentryOptions); + + expect(consoleWarnSpy).not.toHaveBeenCalledWith( + expect.stringContaining('The configured `useRunAfterProductionCompileHook` option is not compatible'), + ); + + consoleWarnSpy.mockRestore(); + }); + + it('handles edge case when Next.js version is undefined', () => { + delete process.env.TURBOPACK; // Ensure webpack mode + vi.spyOn(util, 'getNextjsVersion').mockReturnValue(undefined); + vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(false); + const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + const sentryOptions = { + useRunAfterProductionCompileHook: true, + }; + + materializeFinalNextConfig(exportedNextConfig, undefined, sentryOptions); + + expect(consoleWarnSpy).toHaveBeenCalledWith( + '[@sentry/nextjs] The configured `useRunAfterProductionCompileHook` option is not compatible with your current Next.js version. This option is only supported on Next.js version 15.4.1 or later. Will not run source map and release management logic.', + ); + + consoleWarnSpy.mockRestore(); + }); + + it('handles edge case when Next.js version is empty string', () => { + delete process.env.TURBOPACK; // Ensure webpack mode + vi.spyOn(util, 'getNextjsVersion').mockReturnValue(''); + vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(false); + const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + const sentryOptions = { + useRunAfterProductionCompileHook: true, + }; + + materializeFinalNextConfig(exportedNextConfig, undefined, sentryOptions); + + expect(consoleWarnSpy).toHaveBeenCalledWith( + '[@sentry/nextjs] The configured `useRunAfterProductionCompileHook` option is not compatible with your current Next.js version. This option is only supported on Next.js version 15.4.1 or later. Will not run source map and release management logic.', + ); + + consoleWarnSpy.mockRestore(); + }); + + it('works correctly with other sentry options present', () => { + delete process.env.TURBOPACK; // Ensure webpack mode + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.0'); // Unsupported version + vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(false); + const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + const sentryOptions = { + useRunAfterProductionCompileHook: true, + debug: true, + sourcemaps: { + disable: false, + }, + tunnelRoute: '/tunnel', + }; + + const finalConfig = materializeFinalNextConfig(exportedNextConfig, undefined, sentryOptions); + + expect(consoleWarnSpy).toHaveBeenCalledWith( + '[@sentry/nextjs] The configured `useRunAfterProductionCompileHook` option is not compatible with your current Next.js version. This option is only supported on Next.js version 15.4.1 or later. Will not run source map and release management logic.', + ); + + // Ensure other functionality still works (tunnel route creates rewrites function) + expect(finalConfig.rewrites).toBeInstanceOf(Function); + // Release name should be set (from git or environment) + expect(finalConfig.env).toHaveProperty('_sentryRelease'); + + consoleWarnSpy.mockRestore(); + }); + }); }); From 678d6e81ec4d2be98980fd1150e9f63c8e404e23 Mon Sep 17 00:00:00 2001 From: Martin Sonnberger Date: Wed, 24 Sep 2025 15:11:30 +0200 Subject: [PATCH 07/12] feat(aws): Enable Lambda extension by default when using the Lamba layer (#17684) Enables the Lambda extension by default when using the Lambda layer --------- Co-authored-by: Francesco Gringl-Novy --- .../ExperimentalExtension/index.mjs | 16 ------ .../aws-serverless/tests/layer.test.ts | 48 ---------------- packages/aws-serverless/src/init.ts | 16 +++--- packages/aws-serverless/test/init.test.ts | 55 ++++++++++++++----- 4 files changed, 48 insertions(+), 87 deletions(-) delete mode 100644 dev-packages/e2e-tests/test-applications/aws-serverless/src/lambda-functions-layer/ExperimentalExtension/index.mjs diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless/src/lambda-functions-layer/ExperimentalExtension/index.mjs b/dev-packages/e2e-tests/test-applications/aws-serverless/src/lambda-functions-layer/ExperimentalExtension/index.mjs deleted file mode 100644 index d4cd56b78c90..000000000000 --- a/dev-packages/e2e-tests/test-applications/aws-serverless/src/lambda-functions-layer/ExperimentalExtension/index.mjs +++ /dev/null @@ -1,16 +0,0 @@ -import * as Sentry from '@sentry/aws-serverless'; - -Sentry.init({ - dsn: process.env.SENTRY_DSN, - tracesSampleRate: 1, - debug: true, - _experiments: { - enableLambdaExtension: true, - }, -}); - -export const handler = async (event, context) => { - Sentry.startSpan({ name: 'manual-span', op: 'test' }, async () => { - return 'Hello, world!'; - }); -}; diff --git a/dev-packages/e2e-tests/test-applications/aws-serverless/tests/layer.test.ts b/dev-packages/e2e-tests/test-applications/aws-serverless/tests/layer.test.ts index 6ec76124140d..bb7ae03a96e7 100644 --- a/dev-packages/e2e-tests/test-applications/aws-serverless/tests/layer.test.ts +++ b/dev-packages/e2e-tests/test-applications/aws-serverless/tests/layer.test.ts @@ -242,52 +242,4 @@ test.describe('Lambda layer', () => { }), ); }); - - test('experimental extension works', async ({ lambdaClient }) => { - const transactionEventPromise = waitForTransaction('aws-serverless-lambda-sam', transactionEvent => { - return transactionEvent?.transaction === 'LayerExperimentalExtension'; - }); - - await lambdaClient.send( - new InvokeCommand({ - FunctionName: 'LayerExperimentalExtension', - Payload: JSON.stringify({}), - }), - ); - - const transactionEvent = await transactionEventPromise; - - expect(transactionEvent.transaction).toEqual('LayerExperimentalExtension'); - expect(transactionEvent.contexts?.trace).toEqual({ - data: { - 'sentry.sample_rate': 1, - 'sentry.source': 'custom', - 'sentry.origin': 'auto.otel.aws-lambda', - 'sentry.op': 'function.aws.lambda', - 'cloud.account.id': '012345678912', - 'faas.execution': expect.any(String), - 'faas.id': 'arn:aws:lambda:us-east-1:012345678912:function:LayerExperimentalExtension', - 'faas.coldstart': true, - 'otel.kind': 'SERVER', - }, - op: 'function.aws.lambda', - origin: 'auto.otel.aws-lambda', - span_id: expect.stringMatching(/[a-f0-9]{16}/), - status: 'ok', - trace_id: expect.stringMatching(/[a-f0-9]{32}/), - }); - - expect(transactionEvent.spans).toHaveLength(1); - - expect(transactionEvent.spans).toContainEqual( - expect.objectContaining({ - data: expect.objectContaining({ - 'sentry.op': 'test', - 'sentry.origin': 'manual', - }), - description: 'manual-span', - op: 'test', - }), - ); - }); }); diff --git a/packages/aws-serverless/src/init.ts b/packages/aws-serverless/src/init.ts index 9de744bedf34..6640db8ec5fa 100644 --- a/packages/aws-serverless/src/init.ts +++ b/packages/aws-serverless/src/init.ts @@ -15,12 +15,10 @@ export function getDefaultIntegrations(_options: Options): Integration[] { } export interface AwsServerlessOptions extends NodeOptions { - _experiments?: NodeOptions['_experiments'] & { - /** - * If proxying Sentry events through the Sentry Lambda extension should be enabled. - */ - enableLambdaExtension?: boolean; - }; + /** + * If Sentry events should be proxied through the Lambda extension when using the Lambda layer. Defaults to `true` when using the Lambda layer. + */ + useLayerExtension?: boolean; } /** @@ -29,14 +27,14 @@ export interface AwsServerlessOptions extends NodeOptions { * @param options Configuration options for the SDK, @see {@link AWSLambdaOptions}. */ export function init(options: AwsServerlessOptions = {}): NodeClient | undefined { + const sdkSource = getSDKSource(); const opts = { defaultIntegrations: getDefaultIntegrations(options), + useLayerExtension: sdkSource === 'aws-lambda-layer' && !options.tunnel, ...options, }; - const sdkSource = getSDKSource(); - - if (opts._experiments?.enableLambdaExtension) { + if (opts.useLayerExtension) { if (sdkSource === 'aws-lambda-layer') { if (!opts.tunnel) { DEBUG_BUILD && debug.log('Proxying Sentry events through the Sentry Lambda extension'); diff --git a/packages/aws-serverless/test/init.test.ts b/packages/aws-serverless/test/init.test.ts index b4aa7ddc0d2b..576257e3f3e4 100644 --- a/packages/aws-serverless/test/init.test.ts +++ b/packages/aws-serverless/test/init.test.ts @@ -18,14 +18,12 @@ const mockGetSDKSource = vi.mocked(getSDKSource); const mockInitWithoutDefaultIntegrations = vi.mocked(initWithoutDefaultIntegrations); describe('init', () => { - describe('experimental Lambda extension support', () => { + describe('Lambda extension setup', () => { test('should preserve user-provided tunnel option when Lambda extension is enabled', () => { mockGetSDKSource.mockReturnValue('aws-lambda-layer'); const options: AwsServerlessOptions = { tunnel: 'https://custom-tunnel.example.com', - _experiments: { - enableLambdaExtension: true, - }, + useLayerExtension: true, }; init(options); @@ -40,9 +38,7 @@ describe('init', () => { test('should set default tunnel when Lambda extension is enabled and SDK source is aws-lambda-layer', () => { mockGetSDKSource.mockReturnValue('aws-lambda-layer'); const options: AwsServerlessOptions = { - _experiments: { - enableLambdaExtension: true, - }, + useLayerExtension: true, }; init(options); @@ -57,9 +53,7 @@ describe('init', () => { test('should not set tunnel when Lambda extension is disabled', () => { mockGetSDKSource.mockReturnValue('aws-lambda-layer'); const options: AwsServerlessOptions = { - _experiments: { - enableLambdaExtension: false, - }, + useLayerExtension: false, }; init(options); @@ -74,9 +68,7 @@ describe('init', () => { test('should not set tunnel when SDK source is not aws-lambda-layer even with Lambda extension enabled', () => { mockGetSDKSource.mockReturnValue('npm'); const options: AwsServerlessOptions = { - _experiments: { - enableLambdaExtension: true, - }, + useLayerExtension: true, }; init(options); @@ -88,17 +80,52 @@ describe('init', () => { ); }); - test('should not set tunnel when no experiments are provided', () => { + test('should default useLayerExtension to true when SDK source is aws-lambda-layer', () => { mockGetSDKSource.mockReturnValue('aws-lambda-layer'); const options: AwsServerlessOptions = {}; init(options); + expect(mockInitWithoutDefaultIntegrations).toHaveBeenCalledWith( + expect.objectContaining({ + useLayerExtension: true, + tunnel: 'http://localhost:9000/envelope', + }), + ); + }); + + test('should default useLayerExtension to false when SDK source is not aws-lambda-layer', () => { + mockGetSDKSource.mockReturnValue('npm'); + const options: AwsServerlessOptions = {}; + + init(options); + + expect(mockInitWithoutDefaultIntegrations).toHaveBeenCalledWith( + expect.objectContaining({ + useLayerExtension: false, + }), + ); expect(mockInitWithoutDefaultIntegrations).toHaveBeenCalledWith( expect.not.objectContaining({ tunnel: expect.any(String), }), ); }); + + test('should default useLayerExtension to false when tunnel is provided even when SDK source is aws-lambda-layer', () => { + mockGetSDKSource.mockReturnValue('aws-lambda-layer'); + const options: AwsServerlessOptions = { + tunnel: 'https://custom-tunnel.example.com', + }; + + init(options); + + expect(mockInitWithoutDefaultIntegrations).toHaveBeenCalledWith( + expect.objectContaining({ + useLayerExtension: false, + tunnel: 'https://custom-tunnel.example.com', + }), + ); + }); }); }); From 105f78ef8056ff1ab616eb92c00cee2a4dc8fd83 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Wed, 24 Sep 2025 15:56:50 +0200 Subject: [PATCH 08/12] fix(browser): Improve handling of `0` and `undefined` resource timing values (#17751) This patch - drops any `http.request.*` timing attributes where the original value was `undefined` - sends `0` for any `http.request.*` timing attribte values that were originally `0` (i.e. no longer converts them to the `timeOrigin` absolute timestamp) --------- Co-authored-by: Abhijeet Prasad --- .../src/metrics/resourceTiming.ts | 22 +++++-- .../test/browser/browserMetrics.test.ts | 28 ++++++++- .../test/metrics/resourceTiming.test.ts | 62 +++++++++---------- 3 files changed, 73 insertions(+), 39 deletions(-) diff --git a/packages/browser-utils/src/metrics/resourceTiming.ts b/packages/browser-utils/src/metrics/resourceTiming.ts index fe613355c55d..5a711d307cf3 100644 --- a/packages/browser-utils/src/metrics/resourceTiming.ts +++ b/packages/browser-utils/src/metrics/resourceTiming.ts @@ -2,8 +2,10 @@ import type { SpanAttributes } from '@sentry/core'; import { browserPerformanceTimeOrigin } from '@sentry/core'; import { extractNetworkProtocol, getBrowserPerformanceAPI } from './utils'; -function getAbsoluteTime(time = 0): number { - return ((browserPerformanceTimeOrigin() || performance.timeOrigin) + time) / 1000; +function getAbsoluteTime(time: number | undefined): number | undefined { + // falsy values should be preserved so that we can later on drop undefined values and + // preserve 0 vals for cross-origin resources without proper `Timing-Allow-Origin` header. + return time ? ((browserPerformanceTimeOrigin() || performance.timeOrigin) + time) / 1000 : time; } /** @@ -30,7 +32,7 @@ export function resourceTimingToSpanAttributes(resourceTiming: PerformanceResour return timingSpanData; } - return { + return dropUndefinedKeysFromObject({ ...timingSpanData, 'http.request.redirect_start': getAbsoluteTime(resourceTiming.redirectStart), @@ -55,6 +57,16 @@ export function resourceTimingToSpanAttributes(resourceTiming: PerformanceResour // For TTFB we actually want the relative time from timeOrigin to responseStart // This way, TTFB always measures the "first page load" experience. // see: https://web.dev/articles/ttfb#measure-resource-requests - 'http.request.time_to_first_byte': (resourceTiming.responseStart ?? 0) / 1000, - }; + 'http.request.time_to_first_byte': + resourceTiming.responseStart != null ? resourceTiming.responseStart / 1000 : undefined, + }); +} + +/** + * Remove properties with `undefined` as value from an object. + * In contrast to `dropUndefinedKeys` in core this funciton only works on first-level + * key-value objects and does not recursively go into object properties or arrays. + */ +function dropUndefinedKeysFromObject(attrs: T): Partial { + return Object.fromEntries(Object.entries(attrs).filter(([, value]) => value != null)) as Partial; } diff --git a/packages/browser-utils/test/browser/browserMetrics.test.ts b/packages/browser-utils/test/browser/browserMetrics.test.ts index c717bb81ca0b..c734ec326b47 100644 --- a/packages/browser-utils/test/browser/browserMetrics.test.ts +++ b/packages/browser-utils/test/browser/browserMetrics.test.ts @@ -266,6 +266,18 @@ describe('_addResourceSpans', () => { decodedBodySize: 593, renderBlockingStatus: 'non-blocking', nextHopProtocol: 'http/1.1', + connectStart: 1000, + connectEnd: 1001, + redirectStart: 1002, + redirectEnd: 1003, + fetchStart: 1004, + domainLookupStart: 1005, + domainLookupEnd: 1006, + requestStart: 1007, + responseStart: 1008, + responseEnd: 1009, + secureConnectionStart: 1005, + workerStart: 1006, }); const timeOrigin = 100; @@ -305,7 +317,7 @@ describe('_addResourceSpans', () => { 'http.request.response_end': expect.any(Number), 'http.request.response_start': expect.any(Number), 'http.request.secure_connection_start': expect.any(Number), - 'http.request.time_to_first_byte': 0, + 'http.request.time_to_first_byte': 1.008, 'http.request.worker_start': expect.any(Number), }, }), @@ -492,6 +504,18 @@ describe('_addResourceSpans', () => { encodedBodySize: null, decodedBodySize: null, nextHopProtocol: 'h3', + connectStart: 1000, + connectEnd: 1001, + redirectStart: 1002, + redirectEnd: 1003, + fetchStart: 1004, + domainLookupStart: 1005, + domainLookupEnd: 1006, + requestStart: 1007, + responseStart: 1008, + responseEnd: 1009, + secureConnectionStart: 1005, + workerStart: 1006, } as unknown as PerformanceResourceTiming; _addResourceSpans(span, entry, resourceEntryName, 100, 23, 345); @@ -518,7 +542,7 @@ describe('_addResourceSpans', () => { 'http.request.response_end': expect.any(Number), 'http.request.response_start': expect.any(Number), 'http.request.secure_connection_start': expect.any(Number), - 'http.request.time_to_first_byte': 0, + 'http.request.time_to_first_byte': 1.008, 'http.request.worker_start': expect.any(Number), }, description: '/assets/to/css', diff --git a/packages/browser-utils/test/metrics/resourceTiming.test.ts b/packages/browser-utils/test/metrics/resourceTiming.test.ts index 881a7075441e..5e35097423d1 100644 --- a/packages/browser-utils/test/metrics/resourceTiming.test.ts +++ b/packages/browser-utils/test/metrics/resourceTiming.test.ts @@ -26,7 +26,7 @@ describe('resourceTimingToSpanAttributes', () => { duration: 200, initiatorType: 'fetch', nextHopProtocol: 'h2', - workerStart: 0, + workerStart: 1, redirectStart: 10, redirectEnd: 20, fetchStart: 25, @@ -276,6 +276,13 @@ describe('resourceTimingToSpanAttributes', () => { }); it('handles zero timing values', () => { + /** + * Most resource timing entries have a 0 value if the resource was requested from + * a cross-origin source which does not return a matching `Timing-Allow-Origin` header. + * + * see: https://developer.mozilla.org/en-US/docs/Web/API/Performance_API/Resource_timing#cross-origin_timing_information + */ + extractNetworkProtocolSpy.mockReturnValue({ name: '', version: 'unknown', @@ -284,15 +291,17 @@ describe('resourceTimingToSpanAttributes', () => { const mockResourceTiming = createMockResourceTiming({ nextHopProtocol: '', redirectStart: 0, - fetchStart: 0, + redirectEnd: 0, + workerStart: 0, + fetchStart: 1000100, // fetchStart is not restricted by `Timing-Allow-Origin` header domainLookupStart: 0, domainLookupEnd: 0, connectStart: 0, - secureConnectionStart: 0, connectEnd: 0, + secureConnectionStart: 0, requestStart: 0, responseStart: 0, - responseEnd: 0, + responseEnd: 1000200, // responseEnd is not restricted by `Timing-Allow-Origin` header }); const result = resourceTimingToSpanAttributes(mockResourceTiming); @@ -300,18 +309,18 @@ describe('resourceTimingToSpanAttributes', () => { expect(result).toEqual({ 'network.protocol.version': 'unknown', 'network.protocol.name': '', - 'http.request.redirect_start': 1000, // (1000000 + 0) / 1000 - 'http.request.redirect_end': 1000.02, - 'http.request.worker_start': 1000, - 'http.request.fetch_start': 1000, - 'http.request.domain_lookup_start': 1000, - 'http.request.domain_lookup_end': 1000, - 'http.request.connect_start': 1000, - 'http.request.secure_connection_start': 1000, - 'http.request.connection_end': 1000, - 'http.request.request_start': 1000, - 'http.request.response_start': 1000, - 'http.request.response_end': 1000, + 'http.request.redirect_start': 0, + 'http.request.redirect_end': 0, + 'http.request.worker_start': 0, + 'http.request.fetch_start': 2000.1, + 'http.request.domain_lookup_start': 0, + 'http.request.domain_lookup_end': 0, + 'http.request.connect_start': 0, + 'http.request.secure_connection_start': 0, + 'http.request.connection_end': 0, + 'http.request.request_start': 0, + 'http.request.response_start': 0, + 'http.request.response_end': 2000.2, 'http.request.time_to_first_byte': 0, }); }); @@ -343,7 +352,7 @@ describe('resourceTimingToSpanAttributes', () => { 'network.protocol.name': 'http', 'http.request.redirect_start': 1000.005, 'http.request.redirect_end': 1000.02, - 'http.request.worker_start': 1000, + 'http.request.worker_start': 1000.001, 'http.request.fetch_start': 1000.01, 'http.request.domain_lookup_start': 1000.015, 'http.request.domain_lookup_end': 1000.02, @@ -470,7 +479,7 @@ describe('resourceTimingToSpanAttributes', () => { }); describe('edge cases', () => { - it('handles undefined timing values', () => { + it("doesn't include undefined timing values", () => { browserPerformanceTimeOriginSpy.mockReturnValue(1000000); extractNetworkProtocolSpy.mockReturnValue({ @@ -481,6 +490,7 @@ describe('resourceTimingToSpanAttributes', () => { const mockResourceTiming = createMockResourceTiming({ nextHopProtocol: '', redirectStart: undefined as any, + redirectEnd: undefined as any, fetchStart: undefined as any, workerStart: undefined as any, domainLookupStart: undefined as any, @@ -498,19 +508,6 @@ describe('resourceTimingToSpanAttributes', () => { expect(result).toEqual({ 'network.protocol.version': 'unknown', 'network.protocol.name': '', - 'http.request.redirect_start': 1000, // (1000000 + 0) / 1000 - 'http.request.redirect_end': 1000.02, - 'http.request.worker_start': 1000, - 'http.request.fetch_start': 1000, - 'http.request.domain_lookup_start': 1000, - 'http.request.domain_lookup_end': 1000, - 'http.request.connect_start': 1000, - 'http.request.secure_connection_start': 1000, - 'http.request.connection_end': 1000, - 'http.request.request_start': 1000, - 'http.request.response_start': 1000, - 'http.request.response_end': 1000, - 'http.request.time_to_first_byte': 0, }); }); @@ -534,6 +531,7 @@ describe('resourceTimingToSpanAttributes', () => { requestStart: 999999, responseStart: 999999, responseEnd: 999999, + workerStart: 999999, }); const result = resourceTimingToSpanAttributes(mockResourceTiming); @@ -543,7 +541,7 @@ describe('resourceTimingToSpanAttributes', () => { 'network.protocol.name': '', 'http.request.redirect_start': 1999.999, // (1000000 + 999999) / 1000 'http.request.redirect_end': 1000.02, - 'http.request.worker_start': 1000, + 'http.request.worker_start': 1999.999, 'http.request.fetch_start': 1999.999, 'http.request.domain_lookup_start': 1999.999, 'http.request.domain_lookup_end': 1999.999, From e41942063250a2e735eb36e4cab0f3b93b058b8f Mon Sep 17 00:00:00 2001 From: Francesco Gringl-Novy Date: Wed, 24 Sep 2025 16:15:48 +0200 Subject: [PATCH 09/12] docs: Reword changelog for google gen ai instrumentation (#17753) Rewording this as it is not actually correct, this is not auto-instrumented but needs to be done manually - see https://github.com/getsentry/sentry-javascript/pull/17723 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6350f087f454..77915d8a557f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ Work in this release was contributed by @Karibash. Thank you for your contributi - **feat(cloudflare,vercel-edge): Add support for Google Gen AI instrumentation ([#17723](https://github.com/getsentry/sentry-javascript/pull/17723))** - The SDK now automatically instruments Google's Generative AI operations in Cloudflare Workers and Vercel Edge Runtime environments, providing insights into your AI operations. + The SDK now supports manually instrumenting Google's Generative AI operations in Cloudflare Workers and Vercel Edge Runtime environments, providing insights into your AI operations. You can use `const wrappedClient = Sentry.instrumentGoogleGenAIClient(genAiClient)` to get an instrumented client. ### Other Changes From 34538c8d8d24f0fa702a595827e1fb14f55536ab Mon Sep 17 00:00:00 2001 From: Sigrid Huemer <32902192+s1gr1d@users.noreply.github.com> Date: Wed, 24 Sep 2025 16:18:54 +0200 Subject: [PATCH 10/12] feat(cloudflare): Add `honoIntegration` with error-filtering function (#17743) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds a `honoIntegration` that exposes a `shouldHandleError` function that lets users define a custom handling of capturing errors. By default, we capture exceptions with `error.status >= 500 || error.status <= 299`. It's possible to modify this behavior like this: ```js integrations: [ honoIntegration({ shouldHandleError: (err) => true; // always capture exceptions in onError }) ] ``` **Up for discussion**: The function name is the same like in express/fastify to keep it consistent. But I was also thinking about other names like `shouldHandleInOnError` or `shouldCaptureOnError` 🤔 Because it's specifically about the `onError` function. Let me know what you think. closes https://github.com/getsentry/sentry-javascript/issues/17717 --- CHANGELOG.md | 15 +++ packages/cloudflare/src/handler.ts | 5 +- packages/cloudflare/src/index.ts | 1 + packages/cloudflare/src/integrations/hono.ts | 74 +++++++++++++++ packages/cloudflare/src/sdk.ts | 2 + packages/cloudflare/test/handler.test.ts | 13 +-- .../cloudflare/test/integrations/hono.test.ts | 94 +++++++++++++++++++ packages/cloudflare/tsconfig.test.json | 2 +- packages/cloudflare/vite.config.ts | 6 ++ 9 files changed, 203 insertions(+), 9 deletions(-) create mode 100644 packages/cloudflare/src/integrations/hono.ts create mode 100644 packages/cloudflare/test/integrations/hono.test.ts create mode 100644 packages/cloudflare/vite.config.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 77915d8a557f..1537b2377da9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,21 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +- **feat(cloudflare): Add honoIntegration with error-filtering function ([#17743](https://github.com/getsentry/sentry-javascript/pull/17743))** + + This release adds a `honoIntegration` to `@sentry/cloudflare`, which exposes a `shouldHandleError` function that lets you define which errors in `onError` should be captured. + By default, Sentry captures exceptions with `error.status >= 500 || error.status <= 299`. + + The integration is added by default, and it's possible to modify this behavior like this: + + ```js + integrations: [ + honoIntegration({ + shouldHandleError: (err) => true; // always capture exceptions in onError + }) + ] + ``` + Work in this release was contributed by @Karibash. Thank you for your contribution! ## 10.14.0 diff --git a/packages/cloudflare/src/handler.ts b/packages/cloudflare/src/handler.ts index a6e5983902c6..969cb6be72ee 100644 --- a/packages/cloudflare/src/handler.ts +++ b/packages/cloudflare/src/handler.ts @@ -10,6 +10,7 @@ import { import { setAsyncLocalStorageAsyncContextStrategy } from './async'; import type { CloudflareOptions } from './client'; import { isInstrumented, markAsInstrumented } from './instrument'; +import { getHonoIntegration } from './integrations/hono'; import { getFinalOptions } from './options'; import { wrapRequestHandler } from './request'; import { addCloudResourceContext } from './scope-utils'; @@ -48,7 +49,7 @@ export function withSentry | undefined { + return getClient()?.getIntegrationByName(INTEGRATION_NAME); +} + +function isHonoError(err: unknown): err is HonoError { + if (err instanceof Error) { + return true; + } + return typeof err === 'object' && err !== null && 'status' in (err as Record); +} + +const _honoIntegration = ((options: Partial = {}) => { + return { + name: INTEGRATION_NAME, + handleHonoException(err: HonoError): void { + const shouldHandleError = options.shouldHandleError || defaultShouldHandleError; + + if (!isHonoError(err)) { + DEBUG_BUILD && debug.log("[Hono] Won't capture exception in `onError` because it's not a Hono error.", err); + return; + } + + if (shouldHandleError(err)) { + captureException(err, { mechanism: { handled: false, type: 'auto.faas.hono.error_handler' } }); + } else { + DEBUG_BUILD && debug.log('[Hono] Not capturing exception because `shouldHandleError` returned `false`.', err); + } + }, + }; +}) satisfies IntegrationFn; + +/** + * Automatically captures exceptions caught with the `onError` handler in Hono. + * + * The integration is enabled by default. + * + * @example + * integrations: [ + * honoIntegration({ + * shouldHandleError: (err) => true; // always capture exceptions in onError + * }) + * ] + */ +export const honoIntegration = defineIntegration(_honoIntegration); + +/** + * Default function to determine if an error should be sent to Sentry + * + * 3xx and 4xx errors are not sent by default. + */ +function defaultShouldHandleError(error: HonoError): boolean { + const statusCode = error?.status; + // 3xx and 4xx errors are not sent by default. + return statusCode ? statusCode >= 500 || statusCode <= 299 : true; +} diff --git a/packages/cloudflare/src/sdk.ts b/packages/cloudflare/src/sdk.ts index 9d4fb8d749ae..238cc13253a5 100644 --- a/packages/cloudflare/src/sdk.ts +++ b/packages/cloudflare/src/sdk.ts @@ -14,6 +14,7 @@ import type { CloudflareClientOptions, CloudflareOptions } from './client'; import { CloudflareClient } from './client'; import { makeFlushLock } from './flush'; import { fetchIntegration } from './integrations/fetch'; +import { honoIntegration } from './integrations/hono'; import { setupOpenTelemetryTracer } from './opentelemetry/tracer'; import { makeCloudflareTransport } from './transport'; import { defaultStackParser } from './vendor/stacktrace'; @@ -31,6 +32,7 @@ export function getDefaultIntegrations(options: CloudflareOptions): Integration[ functionToStringIntegration(), linkedErrorsIntegration(), fetchIntegration(), + honoIntegration(), // TODO(v11): the `include` object should be defined directly in the integration based on `sendDefaultPii` requestDataIntegration(sendDefaultPii ? undefined : { include: { cookies: false } }), consoleIntegration(), diff --git a/packages/cloudflare/test/handler.test.ts b/packages/cloudflare/test/handler.test.ts index 97e93199ea31..7768689ffc48 100644 --- a/packages/cloudflare/test/handler.test.ts +++ b/packages/cloudflare/test/handler.test.ts @@ -14,6 +14,7 @@ import { beforeEach, describe, expect, onTestFinished, test, vi } from 'vitest'; import { CloudflareClient } from '../src/client'; import { withSentry } from '../src/handler'; import { markAsInstrumented } from '../src/instrument'; +import * as HonoIntegration from '../src/integrations/hono'; // Custom type for hono-like apps (cloudflare handlers) that include errorHandler and onError type HonoLikeApp = ExportedHandler< @@ -1081,10 +1082,12 @@ describe('withSentry', () => { }); describe('hono errorHandler', () => { - test('captures errors handled by the errorHandler', async () => { - const captureExceptionSpy = vi.spyOn(SentryCore, 'captureException'); + test('calls Hono Integration to handle error captured by the errorHandler', async () => { const error = new Error('test hono error'); + const handleHonoException = vi.fn(); + vi.spyOn(HonoIntegration, 'getHonoIntegration').mockReturnValue({ handleHonoException } as any); + const honoApp = { fetch(_request, _env, _context) { return new Response('test'); @@ -1100,10 +1103,8 @@ describe('withSentry', () => { // simulates hono's error handling const errorHandlerResponse = honoApp.errorHandler?.(error); - expect(captureExceptionSpy).toHaveBeenCalledTimes(1); - expect(captureExceptionSpy).toHaveBeenLastCalledWith(error, { - mechanism: { handled: false, type: 'auto.faas.cloudflare.error_handler' }, - }); + expect(handleHonoException).toHaveBeenCalledTimes(1); + expect(handleHonoException).toHaveBeenLastCalledWith(error); expect(errorHandlerResponse?.status).toBe(500); }); diff --git a/packages/cloudflare/test/integrations/hono.test.ts b/packages/cloudflare/test/integrations/hono.test.ts new file mode 100644 index 000000000000..f1b273b3ed2b --- /dev/null +++ b/packages/cloudflare/test/integrations/hono.test.ts @@ -0,0 +1,94 @@ +import * as sentryCore from '@sentry/core'; +import { type Client, createStackParser } from '@sentry/core'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { CloudflareClient } from '../../src/client'; +import { honoIntegration } from '../../src/integrations/hono'; + +class FakeClient extends CloudflareClient { + public getIntegrationByName(name: string) { + return name === 'Hono' ? (honoIntegration() as any) : undefined; + } +} + +type MockHonoIntegrationType = { handleHonoException: (err: Error) => void }; + +describe('Hono integration', () => { + let client: FakeClient; + + beforeEach(() => { + vi.clearAllMocks(); + client = new FakeClient({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [], + transport: () => ({ send: () => Promise.resolve({}), flush: () => Promise.resolve(true) }), + stackParser: createStackParser(), + }); + + vi.spyOn(sentryCore, 'getClient').mockImplementation(() => client as Client); + }); + + it('captures in errorHandler when onError exists', () => { + const captureExceptionSpy = vi.spyOn(sentryCore, 'captureException'); + const integration = honoIntegration(); + integration.setupOnce?.(); + + const error = new Error('hono boom'); + // simulate withSentry wrapping of errorHandler calling back into integration + (integration as unknown as MockHonoIntegrationType).handleHonoException(error); + + expect(captureExceptionSpy).toHaveBeenCalledTimes(1); + expect(captureExceptionSpy).toHaveBeenLastCalledWith(error, { + mechanism: { handled: false, type: 'auto.faas.hono.error_handler' }, + }); + }); + + it('does not capture for 4xx status', () => { + const captureExceptionSpy = vi.spyOn(sentryCore, 'captureException'); + const integration = honoIntegration(); + integration.setupOnce?.(); + + (integration as unknown as MockHonoIntegrationType).handleHonoException( + Object.assign(new Error('client err'), { status: 404 }), + ); + expect(captureExceptionSpy).not.toHaveBeenCalled(); + }); + + it('does not capture for 3xx status', () => { + const captureExceptionSpy = vi.spyOn(sentryCore, 'captureException'); + const integration = honoIntegration(); + integration.setupOnce?.(); + + (integration as unknown as MockHonoIntegrationType).handleHonoException( + Object.assign(new Error('redirect'), { status: 302 }), + ); + expect(captureExceptionSpy).not.toHaveBeenCalled(); + }); + + it('captures for 5xx status', () => { + const captureExceptionSpy = vi.spyOn(sentryCore, 'captureException'); + const integration = honoIntegration(); + integration.setupOnce?.(); + + const err = Object.assign(new Error('server err'), { status: 500 }); + (integration as unknown as MockHonoIntegrationType).handleHonoException(err); + expect(captureExceptionSpy).toHaveBeenCalledTimes(1); + }); + + it('captures if no status is present on Error', () => { + const captureExceptionSpy = vi.spyOn(sentryCore, 'captureException'); + const integration = honoIntegration(); + integration.setupOnce?.(); + + (integration as unknown as MockHonoIntegrationType).handleHonoException(new Error('no status')); + expect(captureExceptionSpy).toHaveBeenCalledTimes(1); + }); + + it('supports custom shouldHandleError option', () => { + const captureExceptionSpy = vi.spyOn(sentryCore, 'captureException'); + const integration = honoIntegration({ shouldHandleError: () => false }); + integration.setupOnce?.(); + + (integration as unknown as MockHonoIntegrationType).handleHonoException(new Error('blocked')); + expect(captureExceptionSpy).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/cloudflare/tsconfig.test.json b/packages/cloudflare/tsconfig.test.json index 42d9d0df227e..00cada2d8bcf 100644 --- a/packages/cloudflare/tsconfig.test.json +++ b/packages/cloudflare/tsconfig.test.json @@ -1,7 +1,7 @@ { "extends": "./tsconfig.json", - "include": ["test/**/*"], + "include": ["test/**/*", "vite.config.ts"], "compilerOptions": { // other package-specific, test-specific options diff --git a/packages/cloudflare/vite.config.ts b/packages/cloudflare/vite.config.ts new file mode 100644 index 000000000000..b2150cd225a4 --- /dev/null +++ b/packages/cloudflare/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vitest/config'; +import baseConfig from '../../vite/vite.config'; + +export default defineConfig({ + ...baseConfig, +}); From 75796c5127d1c94df13a417e189e5e04ca260f8d Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Wed, 24 Sep 2025 16:05:36 +0200 Subject: [PATCH 11/12] meta(changelog): Update changelog for 10.15.0 --- CHANGELOG.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1537b2377da9..ca5dd9918a40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +## 10.15.0 + +### Important Changes + - **feat(cloudflare): Add honoIntegration with error-filtering function ([#17743](https://github.com/getsentry/sentry-javascript/pull/17743))** This release adds a `honoIntegration` to `@sentry/cloudflare`, which exposes a `shouldHandleError` function that lets you define which errors in `onError` should be captured. @@ -19,6 +23,31 @@ ] ``` +- **feat(node): Add instrumentation for hono handler ([#17428](https://github.com/getsentry/sentry-javascript/pull/17428))** + +This PR enhances the Hono integration by adding comprehensive handler instrumentation, error handling capabilities. + +- **feat(aws): Enable Lambda extension by default when using the Lamba layer ([#17684](https://github.com/getsentry/sentry-javascript/pull/17684))** + +- **feat(browser): Add `setActiveSpanInBrowser` to set an active span in the browser ([#17714](https://github.com/getsentry/sentry-javascript/pull/17714))** + +This PR adds a feature to the browser SDKs only: Making an inactive span active. We do this to enable use cases where having a span only being active in the callback is not practical. + +### Other Changes + +- fix(browser): Improve handling of `0` and `undefined` resource timing values ([#17751](https://github.com/getsentry/sentry-javascript/pull/17751)) +- ref(nextjs): Display build compatibility warning for webpack ([#17746](https://github.com/getsentry/sentry-javascript/pull/17746)) + +
+ Internal Changes + +- docs: Reword changelog for google gen ai instrumentation ([#17753](https://github.com/getsentry/sentry-javascript/pull/17753)) +- build: Add `@typescript-eslint/no-unnecessary-type-assertion` rule ([#17728](https://github.com/getsentry/sentry-javascript/pull/17728)) +- build: Update TS target to `es2020` everywhere ([#17709](https://github.com/getsentry/sentry-javascript/pull/17709)) +- chore: Add external contributor to CHANGELOG.md ([#17745](https://github.com/getsentry/sentry-javascript/pull/17745)) + +
+ Work in this release was contributed by @Karibash. Thank you for your contribution! ## 10.14.0 From d1366f42f242eb9576b30ed305574bf27fdd3396 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Wed, 24 Sep 2025 15:31:08 +0000 Subject: [PATCH 12/12] release: 10.15.0 --- .../browser-integration-tests/package.json | 4 ++-- .../bundle-analyzer-scenarios/package.json | 2 +- dev-packages/clear-cache-gh-action/package.json | 2 +- .../cloudflare-integration-tests/package.json | 6 +++--- dev-packages/e2e-tests/package.json | 2 +- .../external-contributor-gh-action/package.json | 2 +- .../node-core-integration-tests/package.json | 6 +++--- dev-packages/node-integration-tests/package.json | 10 +++++----- dev-packages/node-overhead-gh-action/package.json | 4 ++-- dev-packages/rollup-utils/package.json | 2 +- dev-packages/size-limit-gh-action/package.json | 2 +- dev-packages/test-utils/package.json | 4 ++-- lerna.json | 2 +- packages/angular/package.json | 6 +++--- packages/astro/package.json | 8 ++++---- packages/aws-serverless/package.json | 6 +++--- packages/browser-utils/package.json | 4 ++-- packages/browser/package.json | 14 +++++++------- packages/bun/package.json | 6 +++--- packages/cloudflare/package.json | 4 ++-- packages/core/package.json | 2 +- packages/deno/package.json | 4 ++-- packages/ember/package.json | 6 +++--- packages/eslint-config-sdk/package.json | 6 +++--- packages/eslint-plugin-sdk/package.json | 2 +- packages/feedback/package.json | 4 ++-- packages/gatsby/package.json | 6 +++--- packages/google-cloud-serverless/package.json | 6 +++--- packages/integration-shims/package.json | 4 ++-- packages/nestjs/package.json | 6 +++--- packages/nextjs/package.json | 14 +++++++------- packages/node-core/package.json | 6 +++--- packages/node-native/package.json | 6 +++--- packages/node/package.json | 8 ++++---- packages/nuxt/package.json | 12 ++++++------ packages/opentelemetry/package.json | 4 ++-- packages/pino-transport/package.json | 6 +++--- packages/profiling-node/package.json | 6 +++--- packages/react-router/package.json | 10 +++++----- packages/react/package.json | 6 +++--- packages/remix/package.json | 8 ++++---- packages/replay-canvas/package.json | 6 +++--- packages/replay-internal/package.json | 8 ++++---- packages/replay-worker/package.json | 2 +- packages/solid/package.json | 6 +++--- packages/solidstart/package.json | 8 ++++---- packages/svelte/package.json | 6 +++--- packages/sveltekit/package.json | 10 +++++----- packages/tanstackstart-react/package.json | 10 +++++----- packages/tanstackstart/package.json | 2 +- packages/types/package.json | 4 ++-- packages/typescript/package.json | 2 +- packages/vercel-edge/package.json | 6 +++--- packages/vue/package.json | 6 +++--- packages/wasm/package.json | 6 +++--- 55 files changed, 155 insertions(+), 155 deletions(-) diff --git a/dev-packages/browser-integration-tests/package.json b/dev-packages/browser-integration-tests/package.json index d086c88c2383..0f06b5b9cb90 100644 --- a/dev-packages/browser-integration-tests/package.json +++ b/dev-packages/browser-integration-tests/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/browser-integration-tests", - "version": "10.14.0", + "version": "10.15.0", "main": "index.js", "license": "MIT", "engines": { @@ -43,7 +43,7 @@ "@babel/preset-typescript": "^7.16.7", "@playwright/test": "~1.53.2", "@sentry-internal/rrweb": "2.34.0", - "@sentry/browser": "10.14.0", + "@sentry/browser": "10.15.0", "@supabase/supabase-js": "2.49.3", "axios": "1.8.2", "babel-loader": "^8.2.2", diff --git a/dev-packages/bundle-analyzer-scenarios/package.json b/dev-packages/bundle-analyzer-scenarios/package.json index a6979802028c..aefad1717477 100644 --- a/dev-packages/bundle-analyzer-scenarios/package.json +++ b/dev-packages/bundle-analyzer-scenarios/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/bundle-analyzer-scenarios", - "version": "10.14.0", + "version": "10.15.0", "description": "Scenarios to test bundle analysis with", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/dev-packages/bundle-analyzer-scenarios", diff --git a/dev-packages/clear-cache-gh-action/package.json b/dev-packages/clear-cache-gh-action/package.json index eb05510731c0..f235d00a367d 100644 --- a/dev-packages/clear-cache-gh-action/package.json +++ b/dev-packages/clear-cache-gh-action/package.json @@ -1,7 +1,7 @@ { "name": "@sentry-internal/clear-cache-gh-action", "description": "An internal Github Action to clear GitHub caches.", - "version": "10.14.0", + "version": "10.15.0", "license": "MIT", "engines": { "node": ">=18" diff --git a/dev-packages/cloudflare-integration-tests/package.json b/dev-packages/cloudflare-integration-tests/package.json index 3b9bbcce5931..ebab25a00609 100644 --- a/dev-packages/cloudflare-integration-tests/package.json +++ b/dev-packages/cloudflare-integration-tests/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/cloudflare-integration-tests", - "version": "10.14.0", + "version": "10.15.0", "license": "MIT", "engines": { "node": ">=18" @@ -13,11 +13,11 @@ "test:watch": "yarn test --watch" }, "dependencies": { - "@sentry/cloudflare": "10.14.0" + "@sentry/cloudflare": "10.15.0" }, "devDependencies": { "@cloudflare/workers-types": "^4.20250922.0", - "@sentry-internal/test-utils": "10.14.0", + "@sentry-internal/test-utils": "10.15.0", "vitest": "^3.2.4", "wrangler": "4.22.0" }, diff --git a/dev-packages/e2e-tests/package.json b/dev-packages/e2e-tests/package.json index 7a36e57d65b2..da52e7ebf9a2 100644 --- a/dev-packages/e2e-tests/package.json +++ b/dev-packages/e2e-tests/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/e2e-tests", - "version": "10.14.0", + "version": "10.15.0", "license": "MIT", "private": true, "scripts": { diff --git a/dev-packages/external-contributor-gh-action/package.json b/dev-packages/external-contributor-gh-action/package.json index d38eb397cab2..a4a4f7b99266 100644 --- a/dev-packages/external-contributor-gh-action/package.json +++ b/dev-packages/external-contributor-gh-action/package.json @@ -1,7 +1,7 @@ { "name": "@sentry-internal/external-contributor-gh-action", "description": "An internal Github Action to add external contributors to the CHANGELOG.md file.", - "version": "10.14.0", + "version": "10.15.0", "license": "MIT", "engines": { "node": ">=18" diff --git a/dev-packages/node-core-integration-tests/package.json b/dev-packages/node-core-integration-tests/package.json index e227071a7082..5a33b7c710d2 100644 --- a/dev-packages/node-core-integration-tests/package.json +++ b/dev-packages/node-core-integration-tests/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/node-core-integration-tests", - "version": "10.14.0", + "version": "10.15.0", "license": "MIT", "engines": { "node": ">=18" @@ -34,8 +34,8 @@ "@opentelemetry/resources": "^2.1.0", "@opentelemetry/sdk-trace-base": "^2.1.0", "@opentelemetry/semantic-conventions": "^1.37.0", - "@sentry/core": "10.14.0", - "@sentry/node-core": "10.14.0", + "@sentry/core": "10.15.0", + "@sentry/node-core": "10.15.0", "body-parser": "^1.20.3", "cors": "^2.8.5", "cron": "^3.1.6", diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index e8ee0afb9f5b..b9069e7b6c58 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/node-integration-tests", - "version": "10.14.0", + "version": "10.15.0", "license": "MIT", "engines": { "node": ">=18" @@ -32,9 +32,9 @@ "@nestjs/core": "11.1.3", "@nestjs/platform-express": "11.1.3", "@prisma/client": "6.15.0", - "@sentry/aws-serverless": "10.14.0", - "@sentry/core": "10.14.0", - "@sentry/node": "10.14.0", + "@sentry/aws-serverless": "10.15.0", + "@sentry/core": "10.15.0", + "@sentry/node": "10.15.0", "@types/mongodb": "^3.6.20", "@types/mysql": "^2.15.21", "@types/pg": "^8.6.5", @@ -77,7 +77,7 @@ "yargs": "^16.2.0" }, "devDependencies": { - "@sentry-internal/test-utils": "10.14.0", + "@sentry-internal/test-utils": "10.15.0", "@types/amqplib": "^0.10.5", "@types/node-cron": "^3.0.11", "@types/node-schedule": "^2.1.7", diff --git a/dev-packages/node-overhead-gh-action/package.json b/dev-packages/node-overhead-gh-action/package.json index 032732e056db..b1bd26279098 100644 --- a/dev-packages/node-overhead-gh-action/package.json +++ b/dev-packages/node-overhead-gh-action/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/node-overhead-gh-action", - "version": "10.14.0", + "version": "10.15.0", "license": "MIT", "engines": { "node": ">=18" @@ -23,7 +23,7 @@ "fix": "eslint . --format stylish --fix" }, "dependencies": { - "@sentry/node": "10.14.0", + "@sentry/node": "10.15.0", "express": "^4.21.1", "mysql2": "^3.14.4" }, diff --git a/dev-packages/rollup-utils/package.json b/dev-packages/rollup-utils/package.json index 4ec829a47fbc..470dedbd5f42 100644 --- a/dev-packages/rollup-utils/package.json +++ b/dev-packages/rollup-utils/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/rollup-utils", - "version": "10.14.0", + "version": "10.15.0", "description": "Rollup utilities used at Sentry for the Sentry JavaScript SDK", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/rollup-utils", diff --git a/dev-packages/size-limit-gh-action/package.json b/dev-packages/size-limit-gh-action/package.json index e8e817899a16..98953335f288 100644 --- a/dev-packages/size-limit-gh-action/package.json +++ b/dev-packages/size-limit-gh-action/package.json @@ -1,7 +1,7 @@ { "name": "@sentry-internal/size-limit-gh-action", "description": "An internal Github Action to compare the current size of a PR against the one on develop.", - "version": "10.14.0", + "version": "10.15.0", "license": "MIT", "engines": { "node": ">=18" diff --git a/dev-packages/test-utils/package.json b/dev-packages/test-utils/package.json index 291aebab0629..df1cd72d4bff 100644 --- a/dev-packages/test-utils/package.json +++ b/dev-packages/test-utils/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "10.14.0", + "version": "10.15.0", "name": "@sentry-internal/test-utils", "author": "Sentry", "license": "MIT", @@ -48,7 +48,7 @@ }, "devDependencies": { "@playwright/test": "~1.53.2", - "@sentry/core": "10.14.0" + "@sentry/core": "10.15.0" }, "volta": { "extends": "../../package.json" diff --git a/lerna.json b/lerna.json index ac571e2f4218..ec2646c4569c 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", - "version": "10.14.0", + "version": "10.15.0", "npmClient": "yarn" } diff --git a/packages/angular/package.json b/packages/angular/package.json index 0c63bb990142..8fe9acac76a8 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/angular", - "version": "10.14.0", + "version": "10.15.0", "description": "Official Sentry SDK for Angular", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/angular", @@ -21,8 +21,8 @@ "rxjs": "^6.5.5 || ^7.x" }, "dependencies": { - "@sentry/browser": "10.14.0", - "@sentry/core": "10.14.0", + "@sentry/browser": "10.15.0", + "@sentry/core": "10.15.0", "tslib": "^2.4.1" }, "devDependencies": { diff --git a/packages/astro/package.json b/packages/astro/package.json index b09f89be12a2..e66449a303ca 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/astro", - "version": "10.14.0", + "version": "10.15.0", "description": "Official Sentry SDK for Astro", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/astro", @@ -56,9 +56,9 @@ "astro": ">=3.x || >=4.0.0-beta || >=5.x" }, "dependencies": { - "@sentry/browser": "10.14.0", - "@sentry/core": "10.14.0", - "@sentry/node": "10.14.0", + "@sentry/browser": "10.15.0", + "@sentry/core": "10.15.0", + "@sentry/node": "10.15.0", "@sentry/vite-plugin": "^4.1.0" }, "devDependencies": { diff --git a/packages/aws-serverless/package.json b/packages/aws-serverless/package.json index 560626ed6578..f5361a8428a3 100644 --- a/packages/aws-serverless/package.json +++ b/packages/aws-serverless/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/aws-serverless", - "version": "10.14.0", + "version": "10.15.0", "description": "Official Sentry SDK for AWS Lambda and AWS Serverless Environments", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/aws-serverless", @@ -69,8 +69,8 @@ "@opentelemetry/instrumentation": "^0.204.0", "@opentelemetry/instrumentation-aws-sdk": "0.59.0", "@opentelemetry/semantic-conventions": "^1.37.0", - "@sentry/core": "10.14.0", - "@sentry/node": "10.14.0", + "@sentry/core": "10.15.0", + "@sentry/node": "10.15.0", "@types/aws-lambda": "^8.10.62" }, "devDependencies": { diff --git a/packages/browser-utils/package.json b/packages/browser-utils/package.json index ac311da388b2..dfa69ad9ef25 100644 --- a/packages/browser-utils/package.json +++ b/packages/browser-utils/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/browser-utils", - "version": "10.14.0", + "version": "10.15.0", "description": "Browser Utilities for all Sentry JavaScript SDKs", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/browser-utils", @@ -39,7 +39,7 @@ "access": "public" }, "dependencies": { - "@sentry/core": "10.14.0" + "@sentry/core": "10.15.0" }, "scripts": { "build": "run-p build:transpile build:types", diff --git a/packages/browser/package.json b/packages/browser/package.json index dea278d94fce..8521b2370858 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/browser", - "version": "10.14.0", + "version": "10.15.0", "description": "Official Sentry SDK for browsers", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/browser", @@ -39,14 +39,14 @@ "access": "public" }, "dependencies": { - "@sentry-internal/browser-utils": "10.14.0", - "@sentry-internal/feedback": "10.14.0", - "@sentry-internal/replay": "10.14.0", - "@sentry-internal/replay-canvas": "10.14.0", - "@sentry/core": "10.14.0" + "@sentry-internal/browser-utils": "10.15.0", + "@sentry-internal/feedback": "10.15.0", + "@sentry-internal/replay": "10.15.0", + "@sentry-internal/replay-canvas": "10.15.0", + "@sentry/core": "10.15.0" }, "devDependencies": { - "@sentry-internal/integration-shims": "10.14.0", + "@sentry-internal/integration-shims": "10.15.0", "fake-indexeddb": "^4.0.1" }, "scripts": { diff --git a/packages/bun/package.json b/packages/bun/package.json index b254ed09fe94..ffac9e99ef2f 100644 --- a/packages/bun/package.json +++ b/packages/bun/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/bun", - "version": "10.14.0", + "version": "10.15.0", "description": "Official Sentry SDK for bun", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/bun", @@ -39,8 +39,8 @@ "access": "public" }, "dependencies": { - "@sentry/core": "10.14.0", - "@sentry/node": "10.14.0" + "@sentry/core": "10.15.0", + "@sentry/node": "10.15.0" }, "devDependencies": { "bun-types": "^1.2.9" diff --git a/packages/cloudflare/package.json b/packages/cloudflare/package.json index f1796797bc30..eebf9ef1ef1f 100644 --- a/packages/cloudflare/package.json +++ b/packages/cloudflare/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/cloudflare", - "version": "10.14.0", + "version": "10.15.0", "description": "Official 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", @@ -50,7 +50,7 @@ }, "dependencies": { "@opentelemetry/api": "^1.9.0", - "@sentry/core": "10.14.0" + "@sentry/core": "10.15.0" }, "peerDependencies": { "@cloudflare/workers-types": "^4.x" diff --git a/packages/core/package.json b/packages/core/package.json index 6ae5b8797175..9c11c0774988 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/core", - "version": "10.14.0", + "version": "10.15.0", "description": "Base implementation for all Sentry JavaScript SDKs", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/core", diff --git a/packages/deno/package.json b/packages/deno/package.json index 3ea2d201df79..0ba4798f1b06 100644 --- a/packages/deno/package.json +++ b/packages/deno/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/deno", - "version": "10.14.0", + "version": "10.15.0", "description": "Official Sentry SDK for Deno", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/deno", @@ -25,7 +25,7 @@ ], "dependencies": { "@opentelemetry/api": "^1.9.0", - "@sentry/core": "10.14.0" + "@sentry/core": "10.15.0" }, "scripts": { "deno-types": "node ./scripts/download-deno-types.mjs", diff --git a/packages/ember/package.json b/packages/ember/package.json index 1a41eefe4992..9ccd4e089364 100644 --- a/packages/ember/package.json +++ b/packages/ember/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/ember", - "version": "10.14.0", + "version": "10.15.0", "description": "Official Sentry SDK for Ember.js", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/ember", @@ -32,8 +32,8 @@ "dependencies": { "@babel/core": "^7.27.7", "@embroider/macros": "^1.16.0", - "@sentry/browser": "10.14.0", - "@sentry/core": "10.14.0", + "@sentry/browser": "10.15.0", + "@sentry/core": "10.15.0", "ember-auto-import": "^2.7.2", "ember-cli-babel": "^8.2.0", "ember-cli-htmlbars": "^6.1.1", diff --git a/packages/eslint-config-sdk/package.json b/packages/eslint-config-sdk/package.json index f4e0e3a95b91..fc45bc2cf6a8 100644 --- a/packages/eslint-config-sdk/package.json +++ b/packages/eslint-config-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/eslint-config-sdk", - "version": "10.14.0", + "version": "10.15.0", "description": "Official Sentry SDK eslint config", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/eslint-config-sdk", @@ -22,8 +22,8 @@ "access": "public" }, "dependencies": { - "@sentry-internal/eslint-plugin-sdk": "10.14.0", - "@sentry-internal/typescript": "10.14.0", + "@sentry-internal/eslint-plugin-sdk": "10.15.0", + "@sentry-internal/typescript": "10.15.0", "@typescript-eslint/eslint-plugin": "^5.48.0", "@typescript-eslint/parser": "^5.48.0", "eslint-config-prettier": "^6.11.0", diff --git a/packages/eslint-plugin-sdk/package.json b/packages/eslint-plugin-sdk/package.json index 856691120543..2e24087f70cb 100644 --- a/packages/eslint-plugin-sdk/package.json +++ b/packages/eslint-plugin-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/eslint-plugin-sdk", - "version": "10.14.0", + "version": "10.15.0", "description": "Official Sentry SDK eslint plugin", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/eslint-plugin-sdk", diff --git a/packages/feedback/package.json b/packages/feedback/package.json index 9b81c34cb0c1..37bcbeb66e04 100644 --- a/packages/feedback/package.json +++ b/packages/feedback/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/feedback", - "version": "10.14.0", + "version": "10.15.0", "description": "Sentry SDK integration for user feedback", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/feedback", @@ -39,7 +39,7 @@ "access": "public" }, "dependencies": { - "@sentry/core": "10.14.0" + "@sentry/core": "10.15.0" }, "devDependencies": { "preact": "^10.19.4" diff --git a/packages/gatsby/package.json b/packages/gatsby/package.json index 779ab0cfa5dd..58b73d1bff96 100644 --- a/packages/gatsby/package.json +++ b/packages/gatsby/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/gatsby", - "version": "10.14.0", + "version": "10.15.0", "description": "Official Sentry SDK for Gatsby.js", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/gatsby", @@ -45,8 +45,8 @@ "access": "public" }, "dependencies": { - "@sentry/core": "10.14.0", - "@sentry/react": "10.14.0", + "@sentry/core": "10.15.0", + "@sentry/react": "10.15.0", "@sentry/webpack-plugin": "^4.1.1" }, "peerDependencies": { diff --git a/packages/google-cloud-serverless/package.json b/packages/google-cloud-serverless/package.json index 3fb1deb94d8a..778a465c4dad 100644 --- a/packages/google-cloud-serverless/package.json +++ b/packages/google-cloud-serverless/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/google-cloud-serverless", - "version": "10.14.0", + "version": "10.15.0", "description": "Official Sentry SDK for Google Cloud Functions", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/google-cloud-serverless", @@ -48,8 +48,8 @@ "access": "public" }, "dependencies": { - "@sentry/core": "10.14.0", - "@sentry/node": "10.14.0", + "@sentry/core": "10.15.0", + "@sentry/node": "10.15.0", "@types/express": "^4.17.14" }, "devDependencies": { diff --git a/packages/integration-shims/package.json b/packages/integration-shims/package.json index e5f4518944d9..bfa19a207fda 100644 --- a/packages/integration-shims/package.json +++ b/packages/integration-shims/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/integration-shims", - "version": "10.14.0", + "version": "10.15.0", "description": "Shims for integrations in Sentry SDK.", "main": "build/cjs/index.js", "module": "build/esm/index.js", @@ -56,7 +56,7 @@ "url": "https://github.com/getsentry/sentry-javascript/issues" }, "dependencies": { - "@sentry/core": "10.14.0" + "@sentry/core": "10.15.0" }, "engines": { "node": ">=18" diff --git a/packages/nestjs/package.json b/packages/nestjs/package.json index aaf0e7bb3565..b94a67225206 100644 --- a/packages/nestjs/package.json +++ b/packages/nestjs/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/nestjs", - "version": "10.14.0", + "version": "10.15.0", "description": "Official Sentry SDK for NestJS", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/nestjs", @@ -49,8 +49,8 @@ "@opentelemetry/instrumentation": "^0.204.0", "@opentelemetry/instrumentation-nestjs-core": "0.50.0", "@opentelemetry/semantic-conventions": "^1.37.0", - "@sentry/core": "10.14.0", - "@sentry/node": "10.14.0" + "@sentry/core": "10.15.0", + "@sentry/node": "10.15.0" }, "devDependencies": { "@nestjs/common": "^10.0.0", diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index 403715a4157e..6abe5bb008c3 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/nextjs", - "version": "10.14.0", + "version": "10.15.0", "description": "Official Sentry SDK for Next.js", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/nextjs", @@ -79,13 +79,13 @@ "@opentelemetry/api": "^1.9.0", "@opentelemetry/semantic-conventions": "^1.37.0", "@rollup/plugin-commonjs": "28.0.1", - "@sentry-internal/browser-utils": "10.14.0", + "@sentry-internal/browser-utils": "10.15.0", "@sentry/bundler-plugin-core": "^4.3.0", - "@sentry/core": "10.14.0", - "@sentry/node": "10.14.0", - "@sentry/opentelemetry": "10.14.0", - "@sentry/react": "10.14.0", - "@sentry/vercel-edge": "10.14.0", + "@sentry/core": "10.15.0", + "@sentry/node": "10.15.0", + "@sentry/opentelemetry": "10.15.0", + "@sentry/react": "10.15.0", + "@sentry/vercel-edge": "10.15.0", "@sentry/webpack-plugin": "^4.3.0", "chalk": "3.0.0", "resolve": "1.22.8", diff --git a/packages/node-core/package.json b/packages/node-core/package.json index 830f660bbf87..c0a1fee20d36 100644 --- a/packages/node-core/package.json +++ b/packages/node-core/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/node-core", - "version": "10.14.0", + "version": "10.15.0", "description": "Sentry Node-Core SDK", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/node-core", @@ -66,8 +66,8 @@ "@opentelemetry/semantic-conventions": "^1.37.0" }, "dependencies": { - "@sentry/core": "10.14.0", - "@sentry/opentelemetry": "10.14.0", + "@sentry/core": "10.15.0", + "@sentry/opentelemetry": "10.15.0", "import-in-the-middle": "^1.14.2" }, "devDependencies": { diff --git a/packages/node-native/package.json b/packages/node-native/package.json index f310c434f6cb..6c7ba47deaba 100644 --- a/packages/node-native/package.json +++ b/packages/node-native/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/node-native", - "version": "10.14.0", + "version": "10.15.0", "description": "Native Tools for the Official Sentry Node.js SDK", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/node-native", @@ -64,8 +64,8 @@ }, "dependencies": { "@sentry-internal/node-native-stacktrace": "^0.2.2", - "@sentry/core": "10.14.0", - "@sentry/node": "10.14.0" + "@sentry/core": "10.15.0", + "@sentry/node": "10.15.0" }, "devDependencies": { "@types/node": "^18.19.1" diff --git a/packages/node/package.json b/packages/node/package.json index 93b5ea7fbf62..6bb808447779 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/node", - "version": "10.14.0", + "version": "10.15.0", "description": "Sentry Node SDK using OpenTelemetry for performance instrumentation", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/node", @@ -95,9 +95,9 @@ "@opentelemetry/sdk-trace-base": "^2.1.0", "@opentelemetry/semantic-conventions": "^1.37.0", "@prisma/instrumentation": "6.15.0", - "@sentry/core": "10.14.0", - "@sentry/node-core": "10.14.0", - "@sentry/opentelemetry": "10.14.0", + "@sentry/core": "10.15.0", + "@sentry/node-core": "10.15.0", + "@sentry/opentelemetry": "10.15.0", "import-in-the-middle": "^1.14.2", "minimatch": "^9.0.0" }, diff --git a/packages/nuxt/package.json b/packages/nuxt/package.json index e157ff0352de..07f00fd58f3e 100644 --- a/packages/nuxt/package.json +++ b/packages/nuxt/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/nuxt", - "version": "10.14.0", + "version": "10.15.0", "description": "Official Sentry SDK for Nuxt", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/nuxt", @@ -47,13 +47,13 @@ }, "dependencies": { "@nuxt/kit": "^3.13.2", - "@sentry/browser": "10.14.0", - "@sentry/cloudflare": "10.14.0", - "@sentry/core": "10.14.0", - "@sentry/node": "10.14.0", + "@sentry/browser": "10.15.0", + "@sentry/cloudflare": "10.15.0", + "@sentry/core": "10.15.0", + "@sentry/node": "10.15.0", "@sentry/rollup-plugin": "^4.3.0", "@sentry/vite-plugin": "^4.3.0", - "@sentry/vue": "10.14.0" + "@sentry/vue": "10.15.0" }, "devDependencies": { "@nuxt/module-builder": "^0.8.4", diff --git a/packages/opentelemetry/package.json b/packages/opentelemetry/package.json index 1dd7a9cc168b..2b4657ca1d35 100644 --- a/packages/opentelemetry/package.json +++ b/packages/opentelemetry/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/opentelemetry", - "version": "10.14.0", + "version": "10.15.0", "description": "Official Sentry utilities for OpenTelemetry", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/opentelemetry", @@ -39,7 +39,7 @@ "access": "public" }, "dependencies": { - "@sentry/core": "10.14.0" + "@sentry/core": "10.15.0" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0", diff --git a/packages/pino-transport/package.json b/packages/pino-transport/package.json index 616c506ec089..bd19200b4a0c 100644 --- a/packages/pino-transport/package.json +++ b/packages/pino-transport/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/pino-transport", - "version": "10.14.0", + "version": "10.15.0", "description": "Pino transport for Sentry SDK", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/pino-transport", @@ -39,8 +39,8 @@ "access": "public" }, "dependencies": { - "@sentry/core": "10.14.0", - "@sentry/node": "10.14.0", + "@sentry/core": "10.15.0", + "@sentry/node": "10.15.0", "pino-abstract-transport": "^2.0.0" }, "peerDependencies": { diff --git a/packages/profiling-node/package.json b/packages/profiling-node/package.json index deb257ed7372..c4c3e86387de 100644 --- a/packages/profiling-node/package.json +++ b/packages/profiling-node/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/profiling-node", - "version": "10.14.0", + "version": "10.15.0", "description": "Official Sentry SDK for Node.js Profiling", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/profiling-node", @@ -63,8 +63,8 @@ }, "dependencies": { "@sentry-internal/node-cpu-profiler": "^2.2.0", - "@sentry/core": "10.14.0", - "@sentry/node": "10.14.0" + "@sentry/core": "10.15.0", + "@sentry/node": "10.15.0" }, "devDependencies": { "@types/node": "^18.19.1" diff --git a/packages/react-router/package.json b/packages/react-router/package.json index 52d1b7a66166..b9d339c865db 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/react-router", - "version": "10.14.0", + "version": "10.15.0", "description": "Official Sentry SDK for React Router (Framework)", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/react-router", @@ -49,11 +49,11 @@ "@opentelemetry/core": "^2.1.0", "@opentelemetry/instrumentation": "^0.204.0", "@opentelemetry/semantic-conventions": "^1.37.0", - "@sentry/browser": "10.14.0", + "@sentry/browser": "10.15.0", "@sentry/cli": "^2.53.0", - "@sentry/core": "10.14.0", - "@sentry/node": "10.14.0", - "@sentry/react": "10.14.0", + "@sentry/core": "10.15.0", + "@sentry/node": "10.15.0", + "@sentry/react": "10.15.0", "@sentry/vite-plugin": "^4.1.0", "glob": "11.0.1" }, diff --git a/packages/react/package.json b/packages/react/package.json index 13607540006f..e7e2856e09a1 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/react", - "version": "10.14.0", + "version": "10.15.0", "description": "Official Sentry SDK for React.js", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/react", @@ -39,8 +39,8 @@ "access": "public" }, "dependencies": { - "@sentry/browser": "10.14.0", - "@sentry/core": "10.14.0", + "@sentry/browser": "10.15.0", + "@sentry/core": "10.15.0", "hoist-non-react-statics": "^3.3.2" }, "peerDependencies": { diff --git a/packages/remix/package.json b/packages/remix/package.json index 3bc1c9942dbd..6b2ca7fe93db 100644 --- a/packages/remix/package.json +++ b/packages/remix/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/remix", - "version": "10.14.0", + "version": "10.15.0", "description": "Official Sentry SDK for Remix", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/remix", @@ -69,9 +69,9 @@ "@opentelemetry/semantic-conventions": "^1.37.0", "@remix-run/router": "1.x", "@sentry/cli": "^2.53.0", - "@sentry/core": "10.14.0", - "@sentry/node": "10.14.0", - "@sentry/react": "10.14.0", + "@sentry/core": "10.15.0", + "@sentry/node": "10.15.0", + "@sentry/react": "10.15.0", "glob": "^10.3.4", "yargs": "^17.6.0" }, diff --git a/packages/replay-canvas/package.json b/packages/replay-canvas/package.json index 4c45f539b637..dc758918406e 100644 --- a/packages/replay-canvas/package.json +++ b/packages/replay-canvas/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/replay-canvas", - "version": "10.14.0", + "version": "10.15.0", "description": "Replay canvas integration", "main": "build/npm/cjs/index.js", "module": "build/npm/esm/index.js", @@ -69,8 +69,8 @@ "@sentry-internal/rrweb": "2.37.0" }, "dependencies": { - "@sentry-internal/replay": "10.14.0", - "@sentry/core": "10.14.0" + "@sentry-internal/replay": "10.15.0", + "@sentry/core": "10.15.0" }, "engines": { "node": ">=18" diff --git a/packages/replay-internal/package.json b/packages/replay-internal/package.json index ec588fca16d8..53f31f9053dd 100644 --- a/packages/replay-internal/package.json +++ b/packages/replay-internal/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/replay", - "version": "10.14.0", + "version": "10.15.0", "description": "User replays for Sentry", "main": "build/npm/cjs/index.js", "module": "build/npm/esm/index.js", @@ -81,7 +81,7 @@ "homepage": "https://docs.sentry.io/platforms/javascript/session-replay/", "devDependencies": { "@babel/core": "^7.27.7", - "@sentry-internal/replay-worker": "10.14.0", + "@sentry-internal/replay-worker": "10.15.0", "@sentry-internal/rrweb": "2.37.0", "@sentry-internal/rrweb-snapshot": "2.37.0", "fflate": "0.8.2", @@ -90,8 +90,8 @@ "node-fetch": "^2.6.7" }, "dependencies": { - "@sentry-internal/browser-utils": "10.14.0", - "@sentry/core": "10.14.0" + "@sentry-internal/browser-utils": "10.15.0", + "@sentry/core": "10.15.0" }, "engines": { "node": ">=18" diff --git a/packages/replay-worker/package.json b/packages/replay-worker/package.json index ed2d7147241f..a09417250bd6 100644 --- a/packages/replay-worker/package.json +++ b/packages/replay-worker/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/replay-worker", - "version": "10.14.0", + "version": "10.15.0", "description": "Worker for @sentry-internal/replay", "main": "build/esm/index.js", "module": "build/esm/index.js", diff --git a/packages/solid/package.json b/packages/solid/package.json index eced885b8818..6986e039feef 100644 --- a/packages/solid/package.json +++ b/packages/solid/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/solid", - "version": "10.14.0", + "version": "10.15.0", "description": "Official Sentry SDK for Solid", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/solid", @@ -44,8 +44,8 @@ "access": "public" }, "dependencies": { - "@sentry/browser": "10.14.0", - "@sentry/core": "10.14.0" + "@sentry/browser": "10.15.0", + "@sentry/core": "10.15.0" }, "peerDependencies": { "@solidjs/router": "^0.13.4", diff --git a/packages/solidstart/package.json b/packages/solidstart/package.json index e37c661d3778..2dc2a2f2186d 100644 --- a/packages/solidstart/package.json +++ b/packages/solidstart/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/solidstart", - "version": "10.14.0", + "version": "10.15.0", "description": "Official Sentry SDK for Solid Start", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/solidstart", @@ -66,9 +66,9 @@ } }, "dependencies": { - "@sentry/core": "10.14.0", - "@sentry/node": "10.14.0", - "@sentry/solid": "10.14.0", + "@sentry/core": "10.15.0", + "@sentry/node": "10.15.0", + "@sentry/solid": "10.15.0", "@sentry/vite-plugin": "^4.1.0" }, "devDependencies": { diff --git a/packages/svelte/package.json b/packages/svelte/package.json index b9f5b03c9c5c..4a2b89c34ee8 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/svelte", - "version": "10.14.0", + "version": "10.15.0", "description": "Official Sentry SDK for Svelte", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/svelte", @@ -39,8 +39,8 @@ "access": "public" }, "dependencies": { - "@sentry/browser": "10.14.0", - "@sentry/core": "10.14.0", + "@sentry/browser": "10.15.0", + "@sentry/core": "10.15.0", "magic-string": "^0.30.0" }, "peerDependencies": { diff --git a/packages/sveltekit/package.json b/packages/sveltekit/package.json index f33163768057..9e2fbe3d0026 100644 --- a/packages/sveltekit/package.json +++ b/packages/sveltekit/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/sveltekit", - "version": "10.14.0", + "version": "10.15.0", "description": "Official Sentry SDK for SvelteKit", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/sveltekit", @@ -48,10 +48,10 @@ }, "dependencies": { "@babel/parser": "7.26.9", - "@sentry/cloudflare": "10.14.0", - "@sentry/core": "10.14.0", - "@sentry/node": "10.14.0", - "@sentry/svelte": "10.14.0", + "@sentry/cloudflare": "10.15.0", + "@sentry/core": "10.15.0", + "@sentry/node": "10.15.0", + "@sentry/svelte": "10.15.0", "@sentry/vite-plugin": "^4.1.0", "magic-string": "0.30.7", "recast": "0.23.11", diff --git a/packages/tanstackstart-react/package.json b/packages/tanstackstart-react/package.json index 36b4f1e55146..7aecb67afcdf 100644 --- a/packages/tanstackstart-react/package.json +++ b/packages/tanstackstart-react/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/tanstackstart-react", - "version": "10.14.0", + "version": "10.15.0", "description": "Official Sentry SDK for TanStack Start React", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/tanstackstart-react", @@ -52,10 +52,10 @@ "dependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/semantic-conventions": "^1.37.0", - "@sentry-internal/browser-utils": "10.14.0", - "@sentry/core": "10.14.0", - "@sentry/node": "10.14.0", - "@sentry/react": "10.14.0" + "@sentry-internal/browser-utils": "10.15.0", + "@sentry/core": "10.15.0", + "@sentry/node": "10.15.0", + "@sentry/react": "10.15.0" }, "scripts": { "build": "run-p build:transpile build:types", diff --git a/packages/tanstackstart/package.json b/packages/tanstackstart/package.json index 70d1bbe48d33..fff1c3c27661 100644 --- a/packages/tanstackstart/package.json +++ b/packages/tanstackstart/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/tanstackstart", - "version": "10.14.0", + "version": "10.15.0", "description": "Utilities for the Sentry TanStack Start SDKs", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/tanstackstart", diff --git a/packages/types/package.json b/packages/types/package.json index 2e63198d01a3..3b255cad4906 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/types", - "version": "10.14.0", + "version": "10.15.0", "description": "Types for all Sentry JavaScript SDKs", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/types", @@ -57,7 +57,7 @@ "yalc:publish": "yalc publish --push --sig" }, "dependencies": { - "@sentry/core": "10.14.0" + "@sentry/core": "10.15.0" }, "volta": { "extends": "../../package.json" diff --git a/packages/typescript/package.json b/packages/typescript/package.json index ffce0a43a4e0..4eb309ad5eb1 100644 --- a/packages/typescript/package.json +++ b/packages/typescript/package.json @@ -1,6 +1,6 @@ { "name": "@sentry-internal/typescript", - "version": "10.14.0", + "version": "10.15.0", "description": "Typescript configuration used at Sentry", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/typescript", diff --git a/packages/vercel-edge/package.json b/packages/vercel-edge/package.json index d262d5d479df..034e50082396 100644 --- a/packages/vercel-edge/package.json +++ b/packages/vercel-edge/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/vercel-edge", - "version": "10.14.0", + "version": "10.15.0", "description": "Official Sentry SDK for the Vercel Edge Runtime", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/vercel-edge", @@ -41,14 +41,14 @@ "dependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/resources": "^2.1.0", - "@sentry/core": "10.14.0" + "@sentry/core": "10.15.0" }, "devDependencies": { "@edge-runtime/types": "3.0.1", "@opentelemetry/core": "^2.1.0", "@opentelemetry/sdk-trace-base": "^2.1.0", "@opentelemetry/semantic-conventions": "^1.37.0", - "@sentry/opentelemetry": "10.14.0" + "@sentry/opentelemetry": "10.15.0" }, "scripts": { "build": "run-p build:transpile build:types", diff --git a/packages/vue/package.json b/packages/vue/package.json index a74318e92b9a..b75bcca15e80 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/vue", - "version": "10.14.0", + "version": "10.15.0", "description": "Official Sentry SDK for Vue.js", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/vue", @@ -39,8 +39,8 @@ "access": "public" }, "dependencies": { - "@sentry/browser": "10.14.0", - "@sentry/core": "10.14.0" + "@sentry/browser": "10.15.0", + "@sentry/core": "10.15.0" }, "peerDependencies": { "pinia": "2.x || 3.x", diff --git a/packages/wasm/package.json b/packages/wasm/package.json index 2133a44da38b..f5276e2c8dad 100644 --- a/packages/wasm/package.json +++ b/packages/wasm/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/wasm", - "version": "10.14.0", + "version": "10.15.0", "description": "Support for WASM.", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/wasm", @@ -39,8 +39,8 @@ "access": "public" }, "dependencies": { - "@sentry/browser": "10.14.0", - "@sentry/core": "10.14.0" + "@sentry/browser": "10.15.0", + "@sentry/core": "10.15.0" }, "scripts": { "build": "run-p build:transpile build:bundle build:types",