From 063c4dc3280e82f6cb18fbc74f5a46a206f25ced Mon Sep 17 00:00:00 2001 From: Andrei <168741329+andreiborza@users.noreply.github.com> Date: Thu, 18 Dec 2025 13:41:59 +0100 Subject: [PATCH 1/7] Merge pull request #18562 from getsentry/ab/skip-ci-when-no-code-changes chore(ci): Skip build job when no code changes present --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b351bdc647a0..9382c42fe693 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -128,7 +128,7 @@ jobs: needs.job_get_metadata.outputs.changed_any_code == 'true' || needs.job_get_metadata.outputs.is_base_branch == 'true' || needs.job_get_metadata.outputs.is_release == 'true' || - (needs.job_get_metadata.outputs.is_gitflow_sync == 'false' && needs.job_get_metadata.outputs.has_gitflow_label == 'false') + (needs.job_get_metadata.outputs.is_gitflow_sync == 'false' && needs.job_get_metadata.outputs.has_gitflow_label == 'false' && needs.job_get_metadata.outputs.changed_any_code == 'true') steps: - name: Check out base commit (${{ github.event.pull_request.base.sha }}) uses: actions/checkout@v6 From 3fda84df88bd93ecadba2c809fa9be02e2588ced Mon Sep 17 00:00:00 2001 From: Sigrid <32902192+s1gr1d@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:36:01 +0100 Subject: [PATCH 2/7] fix(cloudflare): Add hono transaction name when error is thrown (#18529) If the error is caught with the hono error handler, no `trace` data was sent alongside the event. This PR fixed this. Before: image After: image --- .../cloudflare-integration-tests/expect.ts | 3 +- .../cloudflare-integration-tests/package.json | 3 +- .../suites/hono/basic/index.ts | 33 ++++++++++++ .../suites/hono/basic/test.ts | 53 +++++++++++++++++++ .../suites/hono/basic/wrangler.jsonc | 7 +++ packages/cloudflare/src/handler.ts | 4 +- packages/cloudflare/src/integrations/hono.ts | 34 +++++++++++- packages/cloudflare/test/handler.test.ts | 3 +- .../cloudflare/test/integrations/hono.test.ts | 27 ++++++++-- yarn.lock | 8 +-- 10 files changed, 159 insertions(+), 16 deletions(-) create mode 100644 dev-packages/cloudflare-integration-tests/suites/hono/basic/index.ts create mode 100644 dev-packages/cloudflare-integration-tests/suites/hono/basic/test.ts create mode 100644 dev-packages/cloudflare-integration-tests/suites/hono/basic/wrangler.jsonc diff --git a/dev-packages/cloudflare-integration-tests/expect.ts b/dev-packages/cloudflare-integration-tests/expect.ts index 5caec668cd13..11631832852b 100644 --- a/dev-packages/cloudflare-integration-tests/expect.ts +++ b/dev-packages/cloudflare-integration-tests/expect.ts @@ -58,7 +58,7 @@ export function expectedEvent(event: Event): Event { }); } -export function eventEnvelope(event: Event): Envelope { +export function eventEnvelope(event: Event, includeSampleRand = false): Envelope { return [ { event_id: UUID_MATCHER, @@ -69,6 +69,7 @@ export function eventEnvelope(event: Event): Envelope { public_key: 'public', trace_id: UUID_MATCHER, sample_rate: expect.any(String), + ...(includeSampleRand && { sample_rand: expect.stringMatching(/^[01](\.\d+)?$/) }), sampled: expect.any(String), transaction: expect.any(String), }, diff --git a/dev-packages/cloudflare-integration-tests/package.json b/dev-packages/cloudflare-integration-tests/package.json index bebe56ca85a7..462f804e11cf 100644 --- a/dev-packages/cloudflare-integration-tests/package.json +++ b/dev-packages/cloudflare-integration-tests/package.json @@ -14,7 +14,8 @@ }, "dependencies": { "@langchain/langgraph": "^1.0.1", - "@sentry/cloudflare": "10.32.0" + "@sentry/cloudflare": "10.32.0", + "hono": "^4.0.0" }, "devDependencies": { "@cloudflare/workers-types": "^4.20250922.0", diff --git a/dev-packages/cloudflare-integration-tests/suites/hono/basic/index.ts b/dev-packages/cloudflare-integration-tests/suites/hono/basic/index.ts new file mode 100644 index 000000000000..6daae5f3f141 --- /dev/null +++ b/dev-packages/cloudflare-integration-tests/suites/hono/basic/index.ts @@ -0,0 +1,33 @@ +import * as Sentry from '@sentry/cloudflare'; +import { Hono } from 'hono'; + +interface Env { + SENTRY_DSN: string; +} + +const app = new Hono<{ Bindings: Env }>(); + +app.get('/', c => { + return c.text('Hello from Hono on Cloudflare!'); +}); + +app.get('/json', c => { + return c.json({ message: 'Hello from Hono', framework: 'hono', platform: 'cloudflare' }); +}); + +app.get('/error', () => { + throw new Error('Test error from Hono app'); +}); + +app.get('/hello/:name', c => { + const name = c.req.param('name'); + return c.text(`Hello, ${name}!`); +}); + +export default Sentry.withSentry( + (env: Env) => ({ + dsn: env.SENTRY_DSN, + tracesSampleRate: 1.0, + }), + app, +); diff --git a/dev-packages/cloudflare-integration-tests/suites/hono/basic/test.ts b/dev-packages/cloudflare-integration-tests/suites/hono/basic/test.ts new file mode 100644 index 000000000000..9d7eb264f76e --- /dev/null +++ b/dev-packages/cloudflare-integration-tests/suites/hono/basic/test.ts @@ -0,0 +1,53 @@ +import { expect, it } from 'vitest'; +import { eventEnvelope } from '../../../expect'; +import { createRunner } from '../../../runner'; + +it('Hono app captures errors', async ({ signal }) => { + const runner = createRunner(__dirname) + // First envelope: error event from Hono error handler + .expect( + eventEnvelope( + { + level: 'error', + transaction: 'GET /error', + exception: { + values: [ + { + type: 'Error', + value: 'Test error from Hono app', + stacktrace: { + frames: expect.any(Array), + }, + mechanism: { type: 'auto.faas.hono.error_handler', handled: false }, + }, + ], + }, + request: { + headers: expect.any(Object), + method: 'GET', + url: expect.any(String), + }, + }, + true, + ), + ) + // Second envelope: transaction event + .expect(envelope => { + const transactionEvent = envelope[1]?.[0]?.[1]; + expect(transactionEvent).toEqual( + expect.objectContaining({ + type: 'transaction', + transaction: 'GET /error', + contexts: expect.objectContaining({ + trace: expect.objectContaining({ + op: 'http.server', + status: 'internal_error', + }), + }), + }), + ); + }) + .start(signal); + await runner.makeRequest('get', '/error', { expectError: true }); + await runner.completed(); +}); diff --git a/dev-packages/cloudflare-integration-tests/suites/hono/basic/wrangler.jsonc b/dev-packages/cloudflare-integration-tests/suites/hono/basic/wrangler.jsonc new file mode 100644 index 000000000000..39bc81f5c8cd --- /dev/null +++ b/dev-packages/cloudflare-integration-tests/suites/hono/basic/wrangler.jsonc @@ -0,0 +1,7 @@ +{ + "name": "hono-basic-worker", + "compatibility_date": "2025-06-17", + "main": "index.ts", + "compatibility_flags": ["nodejs_compat"] +} + diff --git a/packages/cloudflare/src/handler.ts b/packages/cloudflare/src/handler.ts index 8c6d02791d0f..f7e8353906ec 100644 --- a/packages/cloudflare/src/handler.ts +++ b/packages/cloudflare/src/handler.ts @@ -67,9 +67,9 @@ export function withSentry< ) { handler.errorHandler = new Proxy(handler.errorHandler, { apply(target, thisArg, args) { - const [err] = args; + const [err, context] = args; - getHonoIntegration()?.handleHonoException(err); + getHonoIntegration()?.handleHonoException(err, context); return Reflect.apply(target, thisArg, args); }, diff --git a/packages/cloudflare/src/integrations/hono.ts b/packages/cloudflare/src/integrations/hono.ts index 7cb7b9efa42f..94d8e47057bc 100644 --- a/packages/cloudflare/src/integrations/hono.ts +++ b/packages/cloudflare/src/integrations/hono.ts @@ -1,5 +1,14 @@ import type { IntegrationFn } from '@sentry/core'; -import { captureException, debug, defineIntegration, getClient } from '@sentry/core'; +import { + captureException, + debug, + defineIntegration, + getActiveSpan, + getClient, + getIsolationScope, + getRootSpan, + updateSpanName, +} from '@sentry/core'; import { DEBUG_BUILD } from '../debug-build'; const INTEGRATION_NAME = 'Hono'; @@ -8,6 +17,11 @@ interface HonoError extends Error { status?: number; } +// Minimal type - only exported for tests +export interface HonoContext { + req: { method: string; path?: string }; +} + export interface Options { /** * Callback method deciding whether error should be captured and sent to Sentry @@ -28,10 +42,14 @@ function isHonoError(err: unknown): err is HonoError { return typeof err === 'object' && err !== null && 'status' in (err as Record); } +// Vendored from https://github.com/honojs/hono/blob/d3abeb1f801aaa1b334285c73da5f5f022dbcadb/src/helper/route/index.ts#L58-L59 +const routePath = (c: HonoContext): string => c.req?.path ?? ''; + const _honoIntegration = ((options: Partial = {}) => { return { name: INTEGRATION_NAME, - handleHonoException(err: HonoError): void { + // Hono error handler: https://github.com/honojs/hono/blob/d3abeb1f801aaa1b334285c73da5f5f022dbcadb/src/hono-base.ts#L35 + handleHonoException(err: HonoError, context: HonoContext): void { const shouldHandleError = options.shouldHandleError || defaultShouldHandleError; if (!isHonoError(err)) { @@ -40,6 +58,18 @@ const _honoIntegration = ((options: Partial = {}) => { } if (shouldHandleError(err)) { + if (context) { + const activeSpan = getActiveSpan(); + const spanName = `${context.req.method} ${routePath(context)}`; + + if (activeSpan) { + activeSpan.updateName(spanName); + updateSpanName(getRootSpan(activeSpan), spanName); + } + + getIsolationScope().setTransactionName(spanName); + } + 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); diff --git a/packages/cloudflare/test/handler.test.ts b/packages/cloudflare/test/handler.test.ts index 15fa3effcd7f..24ceeeded151 100644 --- a/packages/cloudflare/test/handler.test.ts +++ b/packages/cloudflare/test/handler.test.ts @@ -1108,7 +1108,8 @@ describe('withSentry', () => { const errorHandlerResponse = honoApp.errorHandler?.(error); expect(handleHonoException).toHaveBeenCalledTimes(1); - expect(handleHonoException).toHaveBeenLastCalledWith(error); + // 2nd param is context, which is undefined here + expect(handleHonoException).toHaveBeenLastCalledWith(error, undefined); expect(errorHandlerResponse?.status).toBe(500); }); diff --git a/packages/cloudflare/test/integrations/hono.test.ts b/packages/cloudflare/test/integrations/hono.test.ts index f1b273b3ed2b..94f23f684a5d 100644 --- a/packages/cloudflare/test/integrations/hono.test.ts +++ b/packages/cloudflare/test/integrations/hono.test.ts @@ -2,6 +2,7 @@ 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 type { HonoContext } from '../../src/integrations/hono'; import { honoIntegration } from '../../src/integrations/hono'; class FakeClient extends CloudflareClient { @@ -10,7 +11,11 @@ class FakeClient extends CloudflareClient { } } -type MockHonoIntegrationType = { handleHonoException: (err: Error) => void }; +type MockHonoIntegrationType = { handleHonoException: (err: Error, ctx: HonoContext) => void }; + +const sampleContext: HonoContext = { + req: { method: 'GET', path: '/vitest-sample' }, +}; describe('Hono integration', () => { let client: FakeClient; @@ -34,7 +39,7 @@ describe('Hono integration', () => { const error = new Error('hono boom'); // simulate withSentry wrapping of errorHandler calling back into integration - (integration as unknown as MockHonoIntegrationType).handleHonoException(error); + (integration as unknown as MockHonoIntegrationType).handleHonoException(error, sampleContext); expect(captureExceptionSpy).toHaveBeenCalledTimes(1); expect(captureExceptionSpy).toHaveBeenLastCalledWith(error, { @@ -49,6 +54,7 @@ describe('Hono integration', () => { (integration as unknown as MockHonoIntegrationType).handleHonoException( Object.assign(new Error('client err'), { status: 404 }), + sampleContext, ); expect(captureExceptionSpy).not.toHaveBeenCalled(); }); @@ -60,6 +66,7 @@ describe('Hono integration', () => { (integration as unknown as MockHonoIntegrationType).handleHonoException( Object.assign(new Error('redirect'), { status: 302 }), + sampleContext, ); expect(captureExceptionSpy).not.toHaveBeenCalled(); }); @@ -70,7 +77,7 @@ describe('Hono integration', () => { integration.setupOnce?.(); const err = Object.assign(new Error('server err'), { status: 500 }); - (integration as unknown as MockHonoIntegrationType).handleHonoException(err); + (integration as unknown as MockHonoIntegrationType).handleHonoException(err, sampleContext); expect(captureExceptionSpy).toHaveBeenCalledTimes(1); }); @@ -79,7 +86,7 @@ describe('Hono integration', () => { const integration = honoIntegration(); integration.setupOnce?.(); - (integration as unknown as MockHonoIntegrationType).handleHonoException(new Error('no status')); + (integration as unknown as MockHonoIntegrationType).handleHonoException(new Error('no status'), sampleContext); expect(captureExceptionSpy).toHaveBeenCalledTimes(1); }); @@ -88,7 +95,17 @@ describe('Hono integration', () => { const integration = honoIntegration({ shouldHandleError: () => false }); integration.setupOnce?.(); - (integration as unknown as MockHonoIntegrationType).handleHonoException(new Error('blocked')); + (integration as unknown as MockHonoIntegrationType).handleHonoException(new Error('blocked'), sampleContext); expect(captureExceptionSpy).not.toHaveBeenCalled(); }); + + it('does not throw error without passed context and still captures', () => { + const captureExceptionSpy = vi.spyOn(sentryCore, 'captureException'); + const integration = honoIntegration(); + integration.setupOnce?.(); + + // @ts-expect-error context is not passed + (integration as unknown as MockHonoIntegrationType).handleHonoException(new Error()); + expect(captureExceptionSpy).toHaveBeenCalledTimes(1); + }); }); diff --git a/yarn.lock b/yarn.lock index db80dbff16ae..e62326e9210f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18848,10 +18848,10 @@ 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== +hono@^4.0.0, hono@^4.9.8: + version "4.11.1" + resolved "https://registry.yarnpkg.com/hono/-/hono-4.11.1.tgz#cb1b0c045fc74a96c693927234c95a45fb46ab0b" + integrity sha512-KsFcH0xxHes0J4zaQgWbYwmz3UPOOskdqZmItstUG93+Wk1ePBLkLGwbP9zlmh1BFUiL8Qp+Xfu9P7feJWpGNg== hookable@^5.5.3: version "5.5.3" From 12f30071d512d2a2f5d693ac04b7448024466150 Mon Sep 17 00:00:00 2001 From: Sigrid <32902192+s1gr1d@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:37:16 +0100 Subject: [PATCH 3/7] fix(ember): Make `implementation` field optional (`hash` routes) (#18564) Related PR: https://github.com/getsentry/sentry-javascript/pull/3195 Closes https://github.com/getsentry/sentry-javascript/issues/18543 --- .../sentry-performance.ts | 8 +- packages/ember/addon/types.ts | 2 +- .../unit/instrument-router-location-test.ts | 100 ++++++++++++++++++ 3 files changed, 106 insertions(+), 4 deletions(-) create mode 100644 packages/ember/tests/unit/instrument-router-location-test.ts diff --git a/packages/ember/addon/instance-initializers/sentry-performance.ts b/packages/ember/addon/instance-initializers/sentry-performance.ts index 666e9f245839..4c5491c6a5a4 100644 --- a/packages/ember/addon/instance-initializers/sentry-performance.ts +++ b/packages/ember/addon/instance-initializers/sentry-performance.ts @@ -84,13 +84,15 @@ function getTransitionInformation( }; } -function getLocationURL(location: EmberRouterMain['location']): string { +// Only exported for testing +export function _getLocationURL(location: EmberRouterMain['location']): string { if (!location?.getURL || !location?.formatURL) { return ''; } const url = location.formatURL(location.getURL()); - if (location.implementation === 'hash') { + // `implementation` is optional in Ember's predefined location types, so we also check if the URL starts with '#'. + if (location.implementation === 'hash' || url.startsWith('#')) { return `${location.rootURL}${url}`; } return url; @@ -110,7 +112,7 @@ export function _instrumentEmberRouter( // Maintaining backwards compatibility with config.browserTracingOptions, but passing it with Sentry options is preferred. const browserTracingOptions = config.browserTracingOptions || config.sentry.browserTracingOptions || {}; - const url = getLocationURL(location); + const url = _getLocationURL(location); const client = getClient(); diff --git a/packages/ember/addon/types.ts b/packages/ember/addon/types.ts index a66a290004f0..887fa59b9901 100644 --- a/packages/ember/addon/types.ts +++ b/packages/ember/addon/types.ts @@ -29,7 +29,7 @@ export interface EmberRouterMain { location: { getURL?: () => string; formatURL?: (url: string) => string; - implementation: string; + implementation?: string; rootURL: string; }; } diff --git a/packages/ember/tests/unit/instrument-router-location-test.ts b/packages/ember/tests/unit/instrument-router-location-test.ts new file mode 100644 index 000000000000..16cc95da906a --- /dev/null +++ b/packages/ember/tests/unit/instrument-router-location-test.ts @@ -0,0 +1,100 @@ +import type { EmberRouterMain } from '@sentry/ember/addon/types'; +import { _getLocationURL } from '@sentry/ember/instance-initializers/sentry-performance'; +import { setupTest } from 'ember-qunit'; +import { module, test } from 'qunit'; +import type { SentryTestContext } from '../helpers/setup-sentry'; +import { setupSentryTest } from '../helpers/setup-sentry'; + +module('Unit | Utility | instrument-router-location', function (hooks) { + setupTest(hooks); + setupSentryTest(hooks); + + test('getLocationURL handles hash location without implementation field', function (this: SentryTestContext, assert) { + // This simulates the default Ember HashLocation which doesn't include the implementation field + const mockLocation: EmberRouterMain['location'] = { + getURL: () => '#/test-route', + formatURL: (url: string) => url, + rootURL: '/', + }; + + const result = _getLocationURL(mockLocation); + assert.strictEqual(result, '/#/test-route', 'Should prepend rootURL to hash URL when implementation is not set'); + }); + + test('_getLocationURL handles hash location with implementation field', function (this: SentryTestContext, assert) { + // This simulates a custom HashLocation with explicit implementation field + const mockLocation: EmberRouterMain['location'] = { + getURL: () => '#/test-route', + formatURL: (url: string) => url, + implementation: 'hash', + rootURL: '/', + }; + + const result = _getLocationURL(mockLocation); + assert.strictEqual(result, '/#/test-route', 'Should prepend rootURL to hash URL when implementation is hash'); + }); + + test('_getLocationURL handles history location', function (this: SentryTestContext, assert) { + // This simulates a history location + const mockLocation: EmberRouterMain['location'] = { + getURL: () => '/test-route', + formatURL: (url: string) => url, + implementation: 'history', + rootURL: '/', + }; + + const result = _getLocationURL(mockLocation); + assert.strictEqual(result, '/test-route', 'Should return URL as-is for non-hash locations'); + }); + + test('_getLocationURL handles none location type', function (this: SentryTestContext, assert) { + // This simulates a 'none' location (often used in tests) + const mockLocation: EmberRouterMain['location'] = { + getURL: () => '', + formatURL: (url: string) => url, + implementation: 'none', + rootURL: '/', + }; + + const result = _getLocationURL(mockLocation); + assert.strictEqual(result, '', 'Should return empty string when URL is empty'); + }); + + test('_getLocationURL handles custom rootURL for hash location', function (this: SentryTestContext, assert) { + // Test with non-root rootURL + const mockLocation: EmberRouterMain['location'] = { + getURL: () => '#/test-route', + formatURL: (url: string) => url, + rootURL: '/my-app/', + }; + + const result = _getLocationURL(mockLocation); + assert.strictEqual( + result, + '/my-app/#/test-route', + 'Should prepend custom rootURL to hash URL when implementation is not set', + ); + }); + + test('_getLocationURL handles location without getURL method', function (this: SentryTestContext, assert) { + // This simulates an incomplete location object + const mockLocation: EmberRouterMain['location'] = { + formatURL: (url: string) => url, + rootURL: '/', + }; + + const result = _getLocationURL(mockLocation); + assert.strictEqual(result, '', 'Should return empty string when getURL is not available'); + }); + + test('_getLocationURL handles location without formatURL method', function (this: SentryTestContext, assert) { + // This simulates an incomplete location object + const mockLocation: EmberRouterMain['location'] = { + getURL: () => '#/test-route', + rootURL: '/', + }; + + const result = _getLocationURL(mockLocation); + assert.strictEqual(result, '', 'Should return empty string when formatURL is not available'); + }); +}); From d1dd30805303a097ed0987ab71a59bc8922f9e1c Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Thu, 18 Dec 2025 17:47:17 +0100 Subject: [PATCH 4/7] chore(test): Remove `cloudflare-astro` e2e test (#18567) This removes a test that only asserted on successful deployment of a static cloudflare app. No actual tests. I don't think there's much value in this but it required a repo-specific env secret we can now get rid of. (fwiw, long-term we definitely want cloudflare-deployed e2e tests but we'll likely need a better test setup anyway) Closes #18568 (added automatically) --- .github/workflows/build.yml | 9 -- .../cloudflare-astro/.gitignore | 21 --- .../cloudflare-astro/astro.config.mjs | 24 ---- .../cloudflare-astro/package.json | 33 ----- .../cloudflare-astro/public/favicon.svg | 9 -- .../cloudflare-astro/sentry.client.config.mjs | 5 - .../cloudflare-astro/sentry.server.mjs | 5 - .../src/components/Card.astro | 61 --------- .../cloudflare-astro/src/env.d.ts | 1 - .../cloudflare-astro/src/layouts/Layout.astro | 46 ------- .../cloudflare-astro/src/pages/index.astro | 123 ------------------ 11 files changed, 337 deletions(-) delete mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-astro/.gitignore delete mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-astro/astro.config.mjs delete mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-astro/package.json delete mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-astro/public/favicon.svg delete mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-astro/sentry.client.config.mjs delete mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-astro/sentry.server.mjs delete mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-astro/src/components/Card.astro delete mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-astro/src/env.d.ts delete mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-astro/src/layouts/Layout.astro delete mode 100644 dev-packages/e2e-tests/test-applications/cloudflare-astro/src/pages/index.astro diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9382c42fe693..94dc3db3942d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1144,15 +1144,6 @@ jobs: retention-days: 7 if-no-files-found: ignore - - name: Deploy Astro to Cloudflare - uses: cloudflare/wrangler-action@v3 - if: matrix.test-application == 'cloudflare-astro' - with: - apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} - accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - command: pages deploy dist --project-name=${{ secrets.CLOUDFLARE_PROJECT_NAME }} - workingDirectory: ${{ runner.temp }}/test-application - job_required_jobs_passed: name: All required jobs passed or were skipped needs: diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-astro/.gitignore b/dev-packages/e2e-tests/test-applications/cloudflare-astro/.gitignore deleted file mode 100644 index 6d4c0aa066aa..000000000000 --- a/dev-packages/e2e-tests/test-applications/cloudflare-astro/.gitignore +++ /dev/null @@ -1,21 +0,0 @@ -# build output -dist/ - -# generated types -.astro/ - -# dependencies -node_modules/ - -# logs -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* - -# environment variables -.env -.env.production - -# macOS-specific files -.DS_Store diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-astro/astro.config.mjs b/dev-packages/e2e-tests/test-applications/cloudflare-astro/astro.config.mjs deleted file mode 100644 index 026e6e4dac7c..000000000000 --- a/dev-packages/e2e-tests/test-applications/cloudflare-astro/astro.config.mjs +++ /dev/null @@ -1,24 +0,0 @@ -import cloudflare from '@astrojs/cloudflare'; -import sentry from '@sentry/astro'; -import { defineConfig } from 'astro/config'; - -const dsn = process.env.E2E_TEST_DSN; - -// https://astro.build/config -export default defineConfig({ - adapter: cloudflare({ - imageService: 'passthrough', - }), - integrations: [ - sentry({ - enabled: Boolean(dsn), - dsn, - sourceMapsUploadOptions: { - enabled: false, - }, - // purposefully setting the default name for client - // and a custom name for server to test both cases - serverInitPath: 'sentry.server.mjs', - }), - ], -}); diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-astro/package.json b/dev-packages/e2e-tests/test-applications/cloudflare-astro/package.json deleted file mode 100644 index 776cf271e86e..000000000000 --- a/dev-packages/e2e-tests/test-applications/cloudflare-astro/package.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "cloudflare-astro", - "type": "module", - "version": "0.0.1", - "private": true, - "scripts": { - "astro": "astro", - "build": "astro build", - "build:bundle": "astro build", - "clean": "npx rimraf node_modules pnpm-lock.yaml", - "dev": "astro dev", - "preview": "astro preview", - "start": "astro dev", - "test:prod": "pnpm -v", - "test:dev": "pnpm -v", - "test:build": "pnpm install && pnpm build", - "test:assert": "pnpm -v" - }, - "dependencies": { - "@astrojs/cloudflare": "12.6.11", - "@sentry/astro": "latest || *", - "astro": "5.15.9" - }, - "devDependencies": { - "@astrojs/internal-helpers": "0.4.1" - }, - "volta": { - "extends": "../../package.json" - }, - "sentryTest": { - "optional": true - } -} diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-astro/public/favicon.svg b/dev-packages/e2e-tests/test-applications/cloudflare-astro/public/favicon.svg deleted file mode 100644 index f157bd1c5e28..000000000000 --- a/dev-packages/e2e-tests/test-applications/cloudflare-astro/public/favicon.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-astro/sentry.client.config.mjs b/dev-packages/e2e-tests/test-applications/cloudflare-astro/sentry.client.config.mjs deleted file mode 100644 index 3e3a697a2903..000000000000 --- a/dev-packages/e2e-tests/test-applications/cloudflare-astro/sentry.client.config.mjs +++ /dev/null @@ -1,5 +0,0 @@ -import * as Sentry from '@sentry/astro'; - -Sentry.init({ - dsn: process.env.E2E_TEST_DSN, -}); diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-astro/sentry.server.mjs b/dev-packages/e2e-tests/test-applications/cloudflare-astro/sentry.server.mjs deleted file mode 100644 index 3e3a697a2903..000000000000 --- a/dev-packages/e2e-tests/test-applications/cloudflare-astro/sentry.server.mjs +++ /dev/null @@ -1,5 +0,0 @@ -import * as Sentry from '@sentry/astro'; - -Sentry.init({ - dsn: process.env.E2E_TEST_DSN, -}); diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-astro/src/components/Card.astro b/dev-packages/e2e-tests/test-applications/cloudflare-astro/src/components/Card.astro deleted file mode 100644 index 4924058df81b..000000000000 --- a/dev-packages/e2e-tests/test-applications/cloudflare-astro/src/components/Card.astro +++ /dev/null @@ -1,61 +0,0 @@ ---- -interface Props { - title: string; - body: string; - href: string; -} - -const { href, title, body } = Astro.props; ---- - - - diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-astro/src/env.d.ts b/dev-packages/e2e-tests/test-applications/cloudflare-astro/src/env.d.ts deleted file mode 100644 index f964fe0cffd8..000000000000 --- a/dev-packages/e2e-tests/test-applications/cloudflare-astro/src/env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-astro/src/layouts/Layout.astro b/dev-packages/e2e-tests/test-applications/cloudflare-astro/src/layouts/Layout.astro deleted file mode 100644 index 121182ff669f..000000000000 --- a/dev-packages/e2e-tests/test-applications/cloudflare-astro/src/layouts/Layout.astro +++ /dev/null @@ -1,46 +0,0 @@ ---- -interface Props { - title: string; -} - -const { title } = Astro.props; ---- - - - - - - - - - - {title} - - - - - - diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-astro/src/pages/index.astro b/dev-packages/e2e-tests/test-applications/cloudflare-astro/src/pages/index.astro deleted file mode 100644 index 0c8795be01ce..000000000000 --- a/dev-packages/e2e-tests/test-applications/cloudflare-astro/src/pages/index.astro +++ /dev/null @@ -1,123 +0,0 @@ ---- -import Layout from '../layouts/Layout.astro'; -import Card from '../components/Card.astro'; ---- - - -
- -

Welcome to Astro

-

- To get started, open the directory src/pages in your project.
- Code Challenge: Tweak the "Welcome to Astro" message above. -

- -
-
- - From 71384a2487c4e044c5c8f9d5cfa2ce4ac71e6a11 Mon Sep 17 00:00:00 2001 From: isaacs Date: Wed, 10 Dec 2025 13:40:57 -0800 Subject: [PATCH 5/7] chore(lint): prefer 'unknown' to 'any', fix lint warnings Explicitly enable `any` usage in typescript in: - `dev-packages`, since those are largely integration and E2E tests. (This is done with the addition of a `dev-packages/.eslintrc.js` file.) - `packages/*/test/`, since those are all tests. (This is done with a rule override added to the root `.eslintrc.js` file.) - Several one-off allowances, generally for vendored types from third party sources, and cases involving function type extension/overriding that TypeScript can't follow. (These are done with file/line overrides, explicit `as` casting, and adding type inference to the `isInstanceOf` method.) In other places (ie, in exported production code paths), replace `any` with `unknown`, and upgrade the `@typescript/no-explicit-any` lint rule to an error. This silences a lot of eslint warnings that were habitually ignored, and encourages us to not opt out of strict type safety in our exported code. --- .eslintrc.js | 6 ++++++ dev-packages/.eslintrc.js | 7 +++++++ .../browser-integration-tests/.eslintrc.js | 2 +- dev-packages/bundler-tests/.eslintrc.js | 2 +- dev-packages/clear-cache-gh-action/.eslintrc.cjs | 2 +- .../cloudflare-integration-tests/.eslintrc.js | 2 +- .../suites/tracing/google-genai/mocks.ts | 1 - dev-packages/e2e-tests/.eslintrc.js | 2 +- .../external-contributor-gh-action/.eslintrc.cjs | 2 +- .../node-core-integration-tests/.eslintrc.js | 2 +- dev-packages/node-integration-tests/.eslintrc.js | 2 +- dev-packages/node-overhead-gh-action/.eslintrc.cjs | 2 +- dev-packages/rollup-utils/.eslintrc.cjs | 2 +- dev-packages/size-limit-gh-action/.eslintrc.cjs | 2 +- dev-packages/test-utils/.eslintrc.js | 2 +- .../instrumentation-aws-lambda/types.ts | 1 + packages/cloudflare/src/durableobject.ts | 14 +++++++------- packages/core/src/integrations/supabase.ts | 1 + packages/core/src/types-hoist/breadcrumb.ts | 3 +++ packages/core/src/types-hoist/context.ts | 1 + packages/core/src/types-hoist/error.ts | 2 ++ packages/core/src/types-hoist/event.ts | 1 + packages/core/src/types-hoist/instrument.ts | 2 ++ packages/core/src/types-hoist/integration.ts | 1 + packages/core/src/types-hoist/misc.ts | 2 ++ packages/core/src/types-hoist/polymorphics.ts | 4 ++++ packages/core/src/types-hoist/samplingcontext.ts | 2 ++ packages/core/src/types-hoist/stackframe.ts | 4 ++++ packages/core/src/types-hoist/user.ts | 2 ++ packages/core/src/utils/aggregate-errors.ts | 8 ++++---- packages/core/src/utils/is.ts | 4 +++- packages/core/test/lib/utils/is.test.ts | 11 ++++++++++- packages/feedback/src/modal/integration.tsx | 4 ++-- packages/nextjs/src/config/types.ts | 4 ++-- .../node/src/integrations/tracing/fastify/types.ts | 1 + .../tracing/fastify/v3/instrumentation.ts | 1 + .../src/integrations/tracing/fastify/v3/types.ts | 1 + .../src/integrations/tracing/fastify/v3/utils.ts | 1 + .../tracing/firebase/otel/patches/firestore.ts | 2 ++ .../integrations/tracing/firebase/otel/types.ts | 1 + .../node/src/integrations/tracing/postgresjs.ts | 1 + packages/remix/src/utils/types.ts | 3 ++- packages/remix/src/vendor/instrumentation.ts | 3 +++ packages/replay-internal/rollup.npm.config.mjs | 1 + packages/replay-internal/src/types/rrweb.ts | 2 ++ .../async-local-storage-context-manager.ts | 3 ++- packages/vercel-edge/test/wintercg-fetch.test.ts | 3 +-- 47 files changed, 98 insertions(+), 34 deletions(-) create mode 100644 dev-packages/.eslintrc.js diff --git a/.eslintrc.js b/.eslintrc.js index 5266f0117a89..ca67fc429584 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -23,6 +23,9 @@ module.exports = { 'types/**', 'scripts/*.js', ], + rules: { + '@typescript-eslint/no-explicit-any': 'error', + }, reportUnusedDisableDirectives: true, overrides: [ { @@ -36,6 +39,9 @@ module.exports = { parserOptions: { project: ['tsconfig.test.json'], }, + rules: { + '@typescript-eslint/no-explicit-any': 'off', + }, }, { files: ['scripts/**/*.ts'], diff --git a/dev-packages/.eslintrc.js b/dev-packages/.eslintrc.js new file mode 100644 index 000000000000..15dafc98d9db --- /dev/null +++ b/dev-packages/.eslintrc.js @@ -0,0 +1,7 @@ +module.exports = { + extends: ['../.eslintrc.js'], + rules: { + // tests often have just cause to do evil + '@typescript-eslint/no-explicit-any': 'off', + }, +}; diff --git a/dev-packages/browser-integration-tests/.eslintrc.js b/dev-packages/browser-integration-tests/.eslintrc.js index 8c07222e9a7c..6e8960a45a06 100644 --- a/dev-packages/browser-integration-tests/.eslintrc.js +++ b/dev-packages/browser-integration-tests/.eslintrc.js @@ -4,7 +4,7 @@ module.exports = { node: true, }, // todo: remove regexp plugin from here once we add it to base.js eslint config for the whole project - extends: ['../../.eslintrc.js', 'plugin:regexp/recommended'], + extends: ['../.eslintrc.js', 'plugin:regexp/recommended'], plugins: ['regexp'], ignorePatterns: [ 'suites/**/subject.js', diff --git a/dev-packages/bundler-tests/.eslintrc.js b/dev-packages/bundler-tests/.eslintrc.js index a65cf78e0b57..5c6808c0f73e 100644 --- a/dev-packages/bundler-tests/.eslintrc.js +++ b/dev-packages/bundler-tests/.eslintrc.js @@ -1,5 +1,5 @@ module.exports = { - extends: ['../../.eslintrc.js'], + extends: ['../.eslintrc.js'], parserOptions: { sourceType: 'module', }, diff --git a/dev-packages/clear-cache-gh-action/.eslintrc.cjs b/dev-packages/clear-cache-gh-action/.eslintrc.cjs index 9472d27555a3..9f5a866e852f 100644 --- a/dev-packages/clear-cache-gh-action/.eslintrc.cjs +++ b/dev-packages/clear-cache-gh-action/.eslintrc.cjs @@ -1,6 +1,6 @@ module.exports = { // todo: remove regexp plugin from here once we add it to base.js eslint config for the whole project - extends: ['../../.eslintrc.js', 'plugin:regexp/recommended'], + extends: ['../.eslintrc.js', 'plugin:regexp/recommended'], plugins: ['regexp'], parserOptions: { sourceType: 'module', diff --git a/dev-packages/cloudflare-integration-tests/.eslintrc.js b/dev-packages/cloudflare-integration-tests/.eslintrc.js index 5ce331e4074a..2cd3ff680383 100644 --- a/dev-packages/cloudflare-integration-tests/.eslintrc.js +++ b/dev-packages/cloudflare-integration-tests/.eslintrc.js @@ -3,7 +3,7 @@ module.exports = { node: true, }, // todo: remove regexp plugin from here once we add it to base.js eslint config for the whole project - extends: ['../../.eslintrc.js', 'plugin:regexp/recommended'], + extends: ['../.eslintrc.js', 'plugin:regexp/recommended'], plugins: ['regexp'], overrides: [ { diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/google-genai/mocks.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/google-genai/mocks.ts index 22ccba15bc36..fc475234ef5c 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/google-genai/mocks.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/google-genai/mocks.ts @@ -3,7 +3,6 @@ import type { GoogleGenAIChat, GoogleGenAIClient, GoogleGenAIResponse } from '@s export class MockGoogleGenAI implements GoogleGenAIClient { public models: { generateContent: (...args: unknown[]) => Promise; - // eslint-disable-next-line @typescript-eslint/no-explicit-any generateContentStream: (...args: unknown[]) => Promise>; }; public chats: { diff --git a/dev-packages/e2e-tests/.eslintrc.js b/dev-packages/e2e-tests/.eslintrc.js index 9c0a56bca5f4..f285653c3e52 100644 --- a/dev-packages/e2e-tests/.eslintrc.js +++ b/dev-packages/e2e-tests/.eslintrc.js @@ -3,7 +3,7 @@ module.exports = { node: true, }, // todo: remove regexp plugin from here once we add it to base.js eslint config for the whole project - extends: ['../../.eslintrc.js', 'plugin:regexp/recommended'], + extends: ['../.eslintrc.js', 'plugin:regexp/recommended'], plugins: ['regexp'], ignorePatterns: ['test-applications/**', 'tmp/**'], parserOptions: { diff --git a/dev-packages/external-contributor-gh-action/.eslintrc.cjs b/dev-packages/external-contributor-gh-action/.eslintrc.cjs index 9472d27555a3..9f5a866e852f 100644 --- a/dev-packages/external-contributor-gh-action/.eslintrc.cjs +++ b/dev-packages/external-contributor-gh-action/.eslintrc.cjs @@ -1,6 +1,6 @@ module.exports = { // todo: remove regexp plugin from here once we add it to base.js eslint config for the whole project - extends: ['../../.eslintrc.js', 'plugin:regexp/recommended'], + extends: ['../.eslintrc.js', 'plugin:regexp/recommended'], plugins: ['regexp'], parserOptions: { sourceType: 'module', diff --git a/dev-packages/node-core-integration-tests/.eslintrc.js b/dev-packages/node-core-integration-tests/.eslintrc.js index e307575fe52e..ce21050cd142 100644 --- a/dev-packages/node-core-integration-tests/.eslintrc.js +++ b/dev-packages/node-core-integration-tests/.eslintrc.js @@ -3,7 +3,7 @@ module.exports = { node: true, }, // todo: remove regexp plugin from here once we add it to base.js eslint config for the whole project - extends: ['../../.eslintrc.js', 'plugin:regexp/recommended'], + extends: ['../.eslintrc.js', 'plugin:regexp/recommended'], plugins: ['regexp'], overrides: [ { diff --git a/dev-packages/node-integration-tests/.eslintrc.js b/dev-packages/node-integration-tests/.eslintrc.js index e307575fe52e..ce21050cd142 100644 --- a/dev-packages/node-integration-tests/.eslintrc.js +++ b/dev-packages/node-integration-tests/.eslintrc.js @@ -3,7 +3,7 @@ module.exports = { node: true, }, // todo: remove regexp plugin from here once we add it to base.js eslint config for the whole project - extends: ['../../.eslintrc.js', 'plugin:regexp/recommended'], + extends: ['../.eslintrc.js', 'plugin:regexp/recommended'], plugins: ['regexp'], overrides: [ { diff --git a/dev-packages/node-overhead-gh-action/.eslintrc.cjs b/dev-packages/node-overhead-gh-action/.eslintrc.cjs index a1c4f5968e46..3560c39da4eb 100644 --- a/dev-packages/node-overhead-gh-action/.eslintrc.cjs +++ b/dev-packages/node-overhead-gh-action/.eslintrc.cjs @@ -3,7 +3,7 @@ module.exports = { node: true, }, // todo: remove regexp plugin from here once we add it to base.js eslint config for the whole project - extends: ['../../.eslintrc.js', 'plugin:regexp/recommended'], + extends: ['../.eslintrc.js', 'plugin:regexp/recommended'], plugins: ['regexp'], overrides: [ { diff --git a/dev-packages/rollup-utils/.eslintrc.cjs b/dev-packages/rollup-utils/.eslintrc.cjs index f4a3ac1b8cb7..c44899e31665 100644 --- a/dev-packages/rollup-utils/.eslintrc.cjs +++ b/dev-packages/rollup-utils/.eslintrc.cjs @@ -1,6 +1,6 @@ module.exports = { // todo: remove regexp plugin from here once we add it to base.js eslint config for the whole project - extends: ['../../.eslintrc.js', 'plugin:regexp/recommended'], + extends: ['../.eslintrc.js', 'plugin:regexp/recommended'], plugins: ['regexp'], ignorePatterns: ['otelLoaderTemplate.js.tmpl'], sourceType: 'module', diff --git a/dev-packages/size-limit-gh-action/.eslintrc.cjs b/dev-packages/size-limit-gh-action/.eslintrc.cjs index fbc38a65db9c..ad9dd7b90cb4 100644 --- a/dev-packages/size-limit-gh-action/.eslintrc.cjs +++ b/dev-packages/size-limit-gh-action/.eslintrc.cjs @@ -1,6 +1,6 @@ module.exports = { // todo: remove regexp plugin from here once we add it to base.js eslint config for the whole project - extends: ['../../.eslintrc.js', 'plugin:regexp/recommended'], + extends: ['../.eslintrc.js', 'plugin:regexp/recommended'], plugins: ['regexp'], parserOptions: { sourceType: 'module', diff --git a/dev-packages/test-utils/.eslintrc.js b/dev-packages/test-utils/.eslintrc.js index 3be74e6c3f9d..d486b3046d17 100644 --- a/dev-packages/test-utils/.eslintrc.js +++ b/dev-packages/test-utils/.eslintrc.js @@ -3,6 +3,6 @@ module.exports = { node: true, }, // todo: remove regexp plugin from here once we add it to base.js eslint config for the whole project - extends: ['../../.eslintrc.js', 'plugin:regexp/recommended'], + extends: ['../.eslintrc.js', 'plugin:regexp/recommended'], plugins: ['regexp'], }; diff --git a/packages/aws-serverless/src/integration/instrumentation-aws-lambda/types.ts b/packages/aws-serverless/src/integration/instrumentation-aws-lambda/types.ts index 1b7603281ba0..f2503b134a2d 100644 --- a/packages/aws-serverless/src/integration/instrumentation-aws-lambda/types.ts +++ b/packages/aws-serverless/src/integration/instrumentation-aws-lambda/types.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +/* eslint-disable @typescript-eslint/no-explicit-any */ import type { Context as OtelContext, Span } from '@opentelemetry/api'; import type { InstrumentationConfig } from '@opentelemetry/instrumentation'; import type { Context } from 'aws-lambda'; diff --git a/packages/cloudflare/src/durableobject.ts b/packages/cloudflare/src/durableobject.ts index db1da6a5d172..a0c042c6a755 100644 --- a/packages/cloudflare/src/durableobject.ts +++ b/packages/cloudflare/src/durableobject.ts @@ -28,7 +28,8 @@ type MethodWrapperOptions = { }; // eslint-disable-next-line @typescript-eslint/no-explicit-any -type OriginalMethod = (...args: any[]) => any; +type UncheckedMethod = (...args: any[]) => any; +type OriginalMethod = UncheckedMethod; function wrapMethodWithSentry( wrapperOptions: MethodWrapperOptions, @@ -269,8 +270,7 @@ export function instrumentDurableObjectWithSentry< // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any (obj as any)[method] = wrapMethodWithSentry( { options, context, spanName: method, spanOp: 'rpc' }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - value as (...args: any[]) => any, + value as UncheckedMethod, ); } } @@ -332,7 +332,7 @@ function instrumentPrototype(target: T, methodsToInst } // Create a wrapper that gets context/options from the instance at runtime - const wrappedMethod = function (this: any, ...args: any[]): unknown { + const wrappedMethod = function (this: unknown, ...args: unknown[]): unknown { const thisWithSentry = this as { __SENTRY_CONTEXT__: DurableObjectState; __SENTRY_OPTIONS__: CloudflareOptions; @@ -342,7 +342,7 @@ function instrumentPrototype(target: T, methodsToInst if (!instanceOptions) { // Fallback to original method if no Sentry data found - return (originalMethod as (...args: any[]) => any).apply(this, args); + return (originalMethod as UncheckedMethod).apply(this, args); } // Use the existing wrapper but with instance-specific context/options @@ -353,12 +353,12 @@ function instrumentPrototype(target: T, methodsToInst spanName: methodName, spanOp: 'rpc', }, - originalMethod as (...args: any[]) => any, + originalMethod as UncheckedMethod, undefined, true, // noMark = true since we'll mark the prototype method ); - return (wrapper as (...args: any[]) => any).apply(this, args); + return wrapper.apply(this, args); }; markAsInstrumented(wrappedMethod); diff --git a/packages/core/src/integrations/supabase.ts b/packages/core/src/integrations/supabase.ts index 61005fdad805..1b6f24cc3136 100644 --- a/packages/core/src/integrations/supabase.ts +++ b/packages/core/src/integrations/supabase.ts @@ -1,6 +1,7 @@ // Based on Kamil Ogórek's work on: // https://github.com/supabase-community/sentry-integration-js +/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable max-lines */ import { addBreadcrumb } from '../breadcrumbs'; import { DEBUG_BUILD } from '../debug-build'; diff --git a/packages/core/src/types-hoist/breadcrumb.ts b/packages/core/src/types-hoist/breadcrumb.ts index 1c5bef4bc49a..391100a5a377 100644 --- a/packages/core/src/types-hoist/breadcrumb.ts +++ b/packages/core/src/types-hoist/breadcrumb.ts @@ -52,6 +52,7 @@ export interface Breadcrumb { * * @summary Arbitrary data associated with this breadcrumb. */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any data?: { [key: string]: any }; /** @@ -70,6 +71,7 @@ export interface Breadcrumb { /** JSDoc */ export interface BreadcrumbHint { + // eslint-disable-next-line @typescript-eslint/no-explicit-any [key: string]: any; } @@ -90,6 +92,7 @@ export interface XhrBreadcrumbData { } export interface FetchBreadcrumbHint { + // eslint-disable-next-line @typescript-eslint/no-explicit-any input: any[]; data?: unknown; response?: unknown; diff --git a/packages/core/src/types-hoist/context.ts b/packages/core/src/types-hoist/context.ts index a3673f84a968..9e52ff9d6834 100644 --- a/packages/core/src/types-hoist/context.ts +++ b/packages/core/src/types-hoist/context.ts @@ -99,6 +99,7 @@ export interface ResponseContext extends Record { } export interface TraceContext extends Record { + // eslint-disable-next-line @typescript-eslint/no-explicit-any data?: { [key: string]: any }; op?: string; parent_span_id?: string; diff --git a/packages/core/src/types-hoist/error.ts b/packages/core/src/types-hoist/error.ts index d94becdbaf1f..ca92b924b933 100644 --- a/packages/core/src/types-hoist/error.ts +++ b/packages/core/src/types-hoist/error.ts @@ -2,5 +2,7 @@ * Just an Error object with arbitrary attributes attached to it. */ export interface ExtendedError extends Error { + // TODO: fix in v11, convert any to unknown + // eslint-disable-next-line @typescript-eslint/no-explicit-any [key: string]: any; } diff --git a/packages/core/src/types-hoist/event.ts b/packages/core/src/types-hoist/event.ts index a042edad2cda..6b333bd2388e 100644 --- a/packages/core/src/types-hoist/event.ts +++ b/packages/core/src/types-hoist/event.ts @@ -83,6 +83,7 @@ export interface EventHint { syntheticException?: Error | null; originalException?: unknown; attachments?: Attachment[]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any data?: any; integrations?: string[]; } diff --git a/packages/core/src/types-hoist/instrument.ts b/packages/core/src/types-hoist/instrument.ts index b067f6618558..7caab9e3dacf 100644 --- a/packages/core/src/types-hoist/instrument.ts +++ b/packages/core/src/types-hoist/instrument.ts @@ -47,6 +47,7 @@ interface SentryFetchData { } export interface HandlerDataFetch { + // eslint-disable-next-line @typescript-eslint/no-explicit-any args: any[]; fetchData: SentryFetchData; // This data is among other things dumped directly onto the fetch breadcrumb data startTimestamp: number; @@ -74,6 +75,7 @@ export interface HandlerDataDom { export interface HandlerDataConsole { level: ConsoleLevel; + // eslint-disable-next-line @typescript-eslint/no-explicit-any args: any[]; } diff --git a/packages/core/src/types-hoist/integration.ts b/packages/core/src/types-hoist/integration.ts index cc9e4bc580ce..120cb1acc884 100644 --- a/packages/core/src/types-hoist/integration.ts +++ b/packages/core/src/types-hoist/integration.ts @@ -47,4 +47,5 @@ export interface Integration { * An integration in function form. * This is expected to return an integration. */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any export type IntegrationFn = (...rest: any[]) => IntegrationType; diff --git a/packages/core/src/types-hoist/misc.ts b/packages/core/src/types-hoist/misc.ts index af9ed8fc6bd7..8a53f12781e2 100644 --- a/packages/core/src/types-hoist/misc.ts +++ b/packages/core/src/types-hoist/misc.ts @@ -4,6 +4,8 @@ import type { QueryParams } from './request'; * Data extracted from an incoming request to a node server */ export interface ExtractedNodeRequestData { + // TODO: fix in v11, convert any to unknown + // eslint-disable-next-line @typescript-eslint/no-explicit-any [key: string]: any; /** Specific headers from the request */ diff --git a/packages/core/src/types-hoist/polymorphics.ts b/packages/core/src/types-hoist/polymorphics.ts index 88abf7770b2c..74ade60e2098 100644 --- a/packages/core/src/types-hoist/polymorphics.ts +++ b/packages/core/src/types-hoist/polymorphics.ts @@ -50,12 +50,14 @@ type NextjsRequest = NodeRequest & { [key: string]: string; }; query?: { + // eslint-disable-next-line @typescript-eslint/no-explicit-any [key: string]: any; }; }; type ExpressRequest = NodeRequest & { baseUrl?: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any body?: string | { [key: string]: any }; host?: string; hostname?: string; @@ -70,9 +72,11 @@ type ExpressRequest = NodeRequest & { ]; }; query?: { + // eslint-disable-next-line @typescript-eslint/no-explicit-any [key: string]: any; }; user?: { + // eslint-disable-next-line @typescript-eslint/no-explicit-any [key: string]: any; }; _reconstructedRoute?: string; diff --git a/packages/core/src/types-hoist/samplingcontext.ts b/packages/core/src/types-hoist/samplingcontext.ts index b0a52862870c..2ae787c5397d 100644 --- a/packages/core/src/types-hoist/samplingcontext.ts +++ b/packages/core/src/types-hoist/samplingcontext.ts @@ -6,6 +6,8 @@ import type { SpanAttributes } from './span'; * Context data passed by the user when starting a transaction, to be used by the tracesSampler method. */ export interface CustomSamplingContext { + // TODO: fix in v11, convert any to unknown + // eslint-disable-next-line @typescript-eslint/no-explicit-any [key: string]: any; } diff --git a/packages/core/src/types-hoist/stackframe.ts b/packages/core/src/types-hoist/stackframe.ts index 4f99f1ba3595..9afb1d440d43 100644 --- a/packages/core/src/types-hoist/stackframe.ts +++ b/packages/core/src/types-hoist/stackframe.ts @@ -13,7 +13,11 @@ export interface StackFrame { in_app?: boolean; instruction_addr?: string; addr_mode?: string; + // TODO: fix in v11, convert any to unknown + // eslint-disable-next-line @typescript-eslint/no-explicit-any vars?: { [key: string]: any }; debug_id?: string; + // TODO: fix in v11, convert any to unknown + // eslint-disable-next-line @typescript-eslint/no-explicit-any module_metadata?: any; } diff --git a/packages/core/src/types-hoist/user.ts b/packages/core/src/types-hoist/user.ts index 801fb66202b1..ff917ffbd344 100644 --- a/packages/core/src/types-hoist/user.ts +++ b/packages/core/src/types-hoist/user.ts @@ -2,6 +2,8 @@ * An interface describing a user of an application or a handled request. */ export interface User { + // TODO: fix in v11, convert any to unknown + // eslint-disable-next-line @typescript-eslint/no-explicit-any [key: string]: any; id?: string | number; ip_address?: string | null; diff --git a/packages/core/src/utils/aggregate-errors.ts b/packages/core/src/utils/aggregate-errors.ts index 16d100d60c09..d990d9609c4f 100644 --- a/packages/core/src/utils/aggregate-errors.ts +++ b/packages/core/src/utils/aggregate-errors.ts @@ -57,14 +57,14 @@ function aggregateExceptionsFromError( // Recursively call this function in order to walk down a chain of errors if (isInstanceOf(error[key], Error)) { applyExceptionGroupFieldsForParentException(exception, exceptionId); - const newException = exceptionFromErrorImplementation(parser, error[key]); + const newException = exceptionFromErrorImplementation(parser, error[key] as Error); const newExceptionId = newExceptions.length; applyExceptionGroupFieldsForChildException(newException, key, newExceptionId, exceptionId); newExceptions = aggregateExceptionsFromError( exceptionFromErrorImplementation, parser, limit, - error[key], + error[key] as ExtendedError, key, [newException, ...newExceptions], newException, @@ -78,14 +78,14 @@ function aggregateExceptionsFromError( error.errors.forEach((childError, i) => { if (isInstanceOf(childError, Error)) { applyExceptionGroupFieldsForParentException(exception, exceptionId); - const newException = exceptionFromErrorImplementation(parser, childError); + const newException = exceptionFromErrorImplementation(parser, childError as Error); const newExceptionId = newExceptions.length; applyExceptionGroupFieldsForChildException(newException, `errors[${i}]`, newExceptionId, exceptionId); newExceptions = aggregateExceptionsFromError( exceptionFromErrorImplementation, parser, limit, - childError, + childError as ExtendedError, key, [newException, ...newExceptions], newException, diff --git a/packages/core/src/utils/is.ts b/packages/core/src/utils/is.ts index 9abfab910099..4c8589934800 100644 --- a/packages/core/src/utils/is.ts +++ b/packages/core/src/utils/is.ts @@ -180,7 +180,9 @@ export function isSyntheticEvent(wat: unknown): boolean { * @param base A constructor to be used in a check. * @returns A boolean representing the result. */ -export function isInstanceOf(wat: any, base: any): boolean { +// TODO: fix in v11, convert any to unknown +// export function isInstanceOf(wat: unknown, base: { new (...args: any[]): T }): wat is T { +export function isInstanceOf(wat: any, base: any): wat is T { try { return wat instanceof base; } catch { diff --git a/packages/core/test/lib/utils/is.test.ts b/packages/core/test/lib/utils/is.test.ts index 092ffae2e3c3..726053e6f53d 100644 --- a/packages/core/test/lib/utils/is.test.ts +++ b/packages/core/test/lib/utils/is.test.ts @@ -107,15 +107,24 @@ describe('isInstanceOf()', () => { expect(isInstanceOf(new Date(), Date)).toEqual(true); // @ts-expect-error Foo implicity has any type, doesn't have constructor expect(isInstanceOf(new Foo(), Foo)).toEqual(true); - + // @ts-expect-error Should only allow constructors expect(isInstanceOf(new Error('wat'), Foo)).toEqual(false); expect(isInstanceOf(new Date('wat'), Error)).toEqual(false); + + // verify type inference + const d: unknown = new Date(); + const e: Date = isInstanceOf(d, Date) ? d : new Date(); + expect(e).toEqual(d); }); test('should not break with incorrect input', () => { + // @ts-expect-error Should only allow constructors expect(isInstanceOf(new Error('wat'), 1)).toEqual(false); + // @ts-expect-error Should only allow constructors expect(isInstanceOf(new Error('wat'), 'wat')).toEqual(false); + // @ts-expect-error Should only allow constructors expect(isInstanceOf(new Error('wat'), null)).toEqual(false); + // @ts-expect-error Should only allow constructors expect(isInstanceOf(new Error('wat'), undefined)).toEqual(false); }); }); diff --git a/packages/feedback/src/modal/integration.tsx b/packages/feedback/src/modal/integration.tsx index 0e3f8a637149..a921a32ad7be 100644 --- a/packages/feedback/src/modal/integration.tsx +++ b/packages/feedback/src/modal/integration.tsx @@ -69,8 +69,8 @@ export const feedbackModalIntegration = ((): FeedbackModalIntegration => { screenshotInput={screenshotInput} showName={options.showName || options.isNameRequired} showEmail={options.showEmail || options.isEmailRequired} - defaultName={(userKey && user?.[userKey.name]) || ''} - defaultEmail={(userKey && user?.[userKey.email]) || ''} + defaultName={String((userKey && user?.[userKey.name]) || '')} + defaultEmail={String((userKey && user?.[userKey.email]) || '')} onFormClose={() => { renderContent(false); options.onFormClose?.(); diff --git a/packages/nextjs/src/config/types.ts b/packages/nextjs/src/config/types.ts index 325e3f1d3187..085e5c874184 100644 --- a/packages/nextjs/src/config/types.ts +++ b/packages/nextjs/src/config/types.ts @@ -11,8 +11,8 @@ type NextRewrite = { }; interface WebpackPluginInstance { - [index: string]: any; - apply: (compiler: any) => void; + [index: string]: unknown; + apply: (compiler: unknown) => void; } export type NextConfigObject = { diff --git a/packages/node/src/integrations/tracing/fastify/types.ts b/packages/node/src/integrations/tracing/fastify/types.ts index e15c1c85324b..1bc426e58aad 100644 --- a/packages/node/src/integrations/tracing/fastify/types.ts +++ b/packages/node/src/integrations/tracing/fastify/types.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ export type HandlerOriginal = | ((request: FastifyRequest, reply: FastifyReply, done: HookHandlerDoneFunction) => Promise) | ((request: FastifyRequest, reply: FastifyReply, done: HookHandlerDoneFunction) => void); diff --git a/packages/node/src/integrations/tracing/fastify/v3/instrumentation.ts b/packages/node/src/integrations/tracing/fastify/v3/instrumentation.ts index 7b8035927a11..9921ba536ba4 100644 --- a/packages/node/src/integrations/tracing/fastify/v3/instrumentation.ts +++ b/packages/node/src/integrations/tracing/fastify/v3/instrumentation.ts @@ -1,4 +1,5 @@ // Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/blob/407f61591ba69a39a6908264379d4d98a48dbec4/plugins/node/opentelemetry-instrumentation-fastify/src/instrumentation.ts +/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-this-alias */ /* eslint-disable jsdoc/require-jsdoc */ /* eslint-disable @typescript-eslint/explicit-function-return-type */ diff --git a/packages/node/src/integrations/tracing/fastify/v3/types.ts b/packages/node/src/integrations/tracing/fastify/v3/types.ts index b4c30a3dd36f..4d41a729f9d0 100644 --- a/packages/node/src/integrations/tracing/fastify/v3/types.ts +++ b/packages/node/src/integrations/tracing/fastify/v3/types.ts @@ -1,4 +1,5 @@ // Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/blob/407f61591ba69a39a6908264379d4d98a48dbec4/plugins/node/opentelemetry-instrumentation-fastify/src/types.ts +/* eslint-disable @typescript-eslint/no-explicit-any */ /* * Copyright The OpenTelemetry Authors * diff --git a/packages/node/src/integrations/tracing/fastify/v3/utils.ts b/packages/node/src/integrations/tracing/fastify/v3/utils.ts index 41f4a706b038..26f0014a67e1 100644 --- a/packages/node/src/integrations/tracing/fastify/v3/utils.ts +++ b/packages/node/src/integrations/tracing/fastify/v3/utils.ts @@ -3,6 +3,7 @@ /* eslint-disable @typescript-eslint/no-dynamic-delete */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/explicit-function-return-type */ +/* eslint-disable @typescript-eslint/no-explicit-any */ /* * Copyright The OpenTelemetry Authors * diff --git a/packages/node/src/integrations/tracing/firebase/otel/patches/firestore.ts b/packages/node/src/integrations/tracing/firebase/otel/patches/firestore.ts index 43cf61917e21..d23fd091ffac 100644 --- a/packages/node/src/integrations/tracing/firebase/otel/patches/firestore.ts +++ b/packages/node/src/integrations/tracing/firebase/otel/patches/firestore.ts @@ -38,7 +38,9 @@ import type { // Inline minimal types used from `shimmer` to avoid importing shimmer's types directly. // We only need the shape for `wrap` and `unwrap` used in this file. +// eslint-disable-next-line @typescript-eslint/no-explicit-any type ShimmerWrap = (target: any, name: string, wrapper: (...args: any[]) => any) => void; +// eslint-disable-next-line @typescript-eslint/no-explicit-any type ShimmerUnwrap = (target: any, name: string) => void; /** diff --git a/packages/node/src/integrations/tracing/firebase/otel/types.ts b/packages/node/src/integrations/tracing/firebase/otel/types.ts index ead830fa2c1a..4b94cbefe02c 100644 --- a/packages/node/src/integrations/tracing/firebase/otel/types.ts +++ b/packages/node/src/integrations/tracing/firebase/otel/types.ts @@ -1,5 +1,6 @@ import type { Span } from '@opentelemetry/api'; import type { InstrumentationConfig } from '@opentelemetry/instrumentation'; +/* eslint-disable @typescript-eslint/no-explicit-any */ // Inlined types from 'firebase/app' export interface FirebaseOptions { diff --git a/packages/node/src/integrations/tracing/postgresjs.ts b/packages/node/src/integrations/tracing/postgresjs.ts index 55ee90444b47..ddb588b90585 100644 --- a/packages/node/src/integrations/tracing/postgresjs.ts +++ b/packages/node/src/integrations/tracing/postgresjs.ts @@ -1,5 +1,6 @@ /* eslint-disable max-lines */ // Instrumentation for https://github.com/porsager/postgres + import { context, trace } from '@opentelemetry/api'; import type { InstrumentationConfig } from '@opentelemetry/instrumentation'; import { diff --git a/packages/remix/src/utils/types.ts b/packages/remix/src/utils/types.ts index 9634678a05ce..46b44ce5afa9 100644 --- a/packages/remix/src/utils/types.ts +++ b/packages/remix/src/utils/types.ts @@ -1,4 +1,5 @@ -export type SentryMetaArgs any> = Parameters[0] & { +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type SentryMetaArgs any> = Parameters[0] & { data: { sentryTrace: string; sentryBaggage: string; diff --git a/packages/remix/src/vendor/instrumentation.ts b/packages/remix/src/vendor/instrumentation.ts index e42f6709c0c4..9da38fbd0e7a 100644 --- a/packages/remix/src/vendor/instrumentation.ts +++ b/packages/remix/src/vendor/instrumentation.ts @@ -1,5 +1,8 @@ /* eslint-disable deprecation/deprecation */ /* eslint-disable jsdoc/require-jsdoc */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable import/no-named-as-default-member */ +/* eslint-disable import/no-duplicates */ // Vendored and modified from: // https://github.com/justindsmith/opentelemetry-instrumentations-js/blob/3b1e8c3e566e5cc3389e9c28cafce6a5ebb39600/packages/instrumentation-remix/src/instrumentation.ts diff --git a/packages/replay-internal/rollup.npm.config.mjs b/packages/replay-internal/rollup.npm.config.mjs index 3d6e8fcf4dff..f45b4f21b72b 100644 --- a/packages/replay-internal/rollup.npm.config.mjs +++ b/packages/replay-internal/rollup.npm.config.mjs @@ -1,3 +1,4 @@ +/* eslint-disable import/no-named-as-default */ import nodeResolve from '@rollup/plugin-node-resolve'; import { makeBaseNPMConfig, makeNPMConfigVariants } from '@sentry-internal/rollup-utils'; diff --git a/packages/replay-internal/src/types/rrweb.ts b/packages/replay-internal/src/types/rrweb.ts index 33f5e1b3bf7f..0d9a2d6065d9 100644 --- a/packages/replay-internal/src/types/rrweb.ts +++ b/packages/replay-internal/src/types/rrweb.ts @@ -69,7 +69,9 @@ export interface CanvasManagerOptions { type: string; quality: number; }>; + // eslint-disable-next-line @typescript-eslint/no-explicit-any mutationCb: (p: any) => void; win: typeof globalThis & Window; + // eslint-disable-next-line @typescript-eslint/no-explicit-any mirror: any; } diff --git a/packages/vercel-edge/src/vendored/async-local-storage-context-manager.ts b/packages/vercel-edge/src/vendored/async-local-storage-context-manager.ts index 257c6c27f041..f60a5aa265b2 100644 --- a/packages/vercel-edge/src/vendored/async-local-storage-context-manager.ts +++ b/packages/vercel-edge/src/vendored/async-local-storage-context-manager.ts @@ -24,6 +24,7 @@ /* eslint-disable @typescript-eslint/explicit-member-accessibility */ /* eslint-disable jsdoc/require-jsdoc */ +/* eslint-disable @typescript-eslint/no-explicit-any */ import type { Context } from '@opentelemetry/api'; import { ROOT_CONTEXT } from '@opentelemetry/api'; @@ -44,7 +45,7 @@ export class AsyncLocalStorageContextManager extends AbstractAsyncHooksContextMa constructor() { super(); - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access const MaybeGlobalAsyncLocalStorageConstructor = (GLOBAL_OBJ as any).AsyncLocalStorage; if (!MaybeGlobalAsyncLocalStorageConstructor) { diff --git a/packages/vercel-edge/test/wintercg-fetch.test.ts b/packages/vercel-edge/test/wintercg-fetch.test.ts index a413cc7e93da..d430b46df831 100644 --- a/packages/vercel-edge/test/wintercg-fetch.test.ts +++ b/packages/vercel-edge/test/wintercg-fetch.test.ts @@ -1,6 +1,5 @@ import type { HandlerDataFetch, Integration } from '@sentry/core'; import * as sentryCore from '@sentry/core'; -import * as sentryUtils from '@sentry/core'; import { createStackParser } from '@sentry/core'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { VercelEdgeClient } from '../src/index'; @@ -12,7 +11,7 @@ class FakeClient extends VercelEdgeClient { } } -const addFetchInstrumentationHandlerSpy = vi.spyOn(sentryUtils, 'addFetchInstrumentationHandler'); +const addFetchInstrumentationHandlerSpy = vi.spyOn(sentryCore, 'addFetchInstrumentationHandler'); const instrumentFetchRequestSpy = vi.spyOn(sentryCore, 'instrumentFetchRequest'); const addBreadcrumbSpy = vi.spyOn(sentryCore, 'addBreadcrumb'); From 0d8547cb0fb5cb5c49bdea1b9c9f6f8c0c69c229 Mon Sep 17 00:00:00 2001 From: Ogi Date: Fri, 19 Dec 2025 11:05:25 +0100 Subject: [PATCH 6/7] fix(vercelai): Fix input token count (#18574) Makes sure that `gen_ai.input_tokens` value contains `gen_ai.input_tokens.cached` --- packages/core/src/tracing/vercel-ai/index.ts | 9 +++++ .../tracing/vercel-ai-cached-tokens.test.ts | 39 +++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 packages/core/test/lib/tracing/vercel-ai-cached-tokens.test.ts diff --git a/packages/core/src/tracing/vercel-ai/index.ts b/packages/core/src/tracing/vercel-ai/index.ts index 93be1ca33423..e64b4b1a9cbf 100644 --- a/packages/core/src/tracing/vercel-ai/index.ts +++ b/packages/core/src/tracing/vercel-ai/index.ts @@ -119,6 +119,15 @@ function processEndedVercelAiSpan(span: SpanJSON): void { renameAttributeKey(attributes, AI_USAGE_PROMPT_TOKENS_ATTRIBUTE, GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE); renameAttributeKey(attributes, AI_USAGE_CACHED_INPUT_TOKENS_ATTRIBUTE, GEN_AI_USAGE_INPUT_TOKENS_CACHED_ATTRIBUTE); + // Input tokens is the sum of prompt tokens and cached input tokens + if ( + typeof attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE] === 'number' && + typeof attributes[GEN_AI_USAGE_INPUT_TOKENS_CACHED_ATTRIBUTE] === 'number' + ) { + attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE] = + attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE] + attributes[GEN_AI_USAGE_INPUT_TOKENS_CACHED_ATTRIBUTE]; + } + if ( typeof attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE] === 'number' && typeof attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE] === 'number' diff --git a/packages/core/test/lib/tracing/vercel-ai-cached-tokens.test.ts b/packages/core/test/lib/tracing/vercel-ai-cached-tokens.test.ts new file mode 100644 index 000000000000..7e85121b9e92 --- /dev/null +++ b/packages/core/test/lib/tracing/vercel-ai-cached-tokens.test.ts @@ -0,0 +1,39 @@ +import { describe, expect, it } from 'vitest'; +import { addVercelAiProcessors } from '../../../src/tracing/vercel-ai'; +import type { SpanJSON } from '../../../src/types-hoist/span'; +import { getDefaultTestClientOptions, TestClient } from '../../mocks/client'; + +describe('vercel-ai cached tokens', () => { + it('should add cached input tokens to total input tokens', () => { + const options = getDefaultTestClientOptions({ tracesSampleRate: 1.0 }); + const client = new TestClient(options); + client.init(); + addVercelAiProcessors(client); + + const mockSpan: SpanJSON = { + description: 'test', + span_id: 'test-span-id', + trace_id: 'test-trace-id', + start_timestamp: 1000, + timestamp: 2000, + origin: 'auto.vercelai.otel', + data: { + 'ai.usage.promptTokens': 100, + 'ai.usage.cachedInputTokens': 50, + }, + }; + + const event = { + type: 'transaction' as const, + spans: [mockSpan], + }; + + const eventProcessor = client['_eventProcessors'].find(processor => processor.id === 'VercelAiEventProcessor'); + expect(eventProcessor).toBeDefined(); + + const processedEvent = eventProcessor!(event, {}); + + expect(processedEvent?.spans?.[0]?.data?.['gen_ai.usage.input_tokens']).toBe(150); + expect(processedEvent?.spans?.[0]?.data?.['gen_ai.usage.input_tokens.cached']).toBe(50); + }); +}); From a981a3dcb12d752fbaea2bc21a23e9cf04a31c31 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 19 Dec 2025 11:07:37 +0100 Subject: [PATCH 7/7] meta(changelog): Update changelog for `10.32.1` --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6cad391a7f7..bf5c81b9d377 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,20 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +## 10.32.1 + +- fix(cloudflare): Add hono transaction name when error is thrown ([#18529](https://github.com/getsentry/sentry-javascript/pull/18529)) +- fix(ember): Make `implementation` field optional (`hash` routes) ([#18564](https://github.com/getsentry/sentry-javascript/pull/18564)) +- fix(vercelai): Fix input token count ([#18574](https://github.com/getsentry/sentry-javascript/pull/18574)) + +
+ Internal Changes + +- chore(lint): prefer 'unknown' to 'any', fix lint warnings +- chore(test): Remove `cloudflare-astro` e2e test ([#18567](https://github.com/getsentry/sentry-javascript/pull/18567)) + +
+ ## 10.32.0 ### Important Changes