diff --git a/CHANGELOG.md b/CHANGELOG.md index 319465691cd5..ca5dd9918a40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,13 +4,59 @@ - "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. + 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 + }) + ] + ``` + +- **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 ### Important Changes - **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 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/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/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/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/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/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/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 23b9f99da2f8..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.20250708.0", - "@sentry-internal/test-utils": "10.14.0", + "@cloudflare/workers-types": "^4.20250922.0", + "@sentry-internal/test-utils": "10.15.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/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/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/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-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-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/package.json b/dev-packages/node-integration-tests/package.json index bf183115df5f..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" @@ -27,13 +27,14 @@ "@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", "@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", @@ -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", @@ -75,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-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/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/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/dev-packages/node-integration-tests/utils/runner.ts b/dev-packages/node-integration-tests/utils/runner.ts index dfe27dc5a4a5..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; @@ -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]]) => { @@ -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/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/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/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/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/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/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/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/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', + }), + ); + }); }); }); 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/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/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-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/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/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-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, 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/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/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/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/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/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/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/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/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/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/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/cloudflare/package.json b/packages/cloudflare/package.json index 50727b5e9937..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" @@ -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/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/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/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/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/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/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/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, +}); 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/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/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'; 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/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/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/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/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/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/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-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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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(); + }); + }); }); 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/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-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/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-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-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/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-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/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/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/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 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/packages/node/src/utils/redisCache.ts b/packages/node/src/utils/redisCache.ts index b9d6cb814856..476a257fbc6d 100644 --- a/packages/node/src/utils/redisCache.ts +++ b/packages/node/src/utils/redisCache.ts @@ -103,9 +103,9 @@ function flatten(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/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/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/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/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/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-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-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/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/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/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/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/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/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-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/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-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/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/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/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/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/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/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/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/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/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/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-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/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/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/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/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/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/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/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/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; } } 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/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/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/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", 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..299b1415d89a 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" @@ -4596,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" @@ -18604,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"