From 09aec615357899e8e5254309304638aa3ee7e5f8 Mon Sep 17 00:00:00 2001 From: Sigrid Huemer <32902192+s1gr1d@users.noreply.github.com> Date: Mon, 23 Jun 2025 16:53:36 +0200 Subject: [PATCH 01/22] feat(core): Add support for `x-forwarded-host` and `x-forwarded-proto` headers (#16687) Adds support for [x-forwarded-host](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Host) (host forwarding) and [x-forwarded-proto](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Proto) (protocol forwarding). This is useful when using proxies. Closes https://github.com/getsentry/sentry-javascript/issues/16671 Support for this was also added [in this PR for Next.js](https://github.com/getsentry/sentry-javascript/pull/16500) --- packages/core/src/utils/request.ts | 11 +- packages/core/test/lib/utils/request.test.ts | 209 +++++++++++++++++++ 2 files changed, 218 insertions(+), 2 deletions(-) diff --git a/packages/core/src/utils/request.ts b/packages/core/src/utils/request.ts index 6c62f422207a..04cd1006ba28 100644 --- a/packages/core/src/utils/request.ts +++ b/packages/core/src/utils/request.ts @@ -74,8 +74,15 @@ export function httpRequestToRequestData(request: { }; }): RequestEventData { const headers = request.headers || {}; - const host = typeof headers.host === 'string' ? headers.host : undefined; - const protocol = request.protocol || (request.socket?.encrypted ? 'https' : 'http'); + + // Check for x-forwarded-host first, then fall back to host header + const forwardedHost = typeof headers['x-forwarded-host'] === 'string' ? headers['x-forwarded-host'] : undefined; + const host = forwardedHost || (typeof headers.host === 'string' ? headers.host : undefined); + + // Check for x-forwarded-proto first, then fall back to existing protocol detection + const forwardedProto = typeof headers['x-forwarded-proto'] === 'string' ? headers['x-forwarded-proto'] : undefined; + const protocol = forwardedProto || request.protocol || (request.socket?.encrypted ? 'https' : 'http'); + const url = request.url || ''; const absoluteUrl = getAbsoluteUrl({ diff --git a/packages/core/test/lib/utils/request.test.ts b/packages/core/test/lib/utils/request.test.ts index 44682e6e9b0f..fe90578d5392 100644 --- a/packages/core/test/lib/utils/request.test.ts +++ b/packages/core/test/lib/utils/request.test.ts @@ -198,6 +198,215 @@ describe('request utils', () => { data: { xx: 'a', yy: 'z' }, }); }); + + describe('x-forwarded headers support', () => { + it('should prioritize x-forwarded-proto header over explicit protocol parameter', () => { + const actual = httpRequestToRequestData({ + url: '/test', + headers: { + host: 'example.com', + 'x-forwarded-proto': 'https', + }, + protocol: 'http', + }); + + expect(actual).toEqual({ + url: 'https://example.com/test', + headers: { + host: 'example.com', + 'x-forwarded-proto': 'https', + }, + }); + }); + + it('should prioritize x-forwarded-proto header even when downgrading from https to http', () => { + const actual = httpRequestToRequestData({ + url: '/test', + headers: { + host: 'example.com', + 'x-forwarded-proto': 'http', + }, + protocol: 'https', + }); + + expect(actual).toEqual({ + url: 'http://example.com/test', + headers: { + host: 'example.com', + 'x-forwarded-proto': 'http', + }, + }); + }); + + it('should prioritize x-forwarded-proto header over socket encryption detection', () => { + const actual = httpRequestToRequestData({ + url: '/test', + headers: { + host: 'example.com', + 'x-forwarded-proto': 'https', + }, + socket: { + encrypted: false, + }, + }); + + expect(actual).toEqual({ + url: 'https://example.com/test', + headers: { + host: 'example.com', + 'x-forwarded-proto': 'https', + }, + }); + }); + + it('should prioritize x-forwarded-host header over standard host header', () => { + const actual = httpRequestToRequestData({ + url: '/test', + headers: { + host: 'localhost:3000', + 'x-forwarded-host': 'example.com', + 'x-forwarded-proto': 'https', + }, + }); + + expect(actual).toEqual({ + url: 'https://example.com/test', + headers: { + host: 'localhost:3000', + 'x-forwarded-host': 'example.com', + 'x-forwarded-proto': 'https', + }, + }); + }); + + it('should construct URL correctly when both x-forwarded-proto and x-forwarded-host are present', () => { + const actual = httpRequestToRequestData({ + method: 'POST', + url: '/api/test?param=value', + headers: { + host: 'localhost:3000', + 'x-forwarded-host': 'api.example.com', + 'x-forwarded-proto': 'https', + 'content-type': 'application/json', + }, + protocol: 'http', + }); + + expect(actual).toEqual({ + method: 'POST', + url: 'https://api.example.com/api/test?param=value', + query_string: 'param=value', + headers: { + host: 'localhost:3000', + 'x-forwarded-host': 'api.example.com', + 'x-forwarded-proto': 'https', + 'content-type': 'application/json', + }, + }); + }); + + it('should fall back to standard headers when x-forwarded headers are not present', () => { + const actual = httpRequestToRequestData({ + url: '/test', + headers: { + host: 'example.com', + }, + protocol: 'https', + }); + + expect(actual).toEqual({ + url: 'https://example.com/test', + headers: { + host: 'example.com', + }, + }); + }); + + it('should ignore x-forwarded headers when they contain non-string values', () => { + const actual = httpRequestToRequestData({ + url: '/test', + headers: { + host: 'example.com', + 'x-forwarded-host': ['forwarded.example.com'] as any, + 'x-forwarded-proto': ['https'] as any, + }, + protocol: 'http', + }); + + expect(actual).toEqual({ + url: 'http://example.com/test', + headers: { + host: 'example.com', + }, + }); + }); + + it('should correctly transform localhost request to public URL using x-forwarded headers', () => { + const actual = httpRequestToRequestData({ + method: 'GET', + url: '/', + headers: { + host: 'localhost:3000', + 'x-forwarded-proto': 'https', + 'x-forwarded-host': 'example.com', + }, + }); + + expect(actual).toEqual({ + method: 'GET', + url: 'https://example.com/', + headers: { + host: 'localhost:3000', + 'x-forwarded-proto': 'https', + 'x-forwarded-host': 'example.com', + }, + }); + }); + + it('should respect x-forwarded-proto even when it downgrades from encrypted socket', () => { + const actual = httpRequestToRequestData({ + url: '/test', + headers: { + host: 'example.com', + 'x-forwarded-proto': 'http', + }, + socket: { + encrypted: true, + }, + }); + + expect(actual).toEqual({ + url: 'http://example.com/test', + headers: { + host: 'example.com', + 'x-forwarded-proto': 'http', + }, + }); + }); + + it('should preserve query parameters when constructing URL with x-forwarded headers', () => { + const actual = httpRequestToRequestData({ + method: 'GET', + url: '/search?q=test&category=api', + headers: { + host: 'localhost:8080', + 'x-forwarded-host': 'search.example.com', + 'x-forwarded-proto': 'https', + }, + }); + + expect(actual).toEqual({ + method: 'GET', + url: 'https://search.example.com/search?q=test&category=api', + query_string: 'q=test&category=api', + headers: { + host: 'localhost:8080', + 'x-forwarded-host': 'search.example.com', + 'x-forwarded-proto': 'https', + }, + }); + }); + }); }); describe('extractQueryParamsFromUrl', () => { From 5fba3313d1368d84c2f8dfc6545163f726b2cdc8 Mon Sep 17 00:00:00 2001 From: Andrei <168741329+andreiborza@users.noreply.github.com> Date: Mon, 23 Jun 2025 17:12:27 +0200 Subject: [PATCH 02/22] chore: Add @sentry/opentelemetry resolution back to remix integration tests (#16692) While the remix package does not directly depend on @sentry/opentelemetry, we still need this resolution override for the integration tests. It was removed in: https://github.com/getsentry/sentry-javascript/pull/16677 --- packages/remix/test/integration/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/remix/test/integration/package.json b/packages/remix/test/integration/package.json index 4e15e7de7398..7f4172b0b42f 100644 --- a/packages/remix/test/integration/package.json +++ b/packages/remix/test/integration/package.json @@ -27,6 +27,7 @@ "@sentry/browser": "file:../../../browser", "@sentry/core": "file:../../../core", "@sentry/node": "file:../../../node", + "@sentry/opentelemetry": "file:../../../opentelemetry", "@sentry/react": "file:../../../react", "@sentry-internal/browser-utils": "file:../../../browser-utils", "@sentry-internal/replay": "file:../../../replay-internal", From 68586025228ab1dff4501c4ae51265c6abdbc9e2 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Mon, 23 Jun 2025 19:36:43 +0200 Subject: [PATCH 03/22] feat(cloudflare): Add `instrumentWorkflowWithSentry` to instrument workflows (#16672) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Closes #16458 It was tricky to instrument Cloudflare workflows! The code for a workflow might look simple but there is a lot of clever shenanigans going on under the hood in the runtime to allow suspension of workflows. Workflows can be hibernated at any time and all state/context inside the your workflow class and elsewhere is lost. Ideally we want all of our step runs to have the same `trace_id` so all steps in a workflow run are linked together and all steps should have the same sampling decision. To work around the state limitations, we use the workflow `instanceId` as both the Sentry `trace_id` and the last 4 characters are used to generate the `sample_rand` used in the sampling decision. Cloudflare uses uuid's by default for `instanceId` but users do have the option of passing their own IDs. If users are supplying their own `instanceId`'s, they need to be both random and a 32 character uuid (with or without hyphens) or the Sentry instrumentation will throw an error. Points worthy of note: - We use a `enableDedupe` config option (docs hidden) which removes the `dedupeIntegration` for workflows. We want to get duplicate errors for step retries - We have to wrap the Cloudflare `WorkflowStep` object in another class. The Cloudflare step object is native so it's properties can't be overridden or proxied - Our wrapping does end up in all the stack traces but should be automatically hidden because they will be evaluated as `in_app: false` - We don't wrap `step.sleep`, `step.sleepUntil` or `step.waitForEvent` because code doesn't run after the Cloudflare native function returns ☹️ - Calling `setPropagationContext` directly on the isolation context didn't work. It needed another `withScope` inside for `setPropagationContext` to work. @mydea is that expected? - This PR doesn't yet capture: - The payload supplied when the workflow run was started - The return results from the workflow steps Here is an example trace showing the final step failing (throwing) 6 times before completing successfully. The exponential retry backoff is clearly visible. image --------- Co-authored-by: Abhijeet Prasad --- packages/cloudflare/package.json | 2 +- packages/cloudflare/src/client.ts | 10 +- packages/cloudflare/src/index.ts | 2 + packages/cloudflare/src/pages-plugin.ts | 4 +- packages/cloudflare/src/sdk.ts | 4 +- packages/cloudflare/src/workflows.ts | 184 ++++++++++ packages/cloudflare/test/workflow.test.ts | 350 ++++++++++++++++++++ packages/sveltekit/src/worker/cloudflare.ts | 2 +- yarn.lock | 8 +- 9 files changed, 556 insertions(+), 10 deletions(-) create mode 100644 packages/cloudflare/src/workflows.ts create mode 100644 packages/cloudflare/test/workflow.test.ts diff --git a/packages/cloudflare/package.json b/packages/cloudflare/package.json index 58ec78d1ca36..e23ffd9383b4 100644 --- a/packages/cloudflare/package.json +++ b/packages/cloudflare/package.json @@ -60,7 +60,7 @@ } }, "devDependencies": { - "@cloudflare/workers-types": "4.20240725.0", + "@cloudflare/workers-types": "4.20250620.0", "@types/node": "^18.19.1", "wrangler": "^3.67.1" }, diff --git a/packages/cloudflare/src/client.ts b/packages/cloudflare/src/client.ts index 224865e3731e..9b4f18086658 100644 --- a/packages/cloudflare/src/client.ts +++ b/packages/cloudflare/src/client.ts @@ -29,8 +29,14 @@ export class CloudflareClient extends ServerRuntimeClient { const options = typeof handlerOrOptions === 'function' ? handlerOrOptions(context) : handlerOrOptions; - return wrapRequestHandler({ options, request: context.request, context }, () => context.next()); + return wrapRequestHandler({ options, request: context.request, context: { ...context, props: {} } }, () => + context.next(), + ); }; } diff --git a/packages/cloudflare/src/sdk.ts b/packages/cloudflare/src/sdk.ts index dee32b856eb0..90ef3c0bedf9 100644 --- a/packages/cloudflare/src/sdk.ts +++ b/packages/cloudflare/src/sdk.ts @@ -20,7 +20,9 @@ import { defaultStackParser } from './vendor/stacktrace'; export function getDefaultIntegrations(options: CloudflareOptions): Integration[] { const sendDefaultPii = options.sendDefaultPii ?? false; return [ - dedupeIntegration(), + // The Dedupe integration should not be used in workflows because we want to + // capture all step failures, even if they are the same error. + ...(options.enableDedupe === false ? [] : [dedupeIntegration()]), // TODO(v10): Replace with `eventFiltersIntegration` once we remove the deprecated `inboundFiltersIntegration` // eslint-disable-next-line deprecation/deprecation inboundFiltersIntegration(), diff --git a/packages/cloudflare/src/workflows.ts b/packages/cloudflare/src/workflows.ts new file mode 100644 index 000000000000..022f0040893a --- /dev/null +++ b/packages/cloudflare/src/workflows.ts @@ -0,0 +1,184 @@ +import type { PropagationContext } from '@sentry/core'; +import { + captureException, + flush, + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + startSpan, + withIsolationScope, + withScope, +} from '@sentry/core'; +import type { + WorkflowEntrypoint, + WorkflowEvent, + WorkflowSleepDuration, + WorkflowStep, + WorkflowStepConfig, + WorkflowStepEvent, + WorkflowTimeoutDuration, +} from 'cloudflare:workers'; +import { setAsyncLocalStorageAsyncContextStrategy } from './async'; +import type { CloudflareOptions } from './client'; +import { addCloudResourceContext } from './scope-utils'; +import { init } from './sdk'; + +const UUID_REGEX = /^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$/i; + +function propagationContextFromInstanceId(instanceId: string): PropagationContext { + // Validate and normalize traceId - should be a valid UUID with or without hyphens + if (!UUID_REGEX.test(instanceId)) { + throw new Error("Invalid 'instanceId' for workflow: Sentry requires random UUIDs for instanceId."); + } + + // Remove hyphens to get UUID without hyphens + const traceId = instanceId.replace(/-/g, ''); + + // Derive sampleRand from last 4 characters of the random UUID + // + // We cannot store any state between workflow steps, so we derive the + // sampleRand from the traceId itself. This ensures that the sampling is + // consistent across all steps in the same workflow instance. + const sampleRand = parseInt(traceId.slice(-4), 16) / 0xffff; + + return { + traceId, + sampleRand, + }; +} + +async function workflowStepWithSentry( + instanceId: string, + options: CloudflareOptions, + callback: () => V, +): Promise { + setAsyncLocalStorageAsyncContextStrategy(); + + return withIsolationScope(async isolationScope => { + const client = init({ ...options, enableDedupe: false }); + isolationScope.setClient(client); + + addCloudResourceContext(isolationScope); + + return withScope(async scope => { + const propagationContext = propagationContextFromInstanceId(instanceId); + scope.setPropagationContext(propagationContext); + + // eslint-disable-next-line no-return-await + return await callback(); + }); + }); +} + +class WrappedWorkflowStep implements WorkflowStep { + public constructor( + private _instanceId: string, + private _ctx: ExecutionContext, + private _options: CloudflareOptions, + private _step: WorkflowStep, + ) {} + + public async do>(name: string, callback: () => Promise): Promise; + public async do>( + name: string, + config: WorkflowStepConfig, + callback: () => Promise, + ): Promise; + public async do>( + name: string, + configOrCallback: WorkflowStepConfig | (() => Promise), + maybeCallback?: () => Promise, + ): Promise { + const userCallback = (maybeCallback || configOrCallback) as () => Promise; + const config = typeof configOrCallback === 'function' ? undefined : configOrCallback; + + const instrumentedCallback: () => Promise = async () => { + return workflowStepWithSentry(this._instanceId, this._options, async () => { + return startSpan( + { + op: 'function.step.do', + name, + attributes: { + 'cloudflare.workflow.timeout': config?.timeout, + 'cloudflare.workflow.retries.backoff': config?.retries?.backoff, + 'cloudflare.workflow.retries.delay': config?.retries?.delay, + 'cloudflare.workflow.retries.limit': config?.retries?.limit, + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.faas.cloudflare.workflow', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'task', + }, + }, + async span => { + try { + const result = await userCallback(); + span.setStatus({ code: 1 }); + return result; + } catch (error) { + captureException(error, { mechanism: { handled: true, type: 'cloudflare' } }); + throw error; + } finally { + this._ctx.waitUntil(flush(2000)); + } + }, + ); + }); + }; + + return config ? this._step.do(name, config, instrumentedCallback) : this._step.do(name, instrumentedCallback); + } + + public async sleep(name: string, duration: WorkflowSleepDuration): Promise { + return this._step.sleep(name, duration); + } + + public async sleepUntil(name: string, timestamp: Date | number): Promise { + return this._step.sleepUntil(name, timestamp); + } + + public async waitForEvent>( + name: string, + options: { type: string; timeout?: WorkflowTimeoutDuration | number }, + ): Promise> { + return this._step.waitForEvent(name, options); + } +} + +/** + * Instruments a Cloudflare Workflow class with Sentry. + * + * @example + * ```typescript + * const InstrumentedWorkflow = instrumentWorkflowWithSentry( + * (env) => ({ dsn: env.SENTRY_DSN }), + * MyWorkflowClass + * ); + * + * export default InstrumentedWorkflow; + * ``` + * + * @param optionsCallback - Function that returns Sentry options to initialize Sentry + * @param WorkflowClass - The workflow class to instrument + * @returns Instrumented workflow class with the same interface + */ +export function instrumentWorkflowWithSentry< + E, // Environment type + P, // Payload type + T extends WorkflowEntrypoint, // WorkflowEntrypoint type + C extends new (ctx: ExecutionContext, env: E) => T, // Constructor type of the WorkflowEntrypoint class +>(optionsCallback: (env: E) => CloudflareOptions, WorkFlowClass: C): C { + return new Proxy(WorkFlowClass, { + construct(target: C, args: [ctx: ExecutionContext, env: E], newTarget) { + const [ctx, env] = args; + const options = optionsCallback(env); + const instance = Reflect.construct(target, args, newTarget) as T; + return new Proxy(instance, { + get(obj, prop, receiver) { + if (prop === 'run') { + return async function (event: WorkflowEvent

, step: WorkflowStep): Promise { + return obj.run.call(obj, event, new WrappedWorkflowStep(event.instanceId, ctx, options, step)); + }; + } + return Reflect.get(obj, prop, receiver); + }, + }); + }, + }) as C; +} diff --git a/packages/cloudflare/test/workflow.test.ts b/packages/cloudflare/test/workflow.test.ts new file mode 100644 index 000000000000..03eee5191eb2 --- /dev/null +++ b/packages/cloudflare/test/workflow.test.ts @@ -0,0 +1,350 @@ +/* eslint-disable @typescript-eslint/unbound-method */ +import type { WorkflowEvent, WorkflowStep, WorkflowStepConfig } from 'cloudflare:workers'; +import { beforeEach, describe, expect, test, vi } from 'vitest'; +import { instrumentWorkflowWithSentry } from '../src/workflows'; + +const mockStep: WorkflowStep = { + do: vi + .fn() + .mockImplementation( + async ( + _name: string, + configOrCallback: WorkflowStepConfig | (() => Promise), + maybeCallback?: () => Promise, + ) => { + let count = 0; + + while (count <= 5) { + count += 1; + + try { + if (typeof configOrCallback === 'function') { + return await configOrCallback(); + } else { + return await (maybeCallback ? maybeCallback() : Promise.resolve()); + } + } catch (error) { + await new Promise(resolve => setTimeout(resolve, 1000)); + } + } + }, + ), + sleep: vi.fn(), + sleepUntil: vi.fn(), + waitForEvent: vi.fn(), +}; + +const mockTransport = { + send: vi.fn().mockImplementation(() => Promise.resolve({ statusCode: 200 })), + flush: vi.fn().mockImplementation(() => Promise.resolve(true)), + close: vi.fn().mockImplementation(() => Promise.resolve(true)), +}; + +const mockContext: ExecutionContext = { + waitUntil: vi.fn().mockImplementation(promise => promise), + passThroughOnException: vi.fn(), + props: {}, +}; + +function getSentryOptions() { + return { + dsn: 'https://8@ingest.sentry.io/4', + release: '1.0.0', + tracesSampleRate: 1.0, + transport: () => mockTransport, + }; +} + +type Params = { + // +}; + +const INSTANCE_ID = 'ae0ee067-61b3-4852-9219-5d62282270f0'; +const SAMPLE_RAND = '0.44116884107728693'; +const TRACE_ID = INSTANCE_ID.replace(/-/g, ''); + +describe('workflows', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + test('Calls expected functions', async () => { + class BasicTestWorkflow { + constructor(_ctx: ExecutionContext, _env: unknown) {} + + async run(_event: Readonly>, step: WorkflowStep): Promise { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const files = await step.do('first step', async () => { + return { files: ['doc_7392_rev3.pdf', 'report_x29_final.pdf'] }; + }); + } + } + + const TestWorkflowInstrumented = instrumentWorkflowWithSentry(getSentryOptions, BasicTestWorkflow as any); + const workflow = new TestWorkflowInstrumented(mockContext, {}) as BasicTestWorkflow; + const event = { payload: {}, timestamp: new Date(), instanceId: INSTANCE_ID }; + await workflow.run(event, mockStep); + + expect(mockStep.do).toHaveBeenCalledTimes(1); + expect(mockStep.do).toHaveBeenCalledWith('first step', expect.any(Function)); + expect(mockContext.waitUntil).toHaveBeenCalledTimes(1); + expect(mockContext.waitUntil).toHaveBeenCalledWith(expect.any(Promise)); + expect(mockTransport.send).toHaveBeenCalledTimes(1); + expect(mockTransport.send).toHaveBeenCalledWith([ + expect.objectContaining({ + trace: expect.objectContaining({ + transaction: 'first step', + trace_id: TRACE_ID, + sample_rand: SAMPLE_RAND, + }), + }), + [ + [ + { + type: 'transaction', + }, + expect.objectContaining({ + event_id: expect.any(String), + contexts: { + trace: { + parent_span_id: undefined, + span_id: expect.any(String), + trace_id: TRACE_ID, + data: { + 'sentry.origin': 'auto.faas.cloudflare.workflow', + 'sentry.op': 'function.step.do', + 'sentry.source': 'task', + 'sentry.sample_rate': 1, + }, + op: 'function.step.do', + status: 'ok', + origin: 'auto.faas.cloudflare.workflow', + }, + cloud_resource: { 'cloud.provider': 'cloudflare' }, + runtime: { name: 'cloudflare' }, + }, + type: 'transaction', + transaction_info: { source: 'task' }, + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + }), + ], + ], + ]); + }); + + class ErrorTestWorkflow { + count = 0; + constructor(_ctx: ExecutionContext, _env: unknown) {} + + async run(_event: Readonly>, step: WorkflowStep): Promise { + await step.do('sometimes error step', async () => { + this.count += 1; + + if (this.count <= 1) { + throw new Error('Test error'); + } + + return { files: ['doc_7392_rev3.pdf', 'report_x29_final.pdf'] }; + }); + } + } + + test('Captures step errors', async () => { + const TestWorkflowInstrumented = instrumentWorkflowWithSentry(getSentryOptions, ErrorTestWorkflow as any); + const workflow = new TestWorkflowInstrumented(mockContext, {}) as ErrorTestWorkflow; + const event = { payload: {}, timestamp: new Date(), instanceId: INSTANCE_ID }; + await workflow.run(event, mockStep); + + expect(mockStep.do).toHaveBeenCalledTimes(1); + expect(mockStep.do).toHaveBeenCalledWith('sometimes error step', expect.any(Function)); + expect(mockContext.waitUntil).toHaveBeenCalledTimes(2); + expect(mockContext.waitUntil).toHaveBeenCalledWith(expect.any(Promise)); + expect(mockTransport.send).toHaveBeenCalledTimes(3); + + // First we should get the error event + expect(mockTransport.send).toHaveBeenNthCalledWith(1, [ + expect.objectContaining({ + trace: expect.objectContaining({ + transaction: 'sometimes error step', + trace_id: TRACE_ID, + sample_rand: SAMPLE_RAND, + }), + }), + [ + [ + { + type: 'event', + }, + expect.objectContaining({ + event_id: expect.any(String), + contexts: { + trace: { + parent_span_id: undefined, + span_id: expect.any(String), + trace_id: TRACE_ID, + }, + cloud_resource: { 'cloud.provider': 'cloudflare' }, + runtime: { name: 'cloudflare' }, + }, + timestamp: expect.any(Number), + exception: { + values: [ + expect.objectContaining({ + type: 'Error', + value: 'Test error', + mechanism: { type: 'cloudflare', handled: true }, + }), + ], + }, + }), + ], + ], + ]); + + // The the failed transaction + expect(mockTransport.send).toHaveBeenNthCalledWith(2, [ + expect.objectContaining({ + trace: expect.objectContaining({ + transaction: 'sometimes error step', + trace_id: TRACE_ID, + sample_rand: SAMPLE_RAND, + }), + }), + [ + [ + { + type: 'transaction', + }, + expect.objectContaining({ + event_id: expect.any(String), + contexts: { + trace: { + parent_span_id: undefined, + span_id: expect.any(String), + trace_id: TRACE_ID, + data: { + 'sentry.origin': 'auto.faas.cloudflare.workflow', + 'sentry.op': 'function.step.do', + 'sentry.source': 'task', + 'sentry.sample_rate': 1, + }, + op: 'function.step.do', + status: 'internal_error', + origin: 'auto.faas.cloudflare.workflow', + }, + cloud_resource: { 'cloud.provider': 'cloudflare' }, + runtime: { name: 'cloudflare' }, + }, + type: 'transaction', + transaction_info: { source: 'task' }, + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + }), + ], + ], + ]); + + // The the successful transaction + expect(mockTransport.send).toHaveBeenNthCalledWith(3, [ + expect.objectContaining({ + trace: expect.objectContaining({ + transaction: 'sometimes error step', + trace_id: TRACE_ID, + sample_rand: SAMPLE_RAND, + }), + }), + [ + [ + { + type: 'transaction', + }, + expect.objectContaining({ + event_id: expect.any(String), + contexts: { + trace: { + parent_span_id: undefined, + span_id: expect.any(String), + trace_id: TRACE_ID, + data: { + 'sentry.origin': 'auto.faas.cloudflare.workflow', + 'sentry.op': 'function.step.do', + 'sentry.source': 'task', + 'sentry.sample_rate': 1, + }, + op: 'function.step.do', + status: 'ok', + origin: 'auto.faas.cloudflare.workflow', + }, + cloud_resource: { 'cloud.provider': 'cloudflare' }, + runtime: { name: 'cloudflare' }, + }, + type: 'transaction', + transaction_info: { source: 'task' }, + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + }), + ], + ], + ]); + }); + + test('Sampled random via instanceId', async () => { + const TestWorkflowInstrumented = instrumentWorkflowWithSentry( + // Override the tracesSampleRate to 0.4 to be below the sampleRand + // calculated from the instanceId + () => ({ ...getSentryOptions(), tracesSampleRate: 0.4 }), + ErrorTestWorkflow as any, + ); + const workflow = new TestWorkflowInstrumented(mockContext, {}) as ErrorTestWorkflow; + const event = { payload: {}, timestamp: new Date(), instanceId: INSTANCE_ID }; + await workflow.run(event, mockStep); + + expect(mockStep.do).toHaveBeenCalledTimes(1); + expect(mockStep.do).toHaveBeenCalledWith('sometimes error step', expect.any(Function)); + expect(mockContext.waitUntil).toHaveBeenCalledTimes(2); + expect(mockContext.waitUntil).toHaveBeenCalledWith(expect.any(Promise)); + + // We should get the error event and then nothing else. No transactions + // should be sent + expect(mockTransport.send).toHaveBeenCalledTimes(1); + + expect(mockTransport.send).toHaveBeenCalledWith([ + expect.objectContaining({ + trace: expect.objectContaining({ + transaction: 'sometimes error step', + trace_id: TRACE_ID, + sample_rand: SAMPLE_RAND, + }), + }), + [ + [ + { + type: 'event', + }, + expect.objectContaining({ + event_id: expect.any(String), + contexts: { + trace: { + parent_span_id: undefined, + span_id: expect.any(String), + trace_id: TRACE_ID, + }, + cloud_resource: { 'cloud.provider': 'cloudflare' }, + runtime: { name: 'cloudflare' }, + }, + timestamp: expect.any(Number), + exception: { + values: [ + expect.objectContaining({ + type: 'Error', + value: 'Test error', + }), + ], + }, + }), + ], + ], + ]); + }); +}); diff --git a/packages/sveltekit/src/worker/cloudflare.ts b/packages/sveltekit/src/worker/cloudflare.ts index 9508c331369e..e3d93e8b8922 100644 --- a/packages/sveltekit/src/worker/cloudflare.ts +++ b/packages/sveltekit/src/worker/cloudflare.ts @@ -34,7 +34,7 @@ export function initCloudflareSentryHandle(options: CloudflareOptions): Handle { return wrapRequestHandler( { options: opts, - request: event.request, + request: event.request as Request>, // @ts-expect-error This will exist in Cloudflare context: event.platform.context, }, diff --git a/yarn.lock b/yarn.lock index 5c958751e59d..36d4a3baa246 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2671,10 +2671,10 @@ resolved "https://registry.yarnpkg.com/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20240718.0.tgz#940893e62df7f5a8ec895572b834c95c1e256fbd" integrity sha512-YpCRvvT47XanFum7C3SedOZKK6BfVhqmwdAAVAQFyc4gsCdegZo0JkUkdloC/jwuWlbCACOG2HTADHOqyeolzQ== -"@cloudflare/workers-types@4.20240725.0": - version "4.20240725.0" - resolved "https://registry.yarnpkg.com/@cloudflare/workers-types/-/workers-types-4.20240725.0.tgz#e151e0c069c0070b4d7168c7b0d4ae6c50d3f237" - integrity sha512-L6T/Bg50zm9IIACQVQ0CdVcQL+2nLkRXdPz6BsXF3SlzgjyWR5ndVctAbfr/HLV7aKYxWnnEZsIORsTWb+FssA== +"@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== "@cnakazawa/watch@^1.0.3": version "1.0.4" From 29d35b44b83269d79ffb9a79c1256d51eb81a7d4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Jun 2025 18:38:52 +0000 Subject: [PATCH 04/22] feat(deps): bump @prisma/instrumentation from 6.9.0 to 6.10.1 (#16698) --- packages/node/package.json | 2 +- yarn.lock | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/node/package.json b/packages/node/package.json index 7e53d6e74ff8..99933dfd6db8 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -94,7 +94,7 @@ "@opentelemetry/resources": "^1.30.1", "@opentelemetry/sdk-trace-base": "^1.30.1", "@opentelemetry/semantic-conventions": "^1.34.0", - "@prisma/instrumentation": "6.9.0", + "@prisma/instrumentation": "6.10.1", "@sentry/core": "9.31.0", "@sentry/opentelemetry": "9.31.0", "import-in-the-middle": "^1.13.1", diff --git a/yarn.lock b/yarn.lock index 36d4a3baa246..c17dfbee5c46 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5878,10 +5878,10 @@ resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.28.tgz#d45e01c4a56f143ee69c54dd6b12eade9e270a73" integrity sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw== -"@prisma/instrumentation@6.9.0": - version "6.9.0" - resolved "https://registry.yarnpkg.com/@prisma/instrumentation/-/instrumentation-6.9.0.tgz#2de2def755e39847fc1a38687cfa92a3f115b2b1" - integrity sha512-HFfr89v7WEbygdTzh1t171SUMYlkFRTXf48QthDc1cKduEsGIsOdt1QhOlpF7VK+yMg9EXHaXQo5Z8lQ7WtEYA== +"@prisma/instrumentation@6.10.1": + version "6.10.1" + resolved "https://registry.yarnpkg.com/@prisma/instrumentation/-/instrumentation-6.10.1.tgz#805f0dcdc29638009f0da709828396894815c893" + integrity sha512-JC8qzgEDuFKjuBsqrZvXHINUb12psnE6Qy3q5p2MBhalC1KW1MBBUwuonx6iS5TCfCdtNslHft8uc2r+EdLWWg== dependencies: "@opentelemetry/instrumentation" "^0.52.0 || ^0.53.0 || ^0.54.0 || ^0.55.0 || ^0.56.0 || ^0.57.0" @@ -27195,6 +27195,7 @@ stylus@0.59.0, stylus@^0.59.0: sucrase@^3.27.0, sucrase@^3.35.0, sucrase@getsentry/sucrase#es2020-polyfills: version "3.36.0" + uid fd682f6129e507c00bb4e6319cc5d6b767e36061 resolved "https://codeload.github.com/getsentry/sucrase/tar.gz/fd682f6129e507c00bb4e6319cc5d6b767e36061" dependencies: "@jridgewell/gen-mapping" "^0.3.2" From e2c01ce1cf5be8adcfe4b9ff7fdd7f8b1ef69338 Mon Sep 17 00:00:00 2001 From: betegon Date: Mon, 23 Jun 2025 21:05:38 +0200 Subject: [PATCH 05/22] bump up import-in-the-middle --- packages/node/package.json | 2 +- yarn.lock | 47 +++++++++++++++++++++++--------------- 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/packages/node/package.json b/packages/node/package.json index 99933dfd6db8..7f2e743ff62c 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -97,7 +97,7 @@ "@prisma/instrumentation": "6.10.1", "@sentry/core": "9.31.0", "@sentry/opentelemetry": "9.31.0", - "import-in-the-middle": "^1.13.1", + "import-in-the-middle": "^1.14.2", "minimatch": "^9.0.0" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index c17dfbee5c46..babe56356bb4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17727,7 +17727,17 @@ import-fresh@^3.0.0, import-fresh@^3.2.1: parent-module "^1.0.0" resolve-from "^4.0.0" -import-in-the-middle@^1.13.1, import-in-the-middle@^1.8.1: +import-in-the-middle@^1.14.2: + version "1.14.2" + resolved "https://registry.yarnpkg.com/import-in-the-middle/-/import-in-the-middle-1.14.2.tgz#283661625a88ff7c0462bd2984f77715c3bc967c" + integrity sha512-5tCuY9BV8ujfOpwtAGgsTx9CGUapcFMEEyByLv1B+v2+6DhAcw+Zr0nhQT7uwaZ7DiourxFEscghOR8e1aPLQw== + dependencies: + acorn "^8.14.0" + acorn-import-attributes "^1.9.5" + cjs-module-lexer "^1.2.2" + module-details-from-path "^1.0.3" + +import-in-the-middle@^1.8.1: version "1.13.1" resolved "https://registry.yarnpkg.com/import-in-the-middle/-/import-in-the-middle-1.13.1.tgz#789651f9e93dd902a5a306f499ab51eb72b03a12" integrity sha512-k2V9wNm9B+ysuelDTHjI9d5KPc4l8zAZTGqj+pcynvWkypZd857ryzN8jNC7Pg2YZXNMJcHRPpaDyCBbNyVRpA== @@ -26920,7 +26930,7 @@ string-template@~0.2.1: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string-width@4.2.3, "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -27030,13 +27040,6 @@ stringify-object@^3.2.1: dependencies: ansi-regex "^5.0.1" -strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" @@ -27058,6 +27061,13 @@ strip-ansi@^5.1.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^7.0.1, strip-ansi@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -27193,9 +27203,8 @@ stylus@0.59.0, stylus@^0.59.0: sax "~1.2.4" source-map "^0.7.3" -sucrase@^3.27.0, sucrase@^3.35.0, sucrase@getsentry/sucrase#es2020-polyfills: +sucrase@^3.27.0, sucrase@^3.35.0: version "3.36.0" - uid fd682f6129e507c00bb4e6319cc5d6b767e36061 resolved "https://codeload.github.com/getsentry/sucrase/tar.gz/fd682f6129e507c00bb4e6319cc5d6b767e36061" dependencies: "@jridgewell/gen-mapping" "^0.3.2" @@ -29829,19 +29838,19 @@ wrangler@^3.67.1: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@7.0.0, wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== +wrap-ansi@^6.0.1: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== dependencies: ansi-styles "^4.0.0" string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^6.0.1: - version "6.2.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" - integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== dependencies: ansi-styles "^4.0.0" string-width "^4.1.0" From ffb5511cf6adea31844170f3963d4f98c9511020 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Mon, 23 Jun 2025 16:00:39 -0400 Subject: [PATCH 06/22] fix(node): account for Object. syntax with local variables matching (#16702) resolves https://github.com/getsentry/sentry-javascript/issues/16701 For function frames that give us function named `X`, the debugger protocol (which is what local variables use) sometimes outputs `Object.X` as the function name. This causes issues when we try to apply local variables to a stacktrace frame. --- .../local-variables-name-matching.js | 35 +++++++++++++++++++ .../suites/public-api/LocalVariables/test.ts | 26 ++++++++++++++ .../integrations/local-variables/common.ts | 2 +- 3 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 dev-packages/node-integration-tests/suites/public-api/LocalVariables/local-variables-name-matching.js diff --git a/dev-packages/node-integration-tests/suites/public-api/LocalVariables/local-variables-name-matching.js b/dev-packages/node-integration-tests/suites/public-api/LocalVariables/local-variables-name-matching.js new file mode 100644 index 000000000000..b9027b749181 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/public-api/LocalVariables/local-variables-name-matching.js @@ -0,0 +1,35 @@ +const Sentry = require('@sentry/node'); +const { loggingTransport } = require('@sentry-internal/node-integration-tests'); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + includeLocalVariables: true, + transport: loggingTransport, +}); + +process.on('uncaughtException', () => { + // do nothing - this will prevent the Error below from closing this process +}); + +// Testing GraphQL resolver: https://github.com/getsentry/sentry-javascript/issues/16701 +const resolvers = { + Query: { + testSentry: args => { + try { + args.foo.map(x => x); + return true; + } catch (error) { + Sentry.captureException(error); + return false; + } + }, + }, +}; + +function regularFunction() { + resolvers.Query.testSentry({ foo: undefined }); +} + +setTimeout(() => { + regularFunction(); +}, 1000); diff --git a/dev-packages/node-integration-tests/suites/public-api/LocalVariables/test.ts b/dev-packages/node-integration-tests/suites/public-api/LocalVariables/test.ts index e95e5a9e3767..2c87d14c2b45 100644 --- a/dev-packages/node-integration-tests/suites/public-api/LocalVariables/test.ts +++ b/dev-packages/node-integration-tests/suites/public-api/LocalVariables/test.ts @@ -101,4 +101,30 @@ describe('LocalVariables integration', () => { .start() .completed(); }); + + test('Should handle different function name formats', async () => { + await createRunner(__dirname, 'local-variables-name-matching.js') + .expect({ + event: { + exception: { + values: [ + { + stacktrace: { + frames: expect.arrayContaining([ + expect.objectContaining({ + function: expect.stringMatching(/^(Object\.testSentry|testSentry)$/), + vars: expect.objectContaining({ + args: expect.any(Object), + }), + }), + ]), + }, + }, + ], + }, + }, + }) + .start() + .completed(); + }); }); diff --git a/packages/node/src/integrations/local-variables/common.ts b/packages/node/src/integrations/local-variables/common.ts index 58ccea70d6de..471fa1a69864 100644 --- a/packages/node/src/integrations/local-variables/common.ts +++ b/packages/node/src/integrations/local-variables/common.ts @@ -70,7 +70,7 @@ export function isAnonymous(name: string | undefined): boolean { /** Do the function names appear to match? */ export function functionNamesMatch(a: string | undefined, b: string | undefined): boolean { - return a === b || (isAnonymous(a) && isAnonymous(b)); + return a === b || `Object.${a}` === b || a === `Object.${b}` || (isAnonymous(a) && isAnonymous(b)); } export interface FrameVariables { From e4872b00974f95d1fd6d1642b43859c5eac5e1ed Mon Sep 17 00:00:00 2001 From: betegon Date: Mon, 23 Jun 2025 22:06:43 +0200 Subject: [PATCH 07/22] fix duplicate --- yarn.lock | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/yarn.lock b/yarn.lock index babe56356bb4..1e714f6340be 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17727,7 +17727,7 @@ import-fresh@^3.0.0, import-fresh@^3.2.1: parent-module "^1.0.0" resolve-from "^4.0.0" -import-in-the-middle@^1.14.2: +import-in-the-middle@^1.14.2, import-in-the-middle@^1.8.1: version "1.14.2" resolved "https://registry.yarnpkg.com/import-in-the-middle/-/import-in-the-middle-1.14.2.tgz#283661625a88ff7c0462bd2984f77715c3bc967c" integrity sha512-5tCuY9BV8ujfOpwtAGgsTx9CGUapcFMEEyByLv1B+v2+6DhAcw+Zr0nhQT7uwaZ7DiourxFEscghOR8e1aPLQw== @@ -17737,16 +17737,6 @@ import-in-the-middle@^1.14.2: cjs-module-lexer "^1.2.2" module-details-from-path "^1.0.3" -import-in-the-middle@^1.8.1: - version "1.13.1" - resolved "https://registry.yarnpkg.com/import-in-the-middle/-/import-in-the-middle-1.13.1.tgz#789651f9e93dd902a5a306f499ab51eb72b03a12" - integrity sha512-k2V9wNm9B+ysuelDTHjI9d5KPc4l8zAZTGqj+pcynvWkypZd857ryzN8jNC7Pg2YZXNMJcHRPpaDyCBbNyVRpA== - dependencies: - acorn "^8.14.0" - acorn-import-attributes "^1.9.5" - cjs-module-lexer "^1.2.2" - module-details-from-path "^1.0.3" - import-lazy@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" From 9cd315306b003bf494d0a033b021c932d4b989b0 Mon Sep 17 00:00:00 2001 From: Petter Friberg <3703560+flaeppe@users.noreply.github.com> Date: Mon, 23 Jun 2025 22:28:52 +0200 Subject: [PATCH 08/22] fix(google-cloud-serverless): Make `CloudEventsContext` compatible with `CloudEvent` (#16705) Update CloudEventsContext interface types Make `type` and `source` required properties - Closes: #16653 - Refs: https://github.com/getsentry/sentry-javascript/issues/16653#issuecomment-2997811924 - Refs: #16661 --- packages/google-cloud-serverless/src/gcpfunction/general.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/google-cloud-serverless/src/gcpfunction/general.ts b/packages/google-cloud-serverless/src/gcpfunction/general.ts index cae27ee6c2ee..e0ff779e924d 100644 --- a/packages/google-cloud-serverless/src/gcpfunction/general.ts +++ b/packages/google-cloud-serverless/src/gcpfunction/general.ts @@ -31,8 +31,8 @@ export interface CloudEventsContext { [key: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any id: string; specversion: string; - type?: string; - source?: string; + type: string; + source: string; time?: string; schemaurl?: string; contenttype?: string; From c37d4e5a7546be97afa92ae5d7950cc7b6542cdd Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Mon, 23 Jun 2025 23:35:15 +0200 Subject: [PATCH 09/22] chore: Add external contributor to CHANGELOG.md (#16706) This PR adds the external contributor to the CHANGELOG.md file, so that they are credited for their contribution. See #16705 Co-authored-by: AbhiPrasad <18689448+AbhiPrasad@users.noreply.github.com> --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f3f00b26f1d..67470eb2cc30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +Work in this release was contributed by @flaeppe. Thank you for your contribution! + ## 9.31.0 ### Important Changes From ebc1e266495dd4f22a65e971b6febb35220f2129 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Tue, 24 Jun 2025 10:38:19 +0200 Subject: [PATCH 10/22] fix(nextjs): Stop injecting release value when create release options is set to `false` (#16695) closes https://github.com/getsentry/sentry-javascript/issues/16593 --- packages/nextjs/src/config/webpack.ts | 7 +- .../nextjs/src/config/withSentryConfig.ts | 11 ++- .../test/config/withSentryConfig.test.ts | 67 +++++++++++++++++++ 3 files changed, 81 insertions(+), 4 deletions(-) diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index aeef52d4e309..bcfbbd2e151f 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -689,6 +689,10 @@ function addValueInjectionLoader( ): void { const assetPrefix = userNextConfig.assetPrefix || userNextConfig.basePath || ''; + // Check if release creation is disabled to prevent injection that breaks build determinism + const shouldCreateRelease = userSentryOptions.release?.create !== false; + const releaseToInject = releaseName && shouldCreateRelease ? releaseName : undefined; + const isomorphicValues = { // `rewritesTunnel` set by the user in Next.js config _sentryRewritesTunnelPath: @@ -700,7 +704,8 @@ function addValueInjectionLoader( // The webpack plugin's release injection breaks the `app` directory so we inject the release manually here instead. // Having a release defined in dev-mode spams releases in Sentry so we only set one in non-dev mode - SENTRY_RELEASE: releaseName && !buildContext.dev ? { id: releaseName } : undefined, + // Only inject if release creation is not explicitly disabled (to maintain build determinism) + SENTRY_RELEASE: releaseToInject && !buildContext.dev ? { id: releaseToInject } : undefined, _sentryBasePath: buildContext.dev ? userNextConfig.basePath : undefined, }; diff --git a/packages/nextjs/src/config/withSentryConfig.ts b/packages/nextjs/src/config/withSentryConfig.ts index ac1e76231eb8..c8eabd00d85c 100644 --- a/packages/nextjs/src/config/withSentryConfig.ts +++ b/packages/nextjs/src/config/withSentryConfig.ts @@ -90,7 +90,12 @@ function getFinalConfigObject( incomingUserNextConfigObject: NextConfigObject, userSentryOptions: SentryBuildOptions, ): NextConfigObject { - const releaseName = userSentryOptions.release?.name ?? getSentryRelease() ?? getGitRevision(); + // Only determine a release name if release creation is not explicitly disabled + // This prevents injection of Git commit hashes that break build determinism + const shouldCreateRelease = userSentryOptions.release?.create !== false; + const releaseName = shouldCreateRelease + ? userSentryOptions.release?.name ?? getSentryRelease() ?? getGitRevision() + : userSentryOptions.release?.name; if (userSentryOptions?.tunnelRoute) { if (incomingUserNextConfigObject.output === 'export') { @@ -126,8 +131,8 @@ function getFinalConfigObject( // 1. compile: Code compilation // 2. generate: Environment variable inlining and prerendering (We don't instrument this phase, we inline in the compile phase) // - // We assume a single “full” build and reruns Webpack instrumentation in both phases. - // During the generate step it collides with Next.js’s inliner + // We assume a single "full" build and reruns Webpack instrumentation in both phases. + // During the generate step it collides with Next.js's inliner // producing malformed JS and build failures. // We skip Sentry processing during generate to avoid this issue. return incomingUserNextConfigObject; diff --git a/packages/nextjs/test/config/withSentryConfig.test.ts b/packages/nextjs/test/config/withSentryConfig.test.ts index ee4b2d364c6a..f1f1ae13f9fa 100644 --- a/packages/nextjs/test/config/withSentryConfig.test.ts +++ b/packages/nextjs/test/config/withSentryConfig.test.ts @@ -144,4 +144,71 @@ describe('withSentryConfig', () => { ); }); }); + + describe('release injection behavior', () => { + afterEach(() => { + vi.restoreAllMocks(); + + // clear env to avoid leaking env vars from fixtures + delete exportedNextConfig.env; + delete process.env.SENTRY_RELEASE; + }); + + it('does not inject release when create is false', () => { + const sentryOptions = { + release: { + create: false, + }, + }; + + // clear env to avoid leaking env vars from fixtures + delete exportedNextConfig.env; + + const finalConfig = materializeFinalNextConfig(exportedNextConfig, undefined, sentryOptions); + + // Should not inject release into environment when create is false + expect(finalConfig.env).not.toHaveProperty('_sentryRelease'); + }); + + it('injects release when create is true (default)', () => { + const sentryOptions = { + release: { + create: true, + name: 'test-release@1.0.0', + }, + }; + + const finalConfig = materializeFinalNextConfig(exportedNextConfig, undefined, sentryOptions); + + // Should inject release into environment when create is true + expect(finalConfig.env).toHaveProperty('_sentryRelease', 'test-release@1.0.0'); + }); + + it('injects release with explicit name', () => { + const sentryOptions = { + release: { + name: 'custom-release-v2.1.0', + }, + }; + + const finalConfig = materializeFinalNextConfig(exportedNextConfig, undefined, sentryOptions); + + // Should inject the explicit release name + expect(finalConfig.env).toHaveProperty('_sentryRelease', 'custom-release-v2.1.0'); + }); + + it('falls back to SENTRY_RELEASE environment variable when no explicit name provided', () => { + process.env.SENTRY_RELEASE = 'env-release-1.5.0'; + + const sentryOptions = { + release: { + create: true, + }, + }; + + const finalConfig = materializeFinalNextConfig(exportedNextConfig, undefined, sentryOptions); + + expect(finalConfig.env).toHaveProperty('_sentryRelease', 'env-release-1.5.0'); + }); + }); }); From e0c905967bfaf5b9a9cafff28c8e5526a435928b Mon Sep 17 00:00:00 2001 From: Rola Abuhasna Date: Tue, 24 Jun 2025 14:36:54 +0200 Subject: [PATCH 11/22] fix: Export logger from sveltekit worker (#16716) ### Issue The `logger` was missing from SvelteKit's worker environment exports, causing `Sentry.logger` to be `null` when deploying to Cloudflare Pages. Users experienced: - `Sentry.logger` is `null` in production - `logger.info()` throws access violations - Works fine locally but fails in Cloudflare Pages deployment Cloudflare Pages uses the **worker runtime environment** (not Node.js) when `@sveltejs/adapter-cloudflare` is configured which is why logger must be exported from there. [official Cloudflare Pages Functions documentation](https://developers.cloudflare.com/pages/functions/) https://developers.cloudflare.com/pages/framework-guides/deploy-a-svelte-kit-site/#functions-setup --- packages/sveltekit/src/worker/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/sveltekit/src/worker/index.ts b/packages/sveltekit/src/worker/index.ts index 2dde8c61a1dc..e49a493fb0b8 100644 --- a/packages/sveltekit/src/worker/index.ts +++ b/packages/sveltekit/src/worker/index.ts @@ -49,6 +49,7 @@ export { isEnabled, lastEventId, linkedErrorsIntegration, + logger, requestDataIntegration, rewriteFramesIntegration, Scope, From 8d17ffc3f25cfb7bb9e078bf9571d7e03d4bfc1d Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Tue, 24 Jun 2025 15:11:11 +0200 Subject: [PATCH 12/22] feat(nextjs): Expose top level buildTime `errorHandler` option (#16718) Exposes a `errorHandler` option in `withSentryConfig` for handling built-time errors. --- packages/nextjs/src/config/types.ts | 17 +++++++++++++++++ .../nextjs/src/config/webpackPluginOptions.ts | 1 + .../config/webpack/webpackPluginOptions.test.ts | 17 +++++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/packages/nextjs/src/config/types.ts b/packages/nextjs/src/config/types.ts index fe05624ba15e..e8172efea72e 100644 --- a/packages/nextjs/src/config/types.ts +++ b/packages/nextjs/src/config/types.ts @@ -445,6 +445,23 @@ export type SentryBuildOptions = { */ automaticVercelMonitors?: boolean; + /** + * When an error occurs during release creation or sourcemaps upload, the plugin will call this function. + * + * By default, the plugin will simply throw an error, thereby stopping the bundling process. + * If an `errorHandler` callback is provided, compilation will continue, unless an error is + * thrown in the provided callback. + * + * To allow compilation to continue but still emit a warning, set this option to the following: + * + * ```js + * (err) => { + * console.warn(err); + * } + * ``` + */ + errorHandler?: (err: Error) => void; + /** * Contains a set of experimental flags that might change in future releases. These flags enable * features that are still in development and may be modified, renamed, or removed without notice. diff --git a/packages/nextjs/src/config/webpackPluginOptions.ts b/packages/nextjs/src/config/webpackPluginOptions.ts index 641efd77524c..f4ff4363cdb7 100644 --- a/packages/nextjs/src/config/webpackPluginOptions.ts +++ b/packages/nextjs/src/config/webpackPluginOptions.ts @@ -62,6 +62,7 @@ export function getWebpackPluginOptions( project: sentryBuildOptions.project, telemetry: sentryBuildOptions.telemetry, debug: sentryBuildOptions.debug, + errorHandler: sentryBuildOptions.errorHandler, reactComponentAnnotation: { ...sentryBuildOptions.reactComponentAnnotation, ...sentryBuildOptions.unstable_sentryWebpackPluginOptions?.reactComponentAnnotation, diff --git a/packages/nextjs/test/config/webpack/webpackPluginOptions.test.ts b/packages/nextjs/test/config/webpack/webpackPluginOptions.test.ts index 76ab58be9b64..e95ab5c82bf8 100644 --- a/packages/nextjs/test/config/webpack/webpackPluginOptions.test.ts +++ b/packages/nextjs/test/config/webpack/webpackPluginOptions.test.ts @@ -138,6 +138,23 @@ describe('getWebpackPluginOptions()', () => { }); }); + it('forwards errorHandler option', () => { + const buildContext = generateBuildContext({ isServer: false }); + const mockErrorHandler = (err: Error) => { + throw err; + }; + + const generatedPluginOptions = getWebpackPluginOptions( + buildContext, + { + errorHandler: mockErrorHandler, + }, + undefined, + ); + + expect(generatedPluginOptions.errorHandler).toBe(mockErrorHandler); + }); + it('returns the right `assets` and `ignore` values during the server build', () => { const buildContext = generateBuildContext({ isServer: true }); const generatedPluginOptions = getWebpackPluginOptions(buildContext, {}, undefined); From cc2bffd3bb779ee9884b93cc3749fb1f1da44398 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Tue, 24 Jun 2025 09:20:24 -0400 Subject: [PATCH 13/22] test: Bump amqplib integration test timeout (#16699) resolves https://github.com/getsentry/sentry-javascript/issues/16697 --- .../node-integration-tests/suites/tracing/amqplib/test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-packages/node-integration-tests/suites/tracing/amqplib/test.ts b/dev-packages/node-integration-tests/suites/tracing/amqplib/test.ts index ad84a74b929a..0be272187f3f 100644 --- a/dev-packages/node-integration-tests/suites/tracing/amqplib/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/amqplib/test.ts @@ -30,7 +30,7 @@ describe('amqplib auto-instrumentation', () => { }); createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createTestRunner, test) => { - test('should be able to send and receive messages', async () => { + test('should be able to send and receive messages', { timeout: 60_000 }, async () => { await createTestRunner() .withDockerCompose({ workingDirectory: [__dirname], From 7d952541c3beae802fa7a356acaeee1be93e4737 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Tue, 24 Jun 2025 09:20:47 -0400 Subject: [PATCH 14/22] chore: Add cursor rules for dependency upgrades (#16669) I want to unleash an army of background agents to crunch through https://github.com/getsentry/sentry-javascript/security/dependabot?q=is%3Aopen+ To do this, I added some cursor rules for upgrading dependencies within the repo. I also tested this out with two dependabot security warnings: resolves https://github.com/getsentry/sentry-javascript/security/dependabot/615 resolves https://github.com/getsentry/sentry-javascript/security/dependabot/613 --------- Co-authored-by: Armen Zambrano G. <44410+armenzg@users.noreply.github.com> --- .cursor/rules/publishing_release.mdc | 1 + .cursor/rules/sdk_dependency_upgrades.mdc | 163 ++++++++++++++ yarn.lock | 253 ++++++++++++++++------ 3 files changed, 354 insertions(+), 63 deletions(-) create mode 100644 .cursor/rules/sdk_dependency_upgrades.mdc diff --git a/.cursor/rules/publishing_release.mdc b/.cursor/rules/publishing_release.mdc index bf801c7a7ff3..01571f684581 100644 --- a/.cursor/rules/publishing_release.mdc +++ b/.cursor/rules/publishing_release.mdc @@ -3,6 +3,7 @@ description: Use this rule if you are looking to publish a release for the Sentr globs: alwaysApply: false --- + # Publishing a Release Use these guidelines when publishing a new Sentry JavaScript SDK release. diff --git a/.cursor/rules/sdk_dependency_upgrades.mdc b/.cursor/rules/sdk_dependency_upgrades.mdc new file mode 100644 index 000000000000..826249108f01 --- /dev/null +++ b/.cursor/rules/sdk_dependency_upgrades.mdc @@ -0,0 +1,163 @@ +--- +description: Use this rule if you are looking to upgrade a dependency in the Sentry JavaScript SDKs +globs: +alwaysApply: false +--- +# Yarn v1 Dependency Upgrades + +## Upgrade Process + +### Dependency Analysis + +```bash +# Check dependency tree +yarn list --depth=0 + +# Find why package is installed +yarn why [package-name] +``` + +### Root Workspace vs. Package Dependencies + +**CRITICAL**: Understand the difference between dependency types: + +- **Root Workspace dependencies**: Shared dev tools, build tools, testing frameworks +- **Package dependencies**: Package-specific runtime and dev dependencies +- Always check if dependency should be in root workspace or package level + +### Upgrade Dependencies + +**MANDATORY**: Only ever upgrade a single package at a time. + +**CRITICAL RULE**: If a dependency is not defined in `package.json` anywhere, the upgrade must be run in the root workspace as the `yarn.lock` file is contained there. + +```bash +# Upgrade specific package to latest compatible version +npx yarn-update-dependency@latest [package-name] +``` + +Avoid upgrading top-level dependencies (defined in `package.json`), especially if they are used for tests. If you are going to upgrade them, ask the user before proceeding. + +**REQUIREMENT**: If a `package.json` file is updated, make sure it has a new line at the end. + +#### CRITICAL: OpenTelemetry Dependency Constraint + +**STOP UPGRADE IMMEDIATELY** if upgrading any dependency with `opentelemetry` in the name and the new version or any of its dependencies uses forbidden OpenTelemetry versions. + +**FORBIDDEN VERSION PATTERNS:** +- `2.x.x` versions (e.g., `2.0.0`, `2.1.0`) +- `0.2xx.x` versions (e.g., `0.200.0`, `0.201.0`) + +When upgrading OpenTelemetry dependencies: +1. Check the dependency's `package.json` after upgrade +2. Verify the package itself doesn't use forbidden version patterns +3. Verify none of its dependencies use `@opentelemetry/*` packages with forbidden version patterns +4. **Example**: `@opentelemetry/instrumentation-pg@0.52.0` is forbidden because it bumped to core `2.0.0` and instrumentation `0.200.0` +5. If forbidden OpenTelemetry versions are detected, **ABORT the upgrade** and notify the user that this upgrade cannot proceed due to OpenTelemetry v2+ compatibility constraints + +#### CRITICAL: E2E Test Dependencies + +**DO NOT UPGRADE** the major version of dependencies in test applications where the test name explicitly mentions a dependency version. + +**RULE**: For `dev-packages/e2e-tests/test-applications/*`, if the test directory name mentions a specific version (e.g., `nestjs-8`), do not upgrade that dependency beyond the mentioned major version. + +**Example**: Do not upgrade the nestjs version of `dev-packages/e2e-tests/test-applications/nestjs-8` to nestjs 9 or above because the test name mentions nestjs 8. + +## Safety Protocols + +### Pre-Upgrade Checklist + +**COMPLETE ALL STEPS** before proceeding with any upgrade: + +1. **Backup**: Ensure clean git state or create backup branch +2. **CI Status**: Verify all tests are passing +3. **Lockfile works**: Confirm `yarn.lock` is in a good state (no merge conflicts) +4. **OpenTelemetry Check**: For OpenTelemetry dependencies, verify no forbidden version patterns (`2.x.x` or `0.2xx.x`) will be introduced + +### Post-Upgrade Verification + +```bash +# rebuild everything +yarn install + +# Build the project +yarn build:dev + +# Make sure dependencies are deduplicated +yarn dedupe-deps:fix + +# Fix any linting issues +yarn fix + +# Check circular dependencies +yarn circularDepCheck +``` + +## Version Management + +### Pinning Strategies + +- **Exact versions** (`1.2.3`): Use for critical dependencies +- **Caret versions** (`^1.2.3`): Allow minor updates only +- **Latest tag**: Avoid as much as possible other than in certain testing and development scenarios + +## Troubleshooting + +- **Yarn Version**: Run `yarn --version` - must be yarn v1 (not v2/v3/v4) +- **Lockfile Issues**: Verify yarn.lock exists and is properly maintained. Fix merge conflicts by running `yarn install` + +## Best Practices + +### Security Audits + +```bash +# Check for security vulnerabilities +yarn audit + +# Fix automatically fixable vulnerabilities +yarn audit fix + +# Install security patches only +yarn upgrade --security-only +``` + +### Check for Outdated Dependencies + +```bash +# Check all outdated dependencies +yarn outdated + +# Check specific package +yarn outdated [package-name] + +# Check dependencies in specific workspace +yarn workspace [workspace-name] outdated +``` + +### Using yarn info for Dependency Inspection + +Use `yarn info` to inspect package dependencies before and after upgrades: + +```bash +# Check current version and dependencies +yarn info + +# Check specific version dependencies +yarn info @ + +# Check dependencies field specifically +yarn info @ dependencies + +# Check all available versions +yarn info versions +``` + +The `yarn info` command provides detailed dependency information without requiring installation, making it particularly useful for: +- Verifying OpenTelemetry packages don't introduce forbidden version patterns (`2.x.x` or `0.2xx.x`) +- Checking what dependencies a package will bring in before upgrading +- Understanding package version history and compatibility + +### Documentation + +- Update README or code comments if dependency change affects usage of the SDK or its integrations +- Notify team of significant changes diff --git a/yarn.lock b/yarn.lock index 1e714f6340be..6dfa6bc056fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10539,6 +10539,39 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +bare-events@^2.2.0, bare-events@^2.5.4: + version "2.5.4" + resolved "https://registry.yarnpkg.com/bare-events/-/bare-events-2.5.4.tgz#16143d435e1ed9eafd1ab85f12b89b3357a41745" + integrity sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA== + +bare-fs@^4.0.1: + version "4.1.5" + resolved "https://registry.yarnpkg.com/bare-fs/-/bare-fs-4.1.5.tgz#1d06c076e68cc8bf97010d29af9e3ac3808cdcf7" + integrity sha512-1zccWBMypln0jEE05LzZt+V/8y8AQsQQqxtklqaIyg5nu6OAYFhZxPXinJTSG+kU5qyNmeLgcn9AW7eHiCHVLA== + dependencies: + bare-events "^2.5.4" + bare-path "^3.0.0" + bare-stream "^2.6.4" + +bare-os@^3.0.1: + version "3.6.1" + resolved "https://registry.yarnpkg.com/bare-os/-/bare-os-3.6.1.tgz#9921f6f59edbe81afa9f56910658422c0f4858d4" + integrity sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g== + +bare-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bare-path/-/bare-path-3.0.0.tgz#b59d18130ba52a6af9276db3e96a2e3d3ea52178" + integrity sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw== + dependencies: + bare-os "^3.0.1" + +bare-stream@^2.6.4: + version "2.6.5" + resolved "https://registry.yarnpkg.com/bare-stream/-/bare-stream-2.6.5.tgz#bba8e879674c4c27f7e27805df005c15d7a2ca07" + integrity sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA== + dependencies: + streamx "^2.21.0" + base64-arraybuffer-es6@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/base64-arraybuffer-es6/-/base64-arraybuffer-es6-0.7.0.tgz#dbe1e6c87b1bf1ca2875904461a7de40f21abc86" @@ -11558,6 +11591,14 @@ calculate-cache-key-for-tree@^2.0.0: dependencies: json-stable-stringify "^1.0.1" +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" @@ -11569,6 +11610,14 @@ call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bin get-intrinsic "^1.2.4" set-function-length "^1.2.1" +call-bound@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" + integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== + dependencies: + call-bind-apply-helpers "^1.0.2" + get-intrinsic "^1.3.0" + callsites@^3.0.0, callsites@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -12460,9 +12509,9 @@ cookie-signature@1.0.6: integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= cookie-signature@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.2.0.tgz#4deed303f5f095e7a02c979e3fcb19157f5eaeea" - integrity sha512-R0BOPfLGTitaKhgKROKZQN6iyq2iDQcH1DOF8nJoaWapguX5bC2w+Q/I9NmmM5lfcvEarnLZr+cCvmEYYSXvYA== + version "1.2.2" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.2.2.tgz#57c7fc3cc293acab9fec54d73e15690ebe4a1793" + integrity sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg== cookie@0.7.1: version "0.7.1" @@ -13599,6 +13648,15 @@ dset@^3.1.2: resolved "https://registry.yarnpkg.com/dset/-/dset-3.1.4.tgz#f8eaf5f023f068a036d08cd07dc9ffb7d0065248" integrity sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA== +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + duplexer3@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" @@ -14412,12 +14470,10 @@ es-check@^7.2.1: supports-color "^8.1.1" winston "3.13.0" -es-define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" - integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== - dependencies: - get-intrinsic "^1.2.4" +es-define-property@^1.0.0, es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== es-errors@^1.3.0: version "1.3.0" @@ -14449,6 +14505,13 @@ es-module-lexer@^1.2.1, es-module-lexer@^1.3.0, es-module-lexer@^1.3.1, es-modul resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz#9159601561880a85f2734560a9099b2c31e5372a" integrity sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA== +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + es-shim-unscopables@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz#702e632193201e3edf8713635d083d378e510241" @@ -15601,7 +15664,7 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-fifo@^1.1.0, fast-fifo@^1.2.0: +fast-fifo@^1.2.0, fast-fifo@^1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.3.2.tgz#286e31de96eb96d38a97899815740ba2a4f3640c" integrity sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ== @@ -16373,16 +16436,21 @@ get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2, get-intrinsic@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" - integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== +get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2, get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" es-errors "^1.3.0" + es-object-atoms "^1.1.1" function-bind "^1.1.2" - has-proto "^1.0.1" - has-symbols "^1.0.3" - hasown "^2.0.0" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" get-own-enumerable-property-symbols@^3.0.0: version "3.0.2" @@ -16414,6 +16482,14 @@ get-port@5.1.1: resolved "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193" integrity sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ== +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + get-source@^2.0.12: version "2.0.12" resolved "https://registry.yarnpkg.com/get-source/-/get-source-2.0.12.tgz#0b47d57ea1e53ce0d3a69f4f3d277eb8047da944" @@ -16828,12 +16904,10 @@ google-p12-pem@^3.0.3: dependencies: node-forge "^1.3.1" -gopd@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" - integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== - dependencies: - get-intrinsic "^1.1.3" +gopd@^1.0.1, gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== got@^9.6.0: version "9.6.0" @@ -16991,15 +17065,10 @@ has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: dependencies: es-define-property "^1.0.0" -has-proto@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" - integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== - -has-symbols@^1.0.1, has-symbols@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" - integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== +has-symbols@^1.0.1, has-symbols@^1.0.3, has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: version "1.0.2" @@ -17073,7 +17142,7 @@ hash-sum@^2.0.0: resolved "https://registry.yarnpkg.com/hash-sum/-/hash-sum-2.0.0.tgz#81d01bb5de8ea4a214ad5d6ead1b523460b0b45a" integrity sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg== -hasown@^2.0.0: +hasown@^2.0.0, hasown@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== @@ -20153,6 +20222,11 @@ matcher-collection@^2.0.0, matcher-collection@^2.0.1: "@types/minimatch" "^3.0.3" minimatch "^3.0.2" +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + mdast-util-definitions@^5.0.0: version "5.1.2" resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz#9910abb60ac5d7115d6819b57ae0bcef07a3f7a7" @@ -20751,11 +20825,16 @@ micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: braces "^3.0.2" picomatch "^2.3.1" -mime-db@1.52.0, "mime-db@>= 1.43.0 < 2", mime-db@^1.52.0: +mime-db@1.52.0: version "1.52.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== +"mime-db@>= 1.43.0 < 2", mime-db@^1.52.0: + version "1.54.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" + integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== + mime-types@^2.1.12, mime-types@^2.1.18, mime-types@^2.1.26, mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" @@ -22291,10 +22370,10 @@ object-hash@^1.3.1: resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.3.1.tgz#fde452098a951cb145f039bb7d455449ddc126df" integrity sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA== -object-inspect@^1.12.2, object-inspect@^1.13.1: - version "1.13.2" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff" - integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g== +object-inspect@^1.12.2, object-inspect@^1.13.3: + version "1.13.4" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" + integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== object-is@^1.1.5: version "1.1.6" @@ -24420,13 +24499,20 @@ pupa@^2.1.1: dependencies: escape-goat "^2.0.0" -qs@6.13.0, qs@^6.4.0: +qs@6.13.0: version "6.13.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== dependencies: side-channel "^1.0.6" +qs@^6.4.0: + version "6.14.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.0.tgz#c63fa40680d2c5c941412a0e899c89af60c0a930" + integrity sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w== + dependencies: + side-channel "^1.1.0" + query-string@^4.2.2: version "4.3.4" resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" @@ -24445,10 +24531,10 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== -queue-tick@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/queue-tick/-/queue-tick-1.0.1.tgz#f6f07ac82c1fd60f82e098b417a80e52f1f4c142" - integrity sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag== +quick-format-unescaped@^4.0.3: + version "4.0.4" + resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz#93ef6dd8d3453cbc7970dd614fad4c5954d6b5a7" + integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg== quick-format-unescaped@^4.0.3: version "4.0.4" @@ -26186,15 +26272,45 @@ shimmer@^1.2.1: resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337" integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw== -side-channel@^1.0.4, side-channel@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" - integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== +side-channel-list@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" + integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== dependencies: - call-bind "^1.0.7" es-errors "^1.3.0" - get-intrinsic "^1.2.4" - object-inspect "^1.13.1" + object-inspect "^1.13.3" + +side-channel-map@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" + integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + +side-channel-weakmap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" + integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + side-channel-map "^1.0.1" + +side-channel@^1.0.4, side-channel@^1.0.6, side-channel@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" + integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + side-channel-list "^1.0.0" + side-channel-map "^1.0.1" + side-channel-weakmap "^1.0.2" sift@13.5.2: version "13.5.2" @@ -26888,13 +27004,15 @@ streamsearch@^1.1.0: resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== -streamx@^2.15.0: - version "2.15.1" - resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.15.1.tgz#396ad286d8bc3eeef8f5cea3f029e81237c024c6" - integrity sha512-fQMzy2O/Q47rgwErk/eGeLu/roaFWV0jVsogDmrszM9uIw8L5OA+t+V93MgYlufNptfjmYR1tOMWhei/Eh7TQA== +streamx@^2.15.0, streamx@^2.21.0: + version "2.22.1" + resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.22.1.tgz#c97cbb0ce18da4f4db5a971dc9ab68ff5dc7f5a5" + integrity sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA== dependencies: - fast-fifo "^1.1.0" - queue-tick "^1.0.1" + fast-fifo "^1.3.2" + text-decoder "^1.1.0" + optionalDependencies: + bare-events "^2.2.0" strict-uri-encode@^1.0.0: version "1.1.0" @@ -27384,9 +27502,9 @@ tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0: integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== tar-fs@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" - integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== + version "2.1.3" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.3.tgz#fb3b8843a26b6f13a08e606f7922875eb1fbbf92" + integrity sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg== dependencies: chownr "^1.1.1" mkdirp-classic "^0.5.2" @@ -27394,13 +27512,15 @@ tar-fs@^2.0.0: tar-stream "^2.1.4" tar-fs@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-3.0.4.tgz#a21dc60a2d5d9f55e0089ccd78124f1d3771dbbf" - integrity sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w== + version "3.0.10" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-3.0.10.tgz#60f8ccd60fe30164bdd3d6606619650236ed38f7" + integrity sha512-C1SwlQGNLe/jPNqapK8epDsXME7CAJR5RL3GcE6KWx1d9OUByzoHVcbu1VPI8tevg9H8Alae0AApHHFGzrD5zA== dependencies: - mkdirp-classic "^0.5.2" pump "^3.0.0" tar-stream "^3.1.5" + optionalDependencies: + bare-fs "^4.0.1" + bare-path "^3.0.0" tar-stream@^2.1.4, tar-stream@~2.2.0: version "2.2.0" @@ -27578,6 +27698,13 @@ testem@^3.10.1: tap-parser "^7.0.0" tmp "0.0.33" +text-decoder@^1.1.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/text-decoder/-/text-decoder-1.2.3.tgz#b19da364d981b2326d5f43099c310cc80d770c65" + integrity sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA== + dependencies: + b4a "^1.6.4" + text-extensions@^1.0.0: version "1.9.0" resolved "https://registry.yarnpkg.com/text-extensions/-/text-extensions-1.9.0.tgz#1853e45fee39c945ce6f6c36b2d659b5aabc2a26" From ebdd485cd484831ae20c02898675707df2b3267a Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Tue, 24 Jun 2025 09:52:26 -0400 Subject: [PATCH 15/22] feat(node): update pipeline spans to use agent naming (#16712) This matches our expectations for the new trace views being built and aligns to Python. ![image](https://github.com/user-attachments/assets/f1d430b3-547e-4bc0-9c52-8b2683397783) --- .../nextjs-15/tests/ai-test.test.ts | 2 +- .../suites/tracing/vercelai/test.ts | 24 +++++++++---------- .../integrations/tracing/vercelai/index.ts | 12 +++++----- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/tests/ai-test.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-15/tests/ai-test.test.ts index 8f08a4a60841..c63716a34fad 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-15/tests/ai-test.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/tests/ai-test.test.ts @@ -21,7 +21,7 @@ test('should create AI spans with correct attributes', async ({ page }) => { // TODO: For now, this is sadly not fully working - the monkey patching of the ai package is not working // because of this, only spans that are manually opted-in at call time will be captured // this may be fixed by https://github.com/vercel/ai/pull/6716 in the future - const aiPipelineSpans = spans.filter(span => span.op === 'ai.pipeline.generate_text'); + const aiPipelineSpans = spans.filter(span => span.op === 'gen_ai.invoke_agent'); const aiGenerateSpans = spans.filter(span => span.op === 'gen_ai.generate_text'); const toolCallSpans = spans.filter(span => span.op === 'gen_ai.execute_tool'); diff --git a/dev-packages/node-integration-tests/suites/tracing/vercelai/test.ts b/dev-packages/node-integration-tests/suites/tracing/vercelai/test.ts index 946e2067212b..a10d602ee93a 100644 --- a/dev-packages/node-integration-tests/suites/tracing/vercelai/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/vercelai/test.ts @@ -26,11 +26,11 @@ describe('Vercel AI integration', () => { 'gen_ai.usage.output_tokens': 20, 'gen_ai.usage.total_tokens': 30, 'operation.name': 'ai.generateText', - 'sentry.op': 'ai.pipeline.generate_text', + 'sentry.op': 'gen_ai.invoke_agent', 'sentry.origin': 'auto.vercelai.otel', }, description: 'generateText', - op: 'ai.pipeline.generate_text', + op: 'gen_ai.invoke_agent', origin: 'auto.vercelai.otel', status: 'ok', }), @@ -83,11 +83,11 @@ describe('Vercel AI integration', () => { 'gen_ai.usage.output_tokens': 20, 'gen_ai.usage.total_tokens': 30, 'operation.name': 'ai.generateText', - 'sentry.op': 'ai.pipeline.generate_text', + 'sentry.op': 'gen_ai.invoke_agent', 'sentry.origin': 'auto.vercelai.otel', }, description: 'generateText', - op: 'ai.pipeline.generate_text', + op: 'gen_ai.invoke_agent', origin: 'auto.vercelai.otel', status: 'ok', }), @@ -140,11 +140,11 @@ describe('Vercel AI integration', () => { 'gen_ai.usage.output_tokens': 25, 'gen_ai.usage.total_tokens': 40, 'operation.name': 'ai.generateText', - 'sentry.op': 'ai.pipeline.generate_text', + 'sentry.op': 'gen_ai.invoke_agent', 'sentry.origin': 'auto.vercelai.otel', }, description: 'generateText', - op: 'ai.pipeline.generate_text', + op: 'gen_ai.invoke_agent', origin: 'auto.vercelai.otel', status: 'ok', }), @@ -220,11 +220,11 @@ describe('Vercel AI integration', () => { 'gen_ai.usage.output_tokens': 20, 'gen_ai.usage.total_tokens': 30, 'operation.name': 'ai.generateText', - 'sentry.op': 'ai.pipeline.generate_text', + 'sentry.op': 'gen_ai.invoke_agent', 'sentry.origin': 'auto.vercelai.otel', }, description: 'generateText', - op: 'ai.pipeline.generate_text', + op: 'gen_ai.invoke_agent', origin: 'auto.vercelai.otel', status: 'ok', }), @@ -280,11 +280,11 @@ describe('Vercel AI integration', () => { 'gen_ai.usage.output_tokens': 20, 'gen_ai.usage.total_tokens': 30, 'operation.name': 'ai.generateText', - 'sentry.op': 'ai.pipeline.generate_text', + 'sentry.op': 'gen_ai.invoke_agent', 'sentry.origin': 'auto.vercelai.otel', }, description: 'generateText', - op: 'ai.pipeline.generate_text', + op: 'gen_ai.invoke_agent', origin: 'auto.vercelai.otel', status: 'ok', }), @@ -341,11 +341,11 @@ describe('Vercel AI integration', () => { 'gen_ai.usage.output_tokens': 25, 'gen_ai.usage.total_tokens': 40, 'operation.name': 'ai.generateText', - 'sentry.op': 'ai.pipeline.generate_text', + 'sentry.op': 'gen_ai.invoke_agent', 'sentry.origin': 'auto.vercelai.otel', }, description: 'generateText', - op: 'ai.pipeline.generate_text', + op: 'gen_ai.invoke_agent', origin: 'auto.vercelai.otel', status: 'ok', }), diff --git a/packages/node/src/integrations/tracing/vercelai/index.ts b/packages/node/src/integrations/tracing/vercelai/index.ts index 9557d62b5e04..8ba6cb5af905 100644 --- a/packages/node/src/integrations/tracing/vercelai/index.ts +++ b/packages/node/src/integrations/tracing/vercelai/index.ts @@ -102,7 +102,7 @@ const _vercelAIIntegration = ((options: VercelAiOptions = {}) => { // Generate Spans if (name === 'ai.generateText') { - span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'ai.pipeline.generate_text'); + span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'gen_ai.invoke_agent'); return; } @@ -113,7 +113,7 @@ const _vercelAIIntegration = ((options: VercelAiOptions = {}) => { } if (name === 'ai.streamText') { - span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'ai.pipeline.stream_text'); + span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'gen_ai.invoke_agent'); return; } @@ -124,7 +124,7 @@ const _vercelAIIntegration = ((options: VercelAiOptions = {}) => { } if (name === 'ai.generateObject') { - span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'ai.pipeline.generate_object'); + span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'gen_ai.invoke_agent'); return; } @@ -135,7 +135,7 @@ const _vercelAIIntegration = ((options: VercelAiOptions = {}) => { } if (name === 'ai.streamObject') { - span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'ai.pipeline.stream_object'); + span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'gen_ai.invoke_agent'); return; } @@ -146,7 +146,7 @@ const _vercelAIIntegration = ((options: VercelAiOptions = {}) => { } if (name === 'ai.embed') { - span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'ai.pipeline.embed'); + span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'gen_ai.invoke_agent'); return; } @@ -157,7 +157,7 @@ const _vercelAIIntegration = ((options: VercelAiOptions = {}) => { } if (name === 'ai.embedMany') { - span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'ai.pipeline.embed_many'); + span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'gen_ai.invoke_agent'); return; } From 4d8581ac3735daf8fedf06fbc2a45f9d8480f461 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Tue, 24 Jun 2025 10:21:34 -0400 Subject: [PATCH 16/22] feat(browser): Add CLS sources to span attributes (#16710) resolves https://github.com/getsentry/sentry-javascript/issues/16707 The session focused on enhancing CLS (Cumulative Layout Shift) spans by adding attributes detailing the elements that caused layout shifts. * In `packages/browser-utils/src/metrics/cls.ts`, the `sendStandaloneClsSpan` function was updated. It now iterates over `LayoutShift` entry sources and adds them as `cls.source.N` attributes to the span, converting DOM nodes to readable CSS selectors using `htmlTreeAsString()`. This aligns standalone CLS spans with the existing implementation for regular pageload spans. * Test expectations in `dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-standalone-spans/test.ts` were updated to assert the presence of these new `cls.source.N` attributes on the captured CLS spans. * `yarn.lock` was updated to reflect changes in dependency resolutions, likely due to package installations during the session. Co-authored-by: Cursor Agent --- .../web-vitals-cls-standalone-spans/test.ts | 3 ++ packages/browser-utils/src/metrics/cls.ts | 8 +++++ yarn.lock | 34 +++++++++---------- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-standalone-spans/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-standalone-spans/test.ts index 26c621b9b227..3db30586c909 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-standalone-spans/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-standalone-spans/test.ts @@ -68,6 +68,7 @@ sentryTest('captures a "GOOD" CLS vital with its source as a standalone span', a transaction: expect.stringContaining('index.html'), 'user_agent.original': expect.stringContaining('Chrome'), 'sentry.pageload.span_id': expect.stringMatching(/[a-f0-9]{16}/), + 'cls.source.1': expect.stringContaining('body > div#content > p'), }, description: expect.stringContaining('body > div#content > p'), exclusive_time: 0, @@ -136,6 +137,7 @@ sentryTest('captures a "MEH" CLS vital with its source as a standalone span', as transaction: expect.stringContaining('index.html'), 'user_agent.original': expect.stringContaining('Chrome'), 'sentry.pageload.span_id': expect.stringMatching(/[a-f0-9]{16}/), + 'cls.source.1': expect.stringContaining('body > div#content > p'), }, description: expect.stringContaining('body > div#content > p'), exclusive_time: 0, @@ -202,6 +204,7 @@ sentryTest('captures a "POOR" CLS vital with its source as a standalone span.', transaction: expect.stringContaining('index.html'), 'user_agent.original': expect.stringContaining('Chrome'), 'sentry.pageload.span_id': expect.stringMatching(/[a-f0-9]{16}/), + 'cls.source.1': expect.stringContaining('body > div#content > p'), }, description: expect.stringContaining('body > div#content > p'), exclusive_time: 0, diff --git a/packages/browser-utils/src/metrics/cls.ts b/packages/browser-utils/src/metrics/cls.ts index 1d35ff53853f..435a1b6f7a1b 100644 --- a/packages/browser-utils/src/metrics/cls.ts +++ b/packages/browser-utils/src/metrics/cls.ts @@ -106,6 +106,14 @@ function sendStandaloneClsSpan(clsValue: number, entry: LayoutShift | undefined, 'sentry.pageload.span_id': pageloadSpanId, }; + // Add CLS sources as span attributes to help with debugging layout shifts + // See: https://developer.mozilla.org/en-US/docs/Web/API/LayoutShift/sources + if (entry?.sources) { + entry.sources.forEach((source, index) => { + attributes[`cls.source.${index + 1}`] = htmlTreeAsString(source.node); + }); + } + const span = startStandaloneWebVitalSpan({ name, transaction: routeName, diff --git a/yarn.lock b/yarn.lock index 6dfa6bc056fc..8c4b670fd328 100644 --- a/yarn.lock +++ b/yarn.lock @@ -27038,7 +27038,7 @@ string-template@~0.2.1: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: +string-width@4.2.3, "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -27148,6 +27148,13 @@ stringify-object@^3.2.1: dependencies: ansi-regex "^5.0.1" +strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" @@ -27169,13 +27176,6 @@ strip-ansi@^5.1.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1, strip-ansi@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -27311,7 +27311,7 @@ stylus@0.59.0, stylus@^0.59.0: sax "~1.2.4" source-map "^0.7.3" -sucrase@^3.27.0, sucrase@^3.35.0: +sucrase@^3.27.0, sucrase@^3.35.0, sucrase@getsentry/sucrase#es2020-polyfills: version "3.36.0" resolved "https://codeload.github.com/getsentry/sucrase/tar.gz/fd682f6129e507c00bb4e6319cc5d6b767e36061" dependencies: @@ -29955,19 +29955,19 @@ wrangler@^3.67.1: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^6.0.1: - version "6.2.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" - integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== +wrap-ansi@7.0.0, wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== dependencies: ansi-styles "^4.0.0" string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== +wrap-ansi@^6.0.1: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== dependencies: ansi-styles "^4.0.0" string-width "^4.1.0" From 85c10ab9a9398adb2546173b0f94b64f12480c4a Mon Sep 17 00:00:00 2001 From: Onur Temizkan Date: Tue, 24 Jun 2025 16:26:28 +0100 Subject: [PATCH 17/22] fix(tests): Set `vite` versions in Remix e2e tests. (#16720) This will fix the [failing e2e tests](https://github.com/getsentry/sentry-javascript/actions/runs/15852381250) on CI --- .../test-applications/create-remix-app-express/package.json | 1 + .../test-applications/create-remix-app-v2/package.json | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-express/package.json b/dev-packages/e2e-tests/test-applications/create-remix-app-express/package.json index 60cdf5b1f635..1050b86850c3 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app-express/package.json +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-express/package.json @@ -48,6 +48,7 @@ "eslint-plugin-react-hooks": "^4.6.0", "tsx": "4.7.2", "typescript": "^5.1.6", + "vite": "^5.4.11", "vite-tsconfig-paths": "^4.2.1" }, "volta": { diff --git a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/package.json b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/package.json index 1f3ca2437454..4a5070b4fe98 100644 --- a/dev-packages/e2e-tests/test-applications/create-remix-app-v2/package.json +++ b/dev-packages/e2e-tests/test-applications/create-remix-app-v2/package.json @@ -30,7 +30,8 @@ "@types/react-dom": "^18.2.34", "@types/prop-types": "15.7.7", "eslint": "^8.38.0", - "typescript": "^5.1.6" + "typescript": "^5.1.6", + "vite": "^5.4.11" }, "resolutions": { "@types/react": "18.2.22" From 63b255f3f2eee5d5f07c4122b62ba4ac4844d428 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Tue, 24 Jun 2025 20:21:38 +0200 Subject: [PATCH 18/22] feat(node): Add `@sentry/node-native` package (#16722) - Ref https://github.com/getsentry/sentry-javascript/pull/16709#pullrequestreview-2954523497 Adds an empty `@sentry/node-native` package --- .github/ISSUE_TEMPLATE/bug.yml | 1 + .../e2e-tests/verdaccio-config/config.yaml | 6 ++ package.json | 1 + packages/node-native/.eslintrc.js | 11 +++ packages/node-native/LICENSE | 21 +++++ packages/node-native/README.md | 21 +++++ packages/node-native/package.json | 78 +++++++++++++++++++ packages/node-native/rollup.npm.config.mjs | 15 ++++ packages/node-native/src/index.ts | 1 + packages/node-native/tsconfig.json | 10 +++ packages/node-native/tsconfig.test.json | 12 +++ packages/node-native/tsconfig.types.json | 11 +++ packages/node-native/vite.config.ts | 8 ++ 13 files changed, 196 insertions(+) create mode 100644 packages/node-native/.eslintrc.js create mode 100644 packages/node-native/LICENSE create mode 100644 packages/node-native/README.md create mode 100644 packages/node-native/package.json create mode 100644 packages/node-native/rollup.npm.config.mjs create mode 100644 packages/node-native/src/index.ts create mode 100644 packages/node-native/tsconfig.json create mode 100644 packages/node-native/tsconfig.test.json create mode 100644 packages/node-native/tsconfig.types.json create mode 100644 packages/node-native/vite.config.ts diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index ba0bdd8df4c3..38da90a027b8 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -37,6 +37,7 @@ body: - '@sentry/node - koa' - '@sentry/node - hapi' - '@sentry/node - connect' + - '@sentry/node-native' - '@sentry/angular' - '@sentry/astro' - '@sentry/aws-serverless' diff --git a/dev-packages/e2e-tests/verdaccio-config/config.yaml b/dev-packages/e2e-tests/verdaccio-config/config.yaml index c5400118d12c..d96c3cc6905e 100644 --- a/dev-packages/e2e-tests/verdaccio-config/config.yaml +++ b/dev-packages/e2e-tests/verdaccio-config/config.yaml @@ -104,6 +104,12 @@ packages: unpublish: $all # proxy: npmjs # Don't proxy for E2E tests! + '@sentry/node-native': + access: $all + publish: $all + unpublish: $all + # proxy: npmjs # Don't proxy for E2E tests! + '@sentry/opentelemetry': access: $all publish: $all diff --git a/package.json b/package.json index 75bd3b8f8380..fe2058e2cb09 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "packages/nestjs", "packages/nextjs", "packages/node", + "packages/node-native", "packages/nuxt", "packages/opentelemetry", "packages/pino-transport", diff --git a/packages/node-native/.eslintrc.js b/packages/node-native/.eslintrc.js new file mode 100644 index 000000000000..ab3515d9ad37 --- /dev/null +++ b/packages/node-native/.eslintrc.js @@ -0,0 +1,11 @@ +module.exports = { + env: { + node: true, + }, + extends: ['../../.eslintrc.js'], + + ignorePatterns: ['build/**/*', 'examples/**/*', 'vitest.config.ts'], + rules: { + '@sentry-internal/sdk/no-class-field-initializers': 'off', + }, +}; diff --git a/packages/node-native/LICENSE b/packages/node-native/LICENSE new file mode 100644 index 000000000000..0da96cd2f885 --- /dev/null +++ b/packages/node-native/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Functional Software, Inc. dba Sentry + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/node-native/README.md b/packages/node-native/README.md new file mode 100644 index 000000000000..00779a75666e --- /dev/null +++ b/packages/node-native/README.md @@ -0,0 +1,21 @@ +

+ + Sentry + +

+ +# Native Tools for the Official Sentry Node.js SDK + +[![npm version](https://img.shields.io/npm/v/@sentry/node-native.svg)](https://www.npmjs.com/package/@sentry/node-native) +[![npm dm](https://img.shields.io/npm/dm/@sentry/node-native.svg)](https://www.npmjs.com/package/@sentry/node-native) +[![npm dt](https://img.shields.io/npm/dt/@sentry/node-native.svg)](https://www.npmjs.com/package/@sentry/node-native) + +## Installation + +```bash +# Using yarn +yarn add @sentry/node @sentry/node-native + +# Using npm +npm install --save @sentry/node @sentry/node-native +``` diff --git a/packages/node-native/package.json b/packages/node-native/package.json new file mode 100644 index 000000000000..5077a2019502 --- /dev/null +++ b/packages/node-native/package.json @@ -0,0 +1,78 @@ +{ + "name": "@sentry/node-native", + "version": "9.27.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", + "author": "Sentry", + "license": "MIT", + "main": "build/cjs/index.js", + "module": "build/esm/index.js", + "types": "build/types/index.d.ts", + "exports": { + "./package.json": "./package.json", + ".": { + "import": { + "types": "./build/types/index.d.ts", + "default": "./build/esm/index.js" + }, + "require": { + "types": "./build/types/index.d.ts", + "default": "./build/cjs/index.js" + } + } + }, + "typesVersions": { + "<5.0": { + "build/types/index.d.ts": [ + "build/types-ts3.8/index.d.ts" + ] + } + }, + "engines": { + "node": ">=18" + }, + "publishConfig": { + "access": "public" + }, + "files": [ + "/build", + "package.json" + ], + "scripts": { + "clean": "rm -rf build", + "lint": "eslint . --format stylish", + "lint:es-compatibility": "es-check es2022 ./build/cjs/*.js && es-check es2022 ./build/esm/*.js --module", + "fix": "eslint . --format stylish --fix", + "build": "yarn build:types && yarn build:transpile", + "build:transpile": "yarn rollup -c rollup.npm.config.mjs", + "build:types:downlevel": "yarn downlevel-dts build/types build/types-ts3.8 --to ts3.8", + "build:types": "tsc -p tsconfig.types.json && yarn build:types:downlevel", + "build:types:watch": "tsc -p tsconfig.types.json --watch", + "build:dev": "yarn clean && yarn build", + "build:transpile:watch": "rollup -c rollup.npm.config.mjs --watch", + "build:watch": "run-p build:transpile:watch build:types:watch", + "build:tarball": "npm pack" + }, + "dependencies": { + "@sentry/core": "9.31.0", + "@sentry/node": "9.31.0" + }, + "devDependencies": { + "@types/node": "^18.19.1" + }, + "volta": { + "extends": "../../package.json" + }, + "sideEffects": false, + "nx": { + "targets": { + "build:transpile": { + "dependsOn": [ + "^build:transpile", + "^build:types" + ] + } + } + } +} diff --git a/packages/node-native/rollup.npm.config.mjs b/packages/node-native/rollup.npm.config.mjs new file mode 100644 index 000000000000..b58b8e8ac027 --- /dev/null +++ b/packages/node-native/rollup.npm.config.mjs @@ -0,0 +1,15 @@ +import { makeBaseNPMConfig, makeNPMConfigVariants } from '@sentry-internal/rollup-utils'; + +export default makeNPMConfigVariants( + makeBaseNPMConfig({ + entrypoints: ['src/index.ts'], + packageSpecificConfig: { + output: { + dir: 'build', + // set exports to 'named' or 'auto' so that rollup doesn't warn + exports: 'named', + preserveModules: true, + }, + }, + }), +); diff --git a/packages/node-native/src/index.ts b/packages/node-native/src/index.ts new file mode 100644 index 000000000000..cb0ff5c3b541 --- /dev/null +++ b/packages/node-native/src/index.ts @@ -0,0 +1 @@ +export {}; diff --git a/packages/node-native/tsconfig.json b/packages/node-native/tsconfig.json new file mode 100644 index 000000000000..29acbf3f36e9 --- /dev/null +++ b/packages/node-native/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "module": "esnext", + "lib": ["es2018"], + "outDir": "build", + "types": ["node"] + }, + "include": ["src/**/*"] +} diff --git a/packages/node-native/tsconfig.test.json b/packages/node-native/tsconfig.test.json new file mode 100644 index 000000000000..c401c76a5305 --- /dev/null +++ b/packages/node-native/tsconfig.test.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + + "include": ["test/**/*", "src/**/*.d.ts", "vite.config.ts"], + + "compilerOptions": { + // should include all types from `./tsconfig.json` plus types for all test frameworks used + "types": ["node"] + + // other package-specific, test-specific options + } +} diff --git a/packages/node-native/tsconfig.types.json b/packages/node-native/tsconfig.types.json new file mode 100644 index 000000000000..7a01535e9a4c --- /dev/null +++ b/packages/node-native/tsconfig.types.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "build/types", + "types": ["node"] + }, + "files": ["src/index.ts"] +} diff --git a/packages/node-native/vite.config.ts b/packages/node-native/vite.config.ts new file mode 100644 index 000000000000..f18ec92095bc --- /dev/null +++ b/packages/node-native/vite.config.ts @@ -0,0 +1,8 @@ +import baseConfig from '../../vite/vite.config'; + +export default { + ...baseConfig, + test: { + ...baseConfig.test, + }, +}; From 3c012d0f0b63594c66901003f6df27b784aed7f9 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Tue, 24 Jun 2025 15:43:27 -0400 Subject: [PATCH 19/22] feat(pino-transport): Add functionality to send logs to sentry (#16667) ref https://github.com/getsentry/sentry-javascript/issues/15952 resolves https://github.com/getsentry/sentry-javascript/issues/16622 This PR adds the ability to send logs to sentry via a pino transport. ## Usage ```js import pino from 'pino'; const logger = pino({ transport: { target: '@sentry/pino-transport', options: { // Optional: filter which log levels to send to Sentry logLevels: ['error', 'fatal'], // defaults to all levels }, }, }); // Now your logs will be sent to Sentry logger.info('This is an info message'); logger.error('This is an error message'); ``` ### Options #### `logLevels` **Type:** `Array<'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal'>` **Default:** `['trace', 'debug', 'info', 'warn', 'error', 'fatal']` (all levels) Use this option to filter which log levels should be sent to Sentry. ```javascript const transport = pino.transport({ target: '@sentry/pino-transport', options: { logLevels: ['warn', 'error', 'fatal'], // Only send warnings and above }, }); ``` ## Next I need to write some integration tests - this is being tracked by https://github.com/getsentry/sentry-javascript/issues/16624 --------- Co-authored-by: Charly Gomez --- README.md | 2 - packages/pino-transport/README.md | 265 +++++++- packages/pino-transport/package.json | 4 +- packages/pino-transport/src/debug-build.ts | 8 + packages/pino-transport/src/index.ts | 246 ++++++- packages/pino-transport/test/index.test.ts | 708 ++++++++++++++++++++- packages/pino-transport/vite.config.ts | 3 + yarn.lock | 5 + 8 files changed, 1218 insertions(+), 23 deletions(-) create mode 100644 packages/pino-transport/src/debug-build.ts diff --git a/README.md b/README.md index f92fd89ec716..8b22dafb0c63 100644 --- a/README.md +++ b/README.md @@ -112,8 +112,6 @@ below: Provides the integration for Session Replay. - [`@sentry/core`](https://github.com/getsentry/sentry-javascript/tree/master/packages/core): The base for all JavaScript SDKs with interfaces, type definitions and base classes. -- [`@sentry/pino-transport`](https://github.com/getsentry/sentry-javascript/tree/master/packages/pino-transport): Pino - transport for automatically sending log messages to Sentry. ## Bug Bounty Program diff --git a/packages/pino-transport/README.md b/packages/pino-transport/README.md index 45480f802cb1..0bb6aeed81ed 100644 --- a/packages/pino-transport/README.md +++ b/packages/pino-transport/README.md @@ -1,30 +1,267 @@ -# @sentry/pino-transport +

+ + Sentry + +

-[![npm version](https://img.shields.io/npm/v/@sentry/pino-transport.svg)](https://www.npmjs.com/package/@sentry/pino-transport) -[![npm dm](https://img.shields.io/npm/dm/@sentry/pino-transport.svg)](https://www.npmjs.com/package/@sentry/pino-transport) -[![npm dt](https://img.shields.io/npm/dt/@sentry/pino-transport.svg)](https://www.npmjs.com/package/@sentry/pino-transport) +# Official Sentry Pino Transport -**This package is currently in alpha. Breaking changes may still occur.** +[![npm version](https://img.shields.io/npm/v/@sentry/solid.svg)](https://www.npmjs.com/package/@sentry/solid) +[![npm dm](https://img.shields.io/npm/dm/@sentry/solid.svg)](https://www.npmjs.com/package/@sentry/solid) +[![npm dt](https://img.shields.io/npm/dt/@sentry/solid.svg)](https://www.npmjs.com/package/@sentry/solid) -A Pino transport for integrating [Pino](https://github.com/pinojs/pino) logging with [Sentry](https://sentry.io). This transport automatically captures log messages as Sentry events and breadcrumbs, making it easy to monitor your application's logs in Sentry. +**WARNING**: This transport is in a **pre-release alpha**. The API is unstable and may change at any time. + +A Pino transport for sending logs to Sentry using the Sentry JavaScript SDK. + +This transport forwards Pino logs to Sentry, allowing you to view and analyze your application logs alongside your errors and performance data in Sentry. ## Installation ```bash -npm install @sentry/node @sentry/pino-transport +npm install @sentry/pino-transport pino # or -yarn add @sentry/node @sentry/pino-transport +yarn add @sentry/pino-transport pino +# or +pnpm add @sentry/pino-transport pino ``` -## Usage +## Requirements + +- Node.js 18+ +- Pino v8 or v9 +- `@sentry/node` SDK with `_experiments.enableLogs: true` -TODO: Add usage instructions +## Setup -## Requirements +First, make sure Sentry is initialized with logging enabled: + +```javascript +import * as Sentry from '@sentry/node'; + +Sentry.init({ + dsn: 'YOUR_DSN', + _experiments: { + enableLogs: true, + }, +}); +``` + +Then create a Pino logger with the Sentry transport: + +```javascript +import pino from 'pino'; + +const logger = pino({ + transport: { + target: '@sentry/pino-transport', + options: { + // Optional: filter which log levels to send to Sentry + levels: ['error', 'fatal'], // defaults to all levels + }, + }, +}); + +// Now your logs will be sent to Sentry +logger.info('This is an info message'); +logger.error('This is an error message'); +``` + +## Configuration Options + +The transport accepts the following options: + +### `logLevels` + +**Type:** `Array<'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal'>` + +**Default:** `['trace', 'debug', 'info', 'warn', 'error', 'fatal']` (all log levels) + +Use this option to filter which log severity levels should be sent to Sentry. + +```javascript +const transport = pino.transport({ + target: '@sentry/pino-transport', + options: { + logLevels: ['warn', 'error', 'fatal'], // Only send warnings and above + }, +}); +``` + +## Log Level Mapping + +Pino log levels are automatically mapped to Sentry log severity levels: + +| Pino Level | Pino Numeric | Sentry Level | +| ---------- | ------------ | ------------ | +| trace | 10 | trace | +| debug | 20 | debug | +| info | 30 | info | +| warn | 40 | warn | +| error | 50 | error | +| fatal | 60 | fatal | + +### Custom Levels Support + +Custom numeric levels are mapped to Sentry levels using ranges, so levels like `11`, `23`, or `42` will map correctly: + +- `0-19` → `trace` +- `20-29` → `debug` +- `30-39` → `info` +- `40-49` → `warn` +- `50-59` → `error` +- `60+` → `fatal` + +```javascript +import pino from 'pino'; + +const logger = pino({ + customLevels: { + critical: 55, // Maps to 'fatal' (55+ range) + notice: 35, // Maps to 'warn' (35-44 range) + verbose: 11, // Maps to 'trace' (0-14 range) + }, + transport: { + target: '@sentry/pino-transport', + }, +}); + +logger.critical('Critical issue occurred'); // → Sent as 'fatal' to Sentry +logger.notice('Important notice'); // → Sent as 'warn' to Sentry +logger.verbose('Detailed information'); // → Sent as 'trace' to Sentry +``` + +#### Custom Level Attributes + +When using custom string levels, the original level name is preserved as `sentry.pino.level` attribute for better traceability: + +```javascript +// Log entry in Sentry will include: +// { +// level: 'warn', // Mapped Sentry level +// message: 'Audit event', +// attributes: { +// 'sentry.pino.level': 'audit', // Original custom level name +// 'sentry.origin': 'auto.logging.pino', +// // ... other log attributes +// } +// } +``` + +### Custom Message Key + +The transport respects Pino's `messageKey` configuration: + +```javascript +const logger = pino({ + messageKey: 'message', // Use 'message' instead of default 'msg' + transport: { + target: '@sentry/pino-transport', + }, +}); + +logger.info({ message: 'Hello world' }); // Works correctly with custom messageKey +``` + +### Nested Key Support + +The transport automatically supports Pino's `nestedKey` configuration, which is used to avoid property conflicts by nesting logged objects under a specific key. When `nestedKey` is configured, the transport flattens these nested properties using dot notation for better searchability in Sentry. + +```javascript +const logger = pino({ + nestedKey: 'payload', // Nest logged objects under 'payload' key + transport: { + target: '@sentry/pino-transport', + }, +}); + +const conflictingObject = { + level: 'hi', // Conflicts with Pino's level + time: 'never', // Conflicts with Pino's time + foo: 'bar', + userId: 123, +}; + +logger.info(conflictingObject); + +// Without nestedKey, this would cause property conflicts +// With nestedKey, Pino creates: { level: 30, time: 1234567890, payload: conflictingObject } +// The transport flattens it to: +// { +// level: 'info', +// message: undefined, +// attributes: { +// 'payload.level': 'hi', // Flattened nested properties +// 'payload.time': 'never', +// 'payload.foo': 'bar', +// 'payload.userId': 123, +// 'sentry.origin': 'auto.logging.pino', +// } +// } +``` + +This flattening ensures that no property conflicts occur between logged objects and Pino's internal properties. + +## Usage Examples + +### Basic Logging + +```javascript +import pino from 'pino'; + +const logger = pino({ + transport: { + target: '@sentry/pino-transport', + }, +}); + +logger.trace('Starting application'); +logger.debug('Debug information', { userId: 123 }); +logger.info('User logged in', { userId: 123, username: 'john_doe' }); +logger.warn('Deprecated API used', { endpoint: '/old-api' }); +logger.error('Database connection failed', { error: 'Connection timeout' }); +logger.fatal('Application crashed', { reason: 'Out of memory' }); +``` + +### Multiple Transports + +```javascript +import pino from 'pino'; + +const logger = pino({ + transport: { + targets: [ + { + target: 'pino-pretty', + options: { colorize: true }, + level: 'debug', + }, + { + target: '@sentry/pino-transport', + options: { + logLevels: ['warn', 'error', 'fatal'], + }, + level: 'warn', + }, + ], + }, +}); +``` + +## Troubleshooting + +### Logs not appearing in Sentry + +1. Ensure `_experiments.enableLogs: true` is set in your Sentry configuration. +2. Check that your DSN is correct and the SDK is properly initialized. +3. Verify the log level is included in the `levels` configuration. +4. Check your Sentry organization stats page to see if logs are being received by Sentry. + +## Related Documentation -- Node.js 18 or higher -- Pino 8.0.0 or higher -- @sentry/node must be configured in your application +- [Sentry Logs Documentation](https://docs.sentry.io/platforms/javascript/guides/node/logs/) +- [Pino Documentation](https://getpino.io/) +- [Pino Transports](https://getpino.io/#/docs/transports) ## License diff --git a/packages/pino-transport/package.json b/packages/pino-transport/package.json index be2445ce2afd..59b1153d6321 100644 --- a/packages/pino-transport/package.json +++ b/packages/pino-transport/package.json @@ -39,7 +39,9 @@ "access": "public" }, "dependencies": { - "@sentry/core": "9.31.0" + "@sentry/node": "9.31.0", + "@sentry/core": "9.31.0", + "pino-abstract-transport": "^2.0.0" }, "peerDependencies": { "pino": "^8.0.0 || ^9.0.0" diff --git a/packages/pino-transport/src/debug-build.ts b/packages/pino-transport/src/debug-build.ts new file mode 100644 index 000000000000..60aa50940582 --- /dev/null +++ b/packages/pino-transport/src/debug-build.ts @@ -0,0 +1,8 @@ +declare const __DEBUG_BUILD__: boolean; + +/** + * This serves as a build time flag that will be true by default, but false in non-debug builds or if users replace `__SENTRY_DEBUG__` in their generated code. + * + * ATTENTION: This constant must never cross package boundaries (i.e. be exported) to guarantee that it can be used for tree shaking. + */ +export const DEBUG_BUILD = __DEBUG_BUILD__; diff --git a/packages/pino-transport/src/index.ts b/packages/pino-transport/src/index.ts index 849aa99b4f7c..34cfe9ecde86 100644 --- a/packages/pino-transport/src/index.ts +++ b/packages/pino-transport/src/index.ts @@ -1,2 +1,244 @@ -// TODO: Implement this -export {}; +import type { LogSeverityLevel } from '@sentry/core'; +import { _INTERNAL_captureLog, isPrimitive, logger, normalize } from '@sentry/core'; +import type buildType from 'pino-abstract-transport'; +import * as pinoAbstractTransport from 'pino-abstract-transport'; +import { DEBUG_BUILD } from './debug-build'; + +// Handle both CommonJS and ES module exports +// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any +const build = (pinoAbstractTransport as any).default || pinoAbstractTransport; + +/** + * The default log levels that will be captured by the Sentry Pino transport. + */ +const DEFAULT_CAPTURED_LEVELS: Array = ['trace', 'debug', 'info', 'warn', 'error', 'fatal']; + +/** + * Options for the Sentry Pino transport. + */ +export interface SentryPinoTransportOptions { + /** + * Use this option to filter which levels should be captured as logs. + * By default, all levels are captured as logs. + * + * @example + * ```ts + * const logger = pino({ + * transport: { + * target: '@sentry/pino-transport', + * options: { + * logLevels: ['error', 'warn'], // Only capture error and warn logs + * }, + * }, + * }); + * ``` + */ + logLevels?: Array; +} + +/** + * Pino source configuration passed to the transport. + * This interface represents the configuration options that Pino provides to transports. + */ +interface PinoSourceConfig { + /** + * Custom levels configuration from Pino. + * Contains the mapping of custom level names to numeric values. + * + * @default undefined + * @example { values: { critical: 55, notice: 35 } } + */ + levels?: unknown; + + /** + * The property name used for the log message. + * Pino allows customizing which property contains the main log message. + * + * @default 'msg' + * @example 'message' when configured with messageKey: 'message' + * @see https://getpino.io/#/docs/api?id=messagekey-string + */ + messageKey?: string; + + /** + * The property name used for error objects. + * Pino allows customizing which property contains error information. + * + * @default 'err' + * @example 'error' when configured with errorKey: 'error' + * @see https://getpino.io/#/docs/api?id=errorkey-string + */ + errorKey?: string; + + /** + * The property name used to nest logged objects to avoid conflicts. + * When set, Pino nests all logged objects under this key to prevent + * conflicts with Pino's internal properties (level, time, pid, etc.). + * The transport flattens these nested properties using dot notation. + * + * @default undefined (no nesting) + * @example 'payload' - objects logged will be nested under { payload: {...} } + * @see https://getpino.io/#/docs/api?id=nestedkey-string + */ + nestedKey?: string; +} + +/** + * Creates a new Sentry Pino transport that forwards logs to Sentry. Requires `_experiments.enableLogs` to be enabled. + * + * Supports Pino v8 and v9. + * + * @param options - Options for the transport. + * @returns A Pino transport that forwards logs to Sentry. + * + * @experimental This method will experience breaking changes. This is not yet part of + * the stable Sentry SDK API and can be changed or removed without warning. + */ +export function createSentryPinoTransport(options?: SentryPinoTransportOptions): ReturnType { + DEBUG_BUILD && logger.log('Initializing Sentry Pino transport'); + const capturedLogLevels = new Set(options?.logLevels ?? DEFAULT_CAPTURED_LEVELS); + + return build( + async function (source: AsyncIterable & PinoSourceConfig) { + for await (const log of source) { + try { + if (!isObject(log)) { + continue; + } + + // Use Pino's messageKey if available, fallback to 'msg' + const messageKey = source.messageKey || 'msg'; + const message = log[messageKey]; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { [messageKey]: _, level, time, ...attributes } = log; + + // Handle nestedKey flattening if configured + if (source.nestedKey && attributes[source.nestedKey] && isObject(attributes[source.nestedKey])) { + const nestedObject = attributes[source.nestedKey] as Record; + // Remove the nested object and flatten its properties + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete attributes[source.nestedKey]; + + // Flatten nested properties with dot notation + for (const [key, value] of Object.entries(nestedObject)) { + attributes[`${source.nestedKey}.${key}`] = value; + } + } + + const logSeverityLevel = mapPinoLevelToSentryLevel(log.level, source.levels); + + if (capturedLogLevels.has(logSeverityLevel)) { + const logAttributes: Record = { + ...attributes, + 'sentry.origin': 'auto.logging.pino', + }; + + // Attach custom level as an attribute if it's a string (custom level) + if (typeof log.level === 'string') { + logAttributes['sentry.pino.level'] = log.level; + } + + _INTERNAL_captureLog({ + level: logSeverityLevel, + message: formatMessage(message), + attributes: logAttributes, + }); + } + } catch { + // Silently ignore errors to prevent breaking the logging pipeline + } + } + }, + { + expectPinoConfig: true, + }, + ); +} + +function formatMessage(message: unknown): string { + if (message === undefined) { + return ''; + } + + if (isPrimitive(message)) { + return String(message); + } + return JSON.stringify(normalize(message)); +} + +/** + * Maps a Pino log level (numeric or custom string) to a Sentry log severity level. + * + * Handles both standard and custom levels, including when `useOnlyCustomLevels` is enabled. + * Uses range-based mapping for numeric levels to handle custom values (e.g., 11 -> trace). + */ +function mapPinoLevelToSentryLevel(level: unknown, levelsConfig?: unknown): LogSeverityLevel { + // Handle numeric levels + if (typeof level === 'number') { + return mapNumericLevelToSentryLevel(level); + } + + // Handle custom string levels + if ( + typeof level === 'string' && + isObject(levelsConfig) && + 'values' in levelsConfig && + isObject(levelsConfig.values) + ) { + // Map custom string levels to numeric then to Sentry levels + const numericLevel = levelsConfig.values[level]; + if (typeof numericLevel === 'number') { + return mapNumericLevelToSentryLevel(numericLevel); + } + } + + // Default fallback + return 'info'; +} + +/** + * Maps a numeric level to the closest Sentry severity level using range-based mapping. + * Handles both standard Pino levels and custom numeric levels. + * + * - `0-19` -> `trace` + * - `20-29` -> `debug` + * - `30-39` -> `info` + * - `40-49` -> `warn` + * - `50-59` -> `error` + * - `60+` -> `fatal` + * + * @see https://github.com/pinojs/pino/blob/116b1b17935630b97222fbfd1c053d199d18ca4b/lib/constants.js#L6-L13 + */ +function mapNumericLevelToSentryLevel(numericLevel: number): LogSeverityLevel { + // 0-19 -> trace + if (numericLevel < 20) { + return 'trace'; + } + // 20-29 -> debug + if (numericLevel < 30) { + return 'debug'; + } + // 30-39 -> info + if (numericLevel < 40) { + return 'info'; + } + // 40-49 -> warn + if (numericLevel < 50) { + return 'warn'; + } + // 50-59 -> error + if (numericLevel < 60) { + return 'error'; + } + // 60+ -> fatal + return 'fatal'; +} + +/** + * Type guard to check if a value is an object. + */ +function isObject(value: unknown): value is Record { + return typeof value === 'object' && value != null; +} + +export default createSentryPinoTransport; diff --git a/packages/pino-transport/test/index.test.ts b/packages/pino-transport/test/index.test.ts index 9329d9cbaede..a93d56f340cd 100644 --- a/packages/pino-transport/test/index.test.ts +++ b/packages/pino-transport/test/index.test.ts @@ -1,8 +1,708 @@ -import { describe, expect, it } from 'vitest'; -import * as index from '../src'; +import { _INTERNAL_captureLog } from '@sentry/core'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { createSentryPinoTransport } from '../src'; + +// Mock the _INTERNAL_captureLog function +vi.mock('@sentry/core', async actual => { + const actualModule = (await actual()) as any; + return { + ...actualModule, + _INTERNAL_captureLog: vi.fn(), + }; +}); + +const mockCaptureLog = vi.mocked(_INTERNAL_captureLog); + +describe('createSentryPinoTransport', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + afterEach(() => { + vi.resetAllMocks(); + }); -describe('pino-transport', () => { it('should be defined', () => { - expect(index).toBeDefined(); + expect(createSentryPinoTransport).toBeDefined(); + }); + + it('should create a transport that forwards logs to Sentry', async () => { + const transport = await createSentryPinoTransport(); + expect(transport).toBeDefined(); + expect(typeof transport.write).toBe('function'); + }); + + it('should capture logs with correct level mapping', async () => { + const transport = await createSentryPinoTransport(); + + // Simulate a Pino log entry + const testLog = { + level: 30, // info level in Pino + msg: 'Test message', + time: Date.now(), + hostname: 'test-host', + pid: 12345, + }; + + // Write the log to the transport + transport.write(`${JSON.stringify(testLog)}\n`); + + // Give it a moment to process + await new Promise(resolve => setTimeout(resolve, 10)); + + expect(mockCaptureLog).toHaveBeenCalledWith({ + level: 'info', + message: 'Test message', + attributes: expect.objectContaining({ + hostname: 'test-host', + pid: 12345, + 'sentry.origin': 'auto.logging.pino', + }), + }); + }); + + it('should map all Pino log levels correctly', async () => { + const transport = await createSentryPinoTransport(); + + const testCases = [ + { pinoLevel: 10, expectedSentryLevel: 'trace' }, + { pinoLevel: 20, expectedSentryLevel: 'debug' }, + { pinoLevel: 30, expectedSentryLevel: 'info' }, + { pinoLevel: 40, expectedSentryLevel: 'warn' }, + { pinoLevel: 50, expectedSentryLevel: 'error' }, + { pinoLevel: 60, expectedSentryLevel: 'fatal' }, + ]; + + for (const { pinoLevel, expectedSentryLevel } of testCases) { + const testLog = { + level: pinoLevel, + msg: `Test ${expectedSentryLevel} message`, + time: Date.now(), + }; + + transport.write(`${JSON.stringify(testLog)}\n`); + } + + // Give it a moment to process all logs + await new Promise(resolve => setTimeout(resolve, 10)); + + expect(mockCaptureLog).toHaveBeenCalledTimes(6); + + testCases.forEach(({ expectedSentryLevel }, index) => { + expect(mockCaptureLog).toHaveBeenNthCalledWith(index + 1, { + level: expectedSentryLevel, + message: `Test ${expectedSentryLevel} message`, + attributes: expect.objectContaining({ + 'sentry.origin': 'auto.logging.pino', + }), + }); + }); + }); + + it('should respect level filtering', async () => { + const transport = await createSentryPinoTransport({ + logLevels: ['error', 'fatal'], + }); + + const testLogs = [ + { level: 30, msg: 'Info message' }, // Should be filtered out + { level: 50, msg: 'Error message' }, // Should be captured + { level: 60, msg: 'Fatal message' }, // Should be captured + ]; + + for (const testLog of testLogs) { + transport.write(`${JSON.stringify(testLog)}\n`); + } + + // Give it a moment to process all logs + await new Promise(resolve => setTimeout(resolve, 10)); + + expect(mockCaptureLog).toHaveBeenCalledTimes(2); + expect(mockCaptureLog).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + level: 'error', + message: 'Error message', + }), + ); + expect(mockCaptureLog).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + level: 'fatal', + message: 'Fatal message', + }), + ); + }); + + it('should handle unknown levels gracefully', async () => { + const transport = await createSentryPinoTransport(); + + const testLog = { + level: 999, // Unknown level + msg: 'Unknown level message', + time: Date.now(), + }; + + transport.write(`${JSON.stringify(testLog)}\n`); + + // Give it a moment to process + await new Promise(resolve => setTimeout(resolve, 10)); + + expect(mockCaptureLog).toHaveBeenCalledWith({ + level: 'fatal', // 999 maps to fatal (55+ range) + message: 'Unknown level message', + attributes: expect.objectContaining({ + 'sentry.origin': 'auto.logging.pino', + }), + }); + }); + + it('should handle non-numeric levels gracefully', async () => { + const transport = await createSentryPinoTransport(); + + const testLog = { + level: 'invalid', // Non-numeric level + msg: 'Invalid level message', + time: Date.now(), + }; + + transport.write(`${JSON.stringify(testLog)}\n`); + + // Give it a moment to process + await new Promise(resolve => setTimeout(resolve, 10)); + + expect(mockCaptureLog).toHaveBeenCalledWith({ + level: 'info', // Default fallback + message: 'Invalid level message', + attributes: expect.objectContaining({ + 'sentry.origin': 'auto.logging.pino', + 'sentry.pino.level': 'invalid', + }), + }); + }); + + it('should handle malformed JSON gracefully', async () => { + const transport = await createSentryPinoTransport(); + + // Write invalid JSON + transport.write('{ invalid json \n'); + + // Give it a moment to process + await new Promise(resolve => setTimeout(resolve, 10)); + + // Should not crash and should not call captureLog + expect(mockCaptureLog).not.toHaveBeenCalled(); + }); + + it('should handle non-object logs gracefully', async () => { + const transport = await createSentryPinoTransport(); + + // Write a string instead of an object + transport.write('"just a string"\n'); + + // Give it a moment to process + await new Promise(resolve => setTimeout(resolve, 10)); + + // pino-abstract-transport parses JSON, so this actually becomes an object + // The transport should handle it gracefully by logging it + expect(mockCaptureLog).toHaveBeenCalledWith({ + level: 'info', // Default fallback since no level provided + message: '', // Empty string for undefined message + attributes: expect.objectContaining({ + 'sentry.origin': 'auto.logging.pino', + }), + }); + }); + + it('should handle string levels gracefully when no custom levels config is available', async () => { + const transport = await createSentryPinoTransport(); + + const testLog = { + level: 'custom', // String level without custom levels config + msg: 'Custom string level message', + time: Date.now(), + }; + + transport.write(`${JSON.stringify(testLog)}\n`); + + await new Promise(resolve => setTimeout(resolve, 10)); + + expect(mockCaptureLog).toHaveBeenCalledWith({ + level: 'info', // Should fallback to info for unknown string levels + message: 'Custom string level message', + attributes: expect.objectContaining({ + 'sentry.origin': 'auto.logging.pino', + 'sentry.pino.level': 'custom', + }), + }); + }); + + it('should attach custom level name as attribute for string levels', async () => { + const transport = await createSentryPinoTransport(); + + const testLog = { + level: 'critical', // Custom string level + msg: 'Critical level message', + time: Date.now(), + userId: 123, + }; + + transport.write(`${JSON.stringify(testLog)}\n`); + + await new Promise(resolve => setTimeout(resolve, 10)); + + expect(mockCaptureLog).toHaveBeenCalledWith({ + level: 'info', // Mapped level + message: 'Critical level message', + attributes: expect.objectContaining({ + userId: 123, + 'sentry.origin': 'auto.logging.pino', + 'sentry.pino.level': 'critical', // Original custom level name preserved + }), + }); + }); + + it('should not attach custom level attribute for numeric levels', async () => { + const transport = await createSentryPinoTransport(); + + const testLog = { + level: 30, // Standard numeric level + msg: 'Standard level message', + time: Date.now(), + }; + + transport.write(`${JSON.stringify(testLog)}\n`); + + await new Promise(resolve => setTimeout(resolve, 10)); + + expect(mockCaptureLog).toHaveBeenCalledWith({ + level: 'info', + message: 'Standard level message', + attributes: expect.objectContaining({ + 'sentry.origin': 'auto.logging.pino', + // Should NOT have 'sentry.pino.level' for numeric levels + }), + }); + + // Explicitly check that the custom level attribute is not present + const capturedCall = mockCaptureLog.mock.calls[0][0]; + expect(capturedCall.attributes).not.toHaveProperty('sentry.pino.level'); + }); + + it('should handle custom numeric levels with range-based mapping', async () => { + const transport = await createSentryPinoTransport(); + + const testCases = [ + { level: 11, expectedSentryLevel: 'trace' }, // 11 is in trace range (0-14) + { level: 23, expectedSentryLevel: 'debug' }, // 23 is in debug range (15-24) + { level: 33, expectedSentryLevel: 'info' }, // 33 is in info range (25-34) + { level: 42, expectedSentryLevel: 'warn' }, // 42 is in warn range (35-44) + { level: 52, expectedSentryLevel: 'error' }, // 52 is in error range (45-54) + { level: 75, expectedSentryLevel: 'fatal' }, // 75 is in fatal range (55+) + ]; + + for (const { level } of testCases) { + const testLog = { + level, + msg: `Custom numeric level ${level}`, + time: Date.now(), + }; + + transport.write(`${JSON.stringify(testLog)}\n`); + } + + await new Promise(resolve => setTimeout(resolve, 10)); + + expect(mockCaptureLog).toHaveBeenCalledTimes(6); + + testCases.forEach(({ level, expectedSentryLevel }, index) => { + expect(mockCaptureLog).toHaveBeenNthCalledWith(index + 1, { + level: expectedSentryLevel, + message: `Custom numeric level ${level}`, + attributes: expect.objectContaining({ + 'sentry.origin': 'auto.logging.pino', + }), + }); + }); + }); + + it('should handle nested keys', async () => { + const transport = await createSentryPinoTransport(); + + // Test with logs that include a nested object structure as Pino would create + // when nestedKey is configured (we'll test by manually checking the flattening logic) + const testLog = { + level: 30, + msg: 'Test message with nested payload', + time: Date.now(), + payload: { + level: 'hi', // Conflicting with Pino's level + time: 'never', // Conflicting with Pino's time + foo: 'bar', + userId: 123, + }, + }; + + transport.write(`${JSON.stringify(testLog)}\n`); + + await new Promise(resolve => setTimeout(resolve, 10)); + + // Without nestedKey configuration, the nested object should remain as-is + expect(mockCaptureLog).toHaveBeenCalledWith({ + level: 'info', + message: 'Test message with nested payload', + attributes: expect.objectContaining({ + 'sentry.origin': 'auto.logging.pino', + payload: { + level: 'hi', + time: 'never', + foo: 'bar', + userId: 123, + }, // Should remain nested without nestedKey config + }), + }); + }); + + it('should handle logs without conflicting nested objects', async () => { + const transport = await createSentryPinoTransport(); + + const testLog = { + level: 40, + msg: 'Warning with simple nested data', + time: Date.now(), + data: { + errorCode: 'E001', + module: 'auth', + details: 'Invalid credentials', + }, + }; + + transport.write(`${JSON.stringify(testLog)}\n`); + + await new Promise(resolve => setTimeout(resolve, 10)); + + expect(mockCaptureLog).toHaveBeenCalledWith({ + level: 'warn', + message: 'Warning with simple nested data', + attributes: expect.objectContaining({ + 'sentry.origin': 'auto.logging.pino', + data: { + errorCode: 'E001', + module: 'auth', + details: 'Invalid credentials', + }, // Should remain as nested object + }), + }); + }); + + it('should handle logs with multiple nested objects', async () => { + const transport = await createSentryPinoTransport(); + + const testLog = { + level: 30, + msg: 'Test message with multiple nested objects', + time: Date.now(), + user: { + id: 123, + name: 'John Doe', + }, + request: { + method: 'POST', + url: '/api/users', + headers: { + 'content-type': 'application/json', + }, + }, + }; + + transport.write(`${JSON.stringify(testLog)}\n`); + + await new Promise(resolve => setTimeout(resolve, 10)); + + expect(mockCaptureLog).toHaveBeenCalledWith({ + level: 'info', + message: 'Test message with multiple nested objects', + attributes: expect.objectContaining({ + 'sentry.origin': 'auto.logging.pino', + user: { + id: 123, + name: 'John Doe', + }, + request: { + method: 'POST', + url: '/api/users', + headers: { + 'content-type': 'application/json', + }, + }, + }), + }); + }); + + it('should handle null nested objects', async () => { + const transport = await createSentryPinoTransport(); + + const testLog = { + level: 30, + msg: 'Test message with null values', + time: Date.now(), + data: null, + user: undefined, + config: { + setting: null, + }, + }; + + transport.write(`${JSON.stringify(testLog)}\n`); + + await new Promise(resolve => setTimeout(resolve, 10)); + + expect(mockCaptureLog).toHaveBeenCalledWith({ + level: 'info', + message: 'Test message with null values', + attributes: expect.objectContaining({ + 'sentry.origin': 'auto.logging.pino', + data: null, + config: { + setting: null, + }, + }), + }); + }); + + it('should work normally with mixed data types', async () => { + const transport = await createSentryPinoTransport(); + + const testLog = { + level: 30, + msg: 'Mixed data types log', + time: Date.now(), + stringValue: 'test', + numberValue: 42, + booleanValue: true, + arrayValue: [1, 2, 3], + objectValue: { nested: 'value' }, + }; + + transport.write(`${JSON.stringify(testLog)}\n`); + + await new Promise(resolve => setTimeout(resolve, 10)); + + expect(mockCaptureLog).toHaveBeenCalledWith({ + level: 'info', + message: 'Mixed data types log', + attributes: expect.objectContaining({ + stringValue: 'test', + numberValue: 42, + booleanValue: true, + arrayValue: [1, 2, 3], + objectValue: { nested: 'value' }, + 'sentry.origin': 'auto.logging.pino', + }), + }); + }); + + it('should handle string messages', async () => { + const transport = await createSentryPinoTransport(); + + const testLog = { + level: 30, + msg: 'This is a string message', + time: Date.now(), + }; + + transport.write(`${JSON.stringify(testLog)}\n`); + + await new Promise(resolve => setTimeout(resolve, 10)); + + expect(mockCaptureLog).toHaveBeenCalledWith({ + level: 'info', + message: 'This is a string message', + attributes: expect.objectContaining({ + 'sentry.origin': 'auto.logging.pino', + }), + }); + }); + + it('should handle number messages', async () => { + const transport = await createSentryPinoTransport(); + + const testLog = { + level: 30, + msg: 42, + time: Date.now(), + }; + + transport.write(`${JSON.stringify(testLog)}\n`); + + await new Promise(resolve => setTimeout(resolve, 10)); + + expect(mockCaptureLog).toHaveBeenCalledWith({ + level: 'info', + message: '42', + attributes: expect.objectContaining({ + 'sentry.origin': 'auto.logging.pino', + }), + }); + }); + + it('should handle boolean messages', async () => { + const transport = await createSentryPinoTransport(); + + const testCases = [{ msg: true }, { msg: false }]; + + for (const { msg } of testCases) { + const testLog = { + level: 30, + msg, + time: Date.now(), + }; + + transport.write(`${JSON.stringify(testLog)}\n`); + } + + await new Promise(resolve => setTimeout(resolve, 10)); + + expect(mockCaptureLog).toHaveBeenCalledTimes(2); + expect(mockCaptureLog).toHaveBeenNthCalledWith(1, { + level: 'info', + message: 'true', + attributes: expect.objectContaining({ + 'sentry.origin': 'auto.logging.pino', + }), + }); + expect(mockCaptureLog).toHaveBeenNthCalledWith(2, { + level: 'info', + message: 'false', + attributes: expect.objectContaining({ + 'sentry.origin': 'auto.logging.pino', + }), + }); + }); + + it('should handle null and undefined messages', async () => { + const transport = await createSentryPinoTransport(); + + const testCases = [{ msg: null }, { msg: undefined }]; + + for (const { msg } of testCases) { + const testLog = { + level: 30, + msg, + time: Date.now(), + }; + + transport.write(`${JSON.stringify(testLog)}\n`); + } + + await new Promise(resolve => setTimeout(resolve, 10)); + + expect(mockCaptureLog).toHaveBeenCalledTimes(2); + expect(mockCaptureLog).toHaveBeenNthCalledWith(1, { + level: 'info', + message: 'null', + attributes: expect.objectContaining({ + 'sentry.origin': 'auto.logging.pino', + }), + }); + expect(mockCaptureLog).toHaveBeenNthCalledWith(2, { + level: 'info', + message: '', + attributes: expect.objectContaining({ + 'sentry.origin': 'auto.logging.pino', + }), + }); + }); + + it('should handle object messages', async () => { + const transport = await createSentryPinoTransport(); + + const testLog = { + level: 30, + msg: { key: 'value', nested: { prop: 123 } }, + time: Date.now(), + }; + + transport.write(`${JSON.stringify(testLog)}\n`); + + await new Promise(resolve => setTimeout(resolve, 10)); + + expect(mockCaptureLog).toHaveBeenCalledWith({ + level: 'info', + message: '{"key":"value","nested":{"prop":123}}', + attributes: expect.objectContaining({ + 'sentry.origin': 'auto.logging.pino', + }), + }); + }); + + it('should handle array messages', async () => { + const transport = await createSentryPinoTransport(); + + const testLog = { + level: 30, + msg: [1, 'two', { three: 3 }], + time: Date.now(), + }; + + transport.write(`${JSON.stringify(testLog)}\n`); + + await new Promise(resolve => setTimeout(resolve, 10)); + + expect(mockCaptureLog).toHaveBeenCalledWith({ + level: 'info', + message: '[1,"two",{"three":3}]', + attributes: expect.objectContaining({ + 'sentry.origin': 'auto.logging.pino', + }), + }); + }); + + it('should handle circular object messages gracefully', async () => { + const transport = await createSentryPinoTransport(); + + // Create a test log with a circular object as the message + // We can't use JSON.stringify directly, so we'll simulate what happens + const testLog = { + level: 30, + msg: { name: 'test', circular: true }, // Simplified object that represents circular data + time: Date.now(), + }; + + transport.write(`${JSON.stringify(testLog)}\n`); + + await new Promise(resolve => setTimeout(resolve, 10)); + + expect(mockCaptureLog).toHaveBeenCalledWith({ + level: 'info', + message: '{"name":"test","circular":true}', // The object should be serialized normally + attributes: expect.objectContaining({ + 'sentry.origin': 'auto.logging.pino', + }), + }); + }); + + it('should handle missing message gracefully', async () => { + const transport = await createSentryPinoTransport(); + + const testLog = { + level: 30, + // No msg property + time: Date.now(), + someOtherData: 'value', + }; + + transport.write(`${JSON.stringify(testLog)}\n`); + + await new Promise(resolve => setTimeout(resolve, 10)); + + expect(mockCaptureLog).toHaveBeenCalledWith({ + level: 'info', + message: '', // Empty string for undefined message + attributes: expect.objectContaining({ + someOtherData: 'value', + 'sentry.origin': 'auto.logging.pino', + }), + }); }); }); diff --git a/packages/pino-transport/vite.config.ts b/packages/pino-transport/vite.config.ts index 4ac6027d5789..ff64487a9265 100644 --- a/packages/pino-transport/vite.config.ts +++ b/packages/pino-transport/vite.config.ts @@ -1,7 +1,10 @@ import { defineConfig } from 'vitest/config'; +import baseConfig from '../../vite/vite.config'; export default defineConfig({ + ...baseConfig, test: { + ...baseConfig.test, environment: 'node', }, }); diff --git a/yarn.lock b/yarn.lock index 8c4b670fd328..c5749e9c4cc1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -24541,6 +24541,11 @@ quick-format-unescaped@^4.0.3: resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz#93ef6dd8d3453cbc7970dd614fad4c5954d6b5a7" integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg== +quick-format-unescaped@^4.0.3: + version "4.0.4" + resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz#93ef6dd8d3453cbc7970dd614fad4c5954d6b5a7" + integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg== + quick-lru@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" From 5f7129c5b80d1b90b107207500327ca89ba360dd Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Wed, 25 Jun 2025 03:10:39 +0200 Subject: [PATCH 20/22] test: Update Vitest (#16682) This PR updates Vitest to the latest version which has much better (non-flickering) output and also adds a custom config for the Node integration tests. This custom config was mainly to override the `pool: 'threads'` option which improves performance significantly when you don't need process isolation. --- .../solid-solidrouter/package.json | 2 +- .../test-applications/solid/package.json | 2 +- .../solidstart-dynamic-import/package.json | 2 +- .../solidstart-spa/package.json | 2 +- .../solidstart-top-level-import/package.json | 2 +- .../test-applications/solidstart/package.json | 2 +- .../node-integration-tests/vite.config.ts | 16 + package.json | 4 +- packages/browser/vite.config.ts | 4 + .../server/createSentryHandleRequest.test.ts | 1 + .../test/integration/sendReplayEvent.test.ts | 4 +- packages/solid/package.json | 2 +- packages/solidstart/package.json | 2 +- yarn.lock | 305 ++++++++++-------- 14 files changed, 206 insertions(+), 144 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/solid-solidrouter/package.json b/dev-packages/e2e-tests/test-applications/solid-solidrouter/package.json index 36d0fc2854f7..303c04081e24 100644 --- a/dev-packages/e2e-tests/test-applications/solid-solidrouter/package.json +++ b/dev-packages/e2e-tests/test-applications/solid-solidrouter/package.json @@ -22,7 +22,7 @@ "solid-devtools": "^0.29.2", "tailwindcss": "^3.4.1", "vite": "^5.4.11", - "vite-plugin-solid": "^2.8.2" + "vite-plugin-solid": "^2.11.6" }, "dependencies": { "@solidjs/router": "^0.13.5", diff --git a/dev-packages/e2e-tests/test-applications/solid/package.json b/dev-packages/e2e-tests/test-applications/solid/package.json index 830450e7328e..5bdcf53a9188 100644 --- a/dev-packages/e2e-tests/test-applications/solid/package.json +++ b/dev-packages/e2e-tests/test-applications/solid/package.json @@ -22,7 +22,7 @@ "solid-devtools": "^0.29.2", "tailwindcss": "^3.4.1", "vite": "^5.4.11", - "vite-plugin-solid": "^2.8.2" + "vite-plugin-solid": "^2.11.6" }, "dependencies": { "solid-js": "^1.8.18", diff --git a/dev-packages/e2e-tests/test-applications/solidstart-dynamic-import/package.json b/dev-packages/e2e-tests/test-applications/solidstart-dynamic-import/package.json index 14931bfd338c..d5ed0f8a8100 100644 --- a/dev-packages/e2e-tests/test-applications/solidstart-dynamic-import/package.json +++ b/dev-packages/e2e-tests/test-applications/solidstart-dynamic-import/package.json @@ -28,7 +28,7 @@ "typescript": "^5.4.5", "vinxi": "^0.4.0", "vite": "^5.4.10", - "vite-plugin-solid": "^2.10.2", + "vite-plugin-solid": "^2.11.6", "vitest": "^1.5.0" }, "overrides": { diff --git a/dev-packages/e2e-tests/test-applications/solidstart-spa/package.json b/dev-packages/e2e-tests/test-applications/solidstart-spa/package.json index 69542682efa3..b834045e0c60 100644 --- a/dev-packages/e2e-tests/test-applications/solidstart-spa/package.json +++ b/dev-packages/e2e-tests/test-applications/solidstart-spa/package.json @@ -28,7 +28,7 @@ "typescript": "^5.4.5", "vinxi": "^0.4.0", "vite": "^5.4.11", - "vite-plugin-solid": "^2.10.2", + "vite-plugin-solid": "^2.11.6", "vitest": "^1.5.0" }, "overrides": { diff --git a/dev-packages/e2e-tests/test-applications/solidstart-top-level-import/package.json b/dev-packages/e2e-tests/test-applications/solidstart-top-level-import/package.json index fbcb5dda1bba..4ab1fe36b633 100644 --- a/dev-packages/e2e-tests/test-applications/solidstart-top-level-import/package.json +++ b/dev-packages/e2e-tests/test-applications/solidstart-top-level-import/package.json @@ -28,7 +28,7 @@ "typescript": "^5.4.5", "vinxi": "^0.4.0", "vite": "^5.4.11", - "vite-plugin-solid": "^2.10.2", + "vite-plugin-solid": "^2.11.6", "vitest": "^1.5.0" }, "overrides": { diff --git a/dev-packages/e2e-tests/test-applications/solidstart/package.json b/dev-packages/e2e-tests/test-applications/solidstart/package.json index 8f31e4447f1b..1746bf0f6c8c 100644 --- a/dev-packages/e2e-tests/test-applications/solidstart/package.json +++ b/dev-packages/e2e-tests/test-applications/solidstart/package.json @@ -28,7 +28,7 @@ "typescript": "^5.4.5", "vinxi": "^0.4.0", "vite": "^5.4.11", - "vite-plugin-solid": "^2.10.2", + "vite-plugin-solid": "^2.11.6", "vitest": "^1.5.0" }, "overrides": { diff --git a/dev-packages/node-integration-tests/vite.config.ts b/dev-packages/node-integration-tests/vite.config.ts index 38d4abb0b16a..4b2c3b2a0a74 100644 --- a/dev-packages/node-integration-tests/vite.config.ts +++ b/dev-packages/node-integration-tests/vite.config.ts @@ -11,5 +11,21 @@ export default defineConfig({ }, include: ['./**/test.ts'], testTimeout: 15000, + // Ensure we can see debug output when DEBUG=true + ...(process.env.DEBUG + ? { + disableConsoleIntercept: true, + silent: false, + } + : {}), + // By default Vitest uses child processes to run tests but all our tests + // already run in their own processes. We use threads instead because the + // overhead is significantly less. + pool: 'threads', + reporters: process.env.DEBUG + ? ['default', { summary: false }] + : process.env.GITHUB_ACTIONS + ? ['dot', 'github-actions'] + : ['verbose'], }, }); diff --git a/package.json b/package.json index fe2058e2cb09..b19ac8358e29 100644 --- a/package.json +++ b/package.json @@ -115,7 +115,7 @@ "@size-limit/webpack": "~11.1.6", "@types/jsdom": "^21.1.6", "@types/node": "^18.19.1", - "@vitest/coverage-v8": "^2.1.8", + "@vitest/coverage-v8": "^3.2.4", "deepmerge": "^4.2.2", "downlevel-dts": "~0.11.0", "es-check": "^7.2.1", @@ -134,7 +134,7 @@ "sucrase": "^3.35.0", "ts-node": "10.9.1", "typescript": "~5.0.0", - "vitest": "^2.1.8", + "vitest": "^3.2.4", "yalc": "^1.0.0-pre.53", "yarn-deduplicate": "6.0.2" }, diff --git a/packages/browser/vite.config.ts b/packages/browser/vite.config.ts index 841ff483d7c4..0869faa66b74 100644 --- a/packages/browser/vite.config.ts +++ b/packages/browser/vite.config.ts @@ -5,5 +5,9 @@ export default defineConfig({ ...baseConfig, test: { ...baseConfig.test, + // Vitest 3 mocks all timers which broke some tests + fakeTimers: { + toFake: ['setTimeout', 'clearTimeout', 'setInterval', 'clearInterval', 'setImmediate', 'clearImmediate', 'Date'], + }, }, }); diff --git a/packages/react-router/test/server/createSentryHandleRequest.test.ts b/packages/react-router/test/server/createSentryHandleRequest.test.ts index 2a7e22e8639e..be414155bb6d 100644 --- a/packages/react-router/test/server/createSentryHandleRequest.test.ts +++ b/packages/react-router/test/server/createSentryHandleRequest.test.ts @@ -242,6 +242,7 @@ describe('createSentryHandleRequest', () => { await handleRequest(mockRequest, 200, mockResponseHeaders, mockRouterContext, mockLoadContext); + // eslint-disable-next-line @typescript-eslint/unbound-method expect(mockResponseHeaders.set).toHaveBeenCalledWith('Content-Type', 'text/html'); }); diff --git a/packages/replay-internal/test/integration/sendReplayEvent.test.ts b/packages/replay-internal/test/integration/sendReplayEvent.test.ts index ef7e6e2b63f6..3b6c9b5764e1 100644 --- a/packages/replay-internal/test/integration/sendReplayEvent.test.ts +++ b/packages/replay-internal/test/integration/sendReplayEvent.test.ts @@ -397,7 +397,9 @@ describe('Integration | sendReplayEvent', () => { // Retries = 3 (total tries = 4 including initial attempt) // + last exception is max retries exceeded expect(spyHandleException).toHaveBeenCalledTimes(5); - expect(spyHandleException).toHaveBeenLastCalledWith(new Error('Unable to send Replay - max retries exceeded')); + const expectedError = new Error('Unable to send Replay - max retries exceeded'); + (expectedError as any).cause = new Error('Something bad happened'); + expect(spyHandleException).toHaveBeenLastCalledWith(expectedError); const spyHandleExceptionCall = spyHandleException.mock.calls; expect(spyHandleExceptionCall[spyHandleExceptionCall.length - 1][0]?.cause.message).toEqual( diff --git a/packages/solid/package.json b/packages/solid/package.json index 44f1746ca05c..40ceae12c794 100644 --- a/packages/solid/package.json +++ b/packages/solid/package.json @@ -62,7 +62,7 @@ "@testing-library/jest-dom": "^6.4.5", "@testing-library/user-event": "^14.5.2", "solid-js": "^1.8.11", - "vite-plugin-solid": "^2.8.2" + "vite-plugin-solid": "^2.11.6" }, "scripts": { "build": "run-p build:transpile build:types", diff --git a/packages/solidstart/package.json b/packages/solidstart/package.json index fc031d735173..70d0bdda3460 100644 --- a/packages/solidstart/package.json +++ b/packages/solidstart/package.json @@ -77,7 +77,7 @@ "@solidjs/testing-library": "0.8.5", "@testing-library/jest-dom": "^6.4.5", "@testing-library/user-event": "^14.5.2", - "vite-plugin-solid": "^2.8.2" + "vite-plugin-solid": "^2.11.6" }, "scripts": { "build": "run-p build:transpile build:types", diff --git a/yarn.lock b/yarn.lock index c5749e9c4cc1..f696182ab447 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2634,10 +2634,10 @@ "@babel/helper-string-parser" "^7.25.9" "@babel/helper-validator-identifier" "^7.25.9" -"@bcoe/v8-coverage@^0.2.3": - version "0.2.3" - resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" - integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@bcoe/v8-coverage@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz#bbe12dca5b4ef983a0d0af4b07b9bc90ea0ababa" + integrity sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA== "@cloudflare/kv-asset-handler@0.3.4", "@cloudflare/kv-asset-handler@^0.3.4": version "0.3.4" @@ -7642,7 +7642,14 @@ dependencies: "@types/chai" "*" -"@types/chai@*", "@types/chai@^4.2.9": +"@types/chai@*", "@types/chai@^5.2.2": + version "5.2.2" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-5.2.2.tgz#6f14cea18180ffc4416bc0fd12be05fdd73bdd6b" + integrity sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg== + dependencies: + "@types/deep-eql" "*" + +"@types/chai@^4.2.9": version "4.2.15" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.15.tgz#b7a6d263c2cecf44b6de9a051cf496249b154553" integrity sha512-rYff6FI+ZTKAPkJUoyz7Udq3GaoDZnxYDEvdEdFZASiA7PoErltHezDishqQiSDWrGxvxmplH304jyzQmjp0AQ== @@ -7689,6 +7696,11 @@ dependencies: "@types/ms" "*" +"@types/deep-eql@*": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/deep-eql/-/deep-eql-4.0.2.tgz#334311971d3a07121e7eb91b684a605e7eea9cbd" + integrity sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw== + "@types/diff-match-patch@^1.0.36": version "1.0.36" resolved "https://registry.yarnpkg.com/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz#dcef10a69d357fe9d43ac4ff2eca6b85dbf466af" @@ -8713,82 +8725,85 @@ resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-5.1.4.tgz#72b8b705cfce36b00b59af196195146e356500c4" integrity sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A== -"@vitest/coverage-v8@^2.1.8": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@vitest/coverage-v8/-/coverage-v8-2.1.8.tgz#738527e6e79cef5004248452527e272e0df12284" - integrity sha512-2Y7BPlKH18mAZYAW1tYByudlCYrQyl5RGvnnDYJKW5tCiO5qg3KSAy3XAxcxKz900a0ZXxWtKrMuZLe3lKBpJw== +"@vitest/coverage-v8@^3.2.4": + version "3.2.4" + resolved "https://registry.yarnpkg.com/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz#a2d8d040288c1956a1c7d0a0e2cdcfc7a3319f13" + integrity sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ== dependencies: "@ampproject/remapping" "^2.3.0" - "@bcoe/v8-coverage" "^0.2.3" - debug "^4.3.7" + "@bcoe/v8-coverage" "^1.0.2" + ast-v8-to-istanbul "^0.3.3" + debug "^4.4.1" istanbul-lib-coverage "^3.2.2" istanbul-lib-report "^3.0.1" istanbul-lib-source-maps "^5.0.6" istanbul-reports "^3.1.7" - magic-string "^0.30.12" + magic-string "^0.30.17" magicast "^0.3.5" - std-env "^3.8.0" + std-env "^3.9.0" test-exclude "^7.0.1" - tinyrainbow "^1.2.0" + tinyrainbow "^2.0.0" -"@vitest/expect@2.1.8": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-2.1.8.tgz#13fad0e8d5a0bf0feb675dcf1d1f1a36a1773bc1" - integrity sha512-8ytZ/fFHq2g4PJVAtDX57mayemKgDR6X3Oa2Foro+EygiOJHUXhCqBAAKQYYajZpFoIfvBCF1j6R6IYRSIUFuw== +"@vitest/expect@3.2.4": + version "3.2.4" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-3.2.4.tgz#8362124cd811a5ee11c5768207b9df53d34f2433" + integrity sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig== dependencies: - "@vitest/spy" "2.1.8" - "@vitest/utils" "2.1.8" - chai "^5.1.2" - tinyrainbow "^1.2.0" + "@types/chai" "^5.2.2" + "@vitest/spy" "3.2.4" + "@vitest/utils" "3.2.4" + chai "^5.2.0" + tinyrainbow "^2.0.0" -"@vitest/mocker@2.1.8": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-2.1.8.tgz#51dec42ac244e949d20009249e033e274e323f73" - integrity sha512-7guJ/47I6uqfttp33mgo6ga5Gr1VnL58rcqYKyShoRK9ebu8T5Rs6HN3s1NABiBeVTdWNrwUMcHH54uXZBN4zA== +"@vitest/mocker@3.2.4": + version "3.2.4" + resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-3.2.4.tgz#4471c4efbd62db0d4fa203e65cc6b058a85cabd3" + integrity sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ== dependencies: - "@vitest/spy" "2.1.8" + "@vitest/spy" "3.2.4" estree-walker "^3.0.3" - magic-string "^0.30.12" + magic-string "^0.30.17" -"@vitest/pretty-format@2.1.8", "@vitest/pretty-format@^2.1.8": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-2.1.8.tgz#88f47726e5d0cf4ba873d50c135b02e4395e2bca" - integrity sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ== +"@vitest/pretty-format@3.2.4", "@vitest/pretty-format@^3.2.4": + version "3.2.4" + resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-3.2.4.tgz#3c102f79e82b204a26c7a5921bf47d534919d3b4" + integrity sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA== dependencies: - tinyrainbow "^1.2.0" + tinyrainbow "^2.0.0" -"@vitest/runner@2.1.8": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-2.1.8.tgz#b0e2dd29ca49c25e9323ea2a45a5125d8729759f" - integrity sha512-17ub8vQstRnRlIU5k50bG+QOMLHRhYPAna5tw8tYbj+jzjcspnwnwtPtiOlkuKC4+ixDPTuLZiqiWWQ2PSXHVg== +"@vitest/runner@3.2.4": + version "3.2.4" + resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-3.2.4.tgz#5ce0274f24a971f6500f6fc166d53d8382430766" + integrity sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ== dependencies: - "@vitest/utils" "2.1.8" - pathe "^1.1.2" + "@vitest/utils" "3.2.4" + pathe "^2.0.3" + strip-literal "^3.0.0" -"@vitest/snapshot@2.1.8": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-2.1.8.tgz#d5dc204f4b95dc8b5e468b455dfc99000047d2de" - integrity sha512-20T7xRFbmnkfcmgVEz+z3AU/3b0cEzZOt/zmnvZEctg64/QZbSDJEVm9fLnnlSi74KibmRsO9/Qabi+t0vCRPg== +"@vitest/snapshot@3.2.4": + version "3.2.4" + resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-3.2.4.tgz#40a8bc0346ac0aee923c0eefc2dc005d90bc987c" + integrity sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ== dependencies: - "@vitest/pretty-format" "2.1.8" - magic-string "^0.30.12" - pathe "^1.1.2" + "@vitest/pretty-format" "3.2.4" + magic-string "^0.30.17" + pathe "^2.0.3" -"@vitest/spy@2.1.8": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-2.1.8.tgz#bc41af3e1e6a41ae3b67e51f09724136b88fa447" - integrity sha512-5swjf2q95gXeYPevtW0BLk6H8+bPlMb4Vw/9Em4hFxDcaOxS+e0LOX4yqNxoHzMR2akEB2xfpnWUzkZokmgWDg== +"@vitest/spy@3.2.4": + version "3.2.4" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-3.2.4.tgz#cc18f26f40f3f028da6620046881f4e4518c2599" + integrity sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw== dependencies: - tinyspy "^3.0.2" + tinyspy "^4.0.3" -"@vitest/utils@2.1.8": - version "2.1.8" - resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-2.1.8.tgz#f8ef85525f3362ebd37fd25d268745108d6ae388" - integrity sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA== +"@vitest/utils@3.2.4": + version "3.2.4" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-3.2.4.tgz#c0813bc42d99527fb8c5b138c7a88516bca46fea" + integrity sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA== dependencies: - "@vitest/pretty-format" "2.1.8" - loupe "^3.1.2" - tinyrainbow "^1.2.0" + "@vitest/pretty-format" "3.2.4" + loupe "^3.1.4" + tinyrainbow "^2.0.0" "@vue-macros/common@^1.12.2": version "1.14.0" @@ -10076,6 +10091,15 @@ ast-types@^0.16.1: dependencies: tslib "^2.0.1" +ast-v8-to-istanbul@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.3.tgz#697101c116cff6b51c0e668ba6352e7e41fe8dd5" + integrity sha512-MuXMrSLVVoA6sYN/6Hke18vMzrT4TZNbZIj/hvh0fnYFpO+/kFXcLIaiPwXXWaQUPg4yJD8fj+lfJ7/1EBconw== + dependencies: + "@jridgewell/trace-mapping" "^0.3.25" + estree-walker "^3.0.3" + js-tokens "^9.0.1" + ast-walker-scope@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/ast-walker-scope/-/ast-walker-scope-0.6.2.tgz#b827e8949c129802f76fe0f142e95fd7efda57dc" @@ -11710,10 +11734,10 @@ ccount@^2.0.0: resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5" integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg== -chai@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/chai/-/chai-5.1.2.tgz#3afbc340b994ae3610ca519a6c70ace77ad4378d" - integrity sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw== +chai@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/chai/-/chai-5.2.0.tgz#1358ee106763624114addf84ab02697e411c9c05" + integrity sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw== dependencies: assertion-error "^2.0.1" check-error "^2.1.1" @@ -15552,10 +15576,10 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2: dependencies: homedir-polyfill "^1.0.1" -expect-type@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-1.1.0.tgz#a146e414250d13dfc49eafcfd1344a4060fa4c75" - integrity sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA== +expect-type@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-1.2.1.tgz#af76d8b357cf5fa76c41c09dafb79c549e75f71f" + integrity sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw== express@4.21.1, express@^4.10.7, express@^4.17.1, express@^4.17.3, express@^4.18.1, express@^4.21.1: version "4.21.1" @@ -18829,10 +18853,10 @@ js-string-escape@^1.0.1: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-tokens@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-9.0.0.tgz#0f893996d6f3ed46df7f0a3b12a03f5fd84223c1" - integrity sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ== +js-tokens@^9.0.0, js-tokens@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-9.0.1.tgz#2ec43964658435296f6761b34e10671c2d9527f4" + integrity sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ== js-yaml@4.1.0, js-yaml@^4.1.0: version "4.1.0" @@ -19902,10 +19926,10 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3 dependencies: js-tokens "^3.0.0 || ^4.0.0" -loupe@^3.1.0, loupe@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/loupe/-/loupe-3.1.2.tgz#c86e0696804a02218f2206124c45d8b15291a240" - integrity sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg== +loupe@^3.1.0, loupe@^3.1.4: + version "3.1.4" + resolved "https://registry.yarnpkg.com/loupe/-/loupe-3.1.4.tgz#784a0060545cb38778ffb19ccde44d7870d5fdd9" + integrity sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg== lower-case@^2.0.2: version "2.0.2" @@ -20050,7 +20074,7 @@ magic-string@^0.26.0, magic-string@^0.26.7: dependencies: sourcemap-codec "^1.4.8" -magic-string@^0.30.0, magic-string@^0.30.10, magic-string@^0.30.11, magic-string@^0.30.12, magic-string@^0.30.3, magic-string@^0.30.4, magic-string@^0.30.5, magic-string@^0.30.8, magic-string@~0.30.0: +magic-string@^0.30.0, magic-string@^0.30.10, magic-string@^0.30.11, magic-string@^0.30.17, magic-string@^0.30.3, magic-string@^0.30.4, magic-string@^0.30.5, magic-string@^0.30.8, magic-string@~0.30.0: version "0.30.17" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453" integrity sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA== @@ -26949,10 +26973,10 @@ statuses@2.0.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= -std-env@^3.7.0, std-env@^3.8.0: - version "3.8.0" - resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.8.0.tgz#b56ffc1baf1a29dcc80a3bdf11d7fca7c315e7d5" - integrity sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w== +std-env@^3.7.0, std-env@^3.9.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.9.0.tgz#1a6f7243b339dca4c9fd55e1c7504c77ef23e8f1" + integrity sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw== stdin-discarder@^0.1.0: version "0.1.0" @@ -27242,6 +27266,13 @@ strip-literal@^2.1.0: dependencies: js-tokens "^9.0.0" +strip-literal@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-literal/-/strip-literal-3.0.0.tgz#ce9c452a91a0af2876ed1ae4e583539a353df3fc" + integrity sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA== + dependencies: + js-tokens "^9.0.1" + strnum@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.0.5.tgz#5c4e829fe15ad4ff0d20c3db5ac97b73c9b072db" @@ -27822,7 +27853,7 @@ tinybench@^2.9.0: resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b" integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg== -tinyexec@^0.3.1: +tinyexec@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-0.3.2.tgz#941794e657a85e496577995c6eef66f53f42b3d2" integrity sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA== @@ -27835,7 +27866,7 @@ tinyglobby@0.2.6: fdir "^6.3.0" picomatch "^4.0.2" -tinyglobby@^0.2.13, tinyglobby@^0.2.6, tinyglobby@^0.2.7: +tinyglobby@^0.2.13, tinyglobby@^0.2.14, tinyglobby@^0.2.6, tinyglobby@^0.2.7: version "0.2.14" resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.14.tgz#5280b0cf3f972b050e74ae88406c0a6a58f4079d" integrity sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ== @@ -27843,20 +27874,20 @@ tinyglobby@^0.2.13, tinyglobby@^0.2.6, tinyglobby@^0.2.7: fdir "^6.4.4" picomatch "^4.0.2" -tinypool@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-1.0.2.tgz#706193cc532f4c100f66aa00b01c42173d9051b2" - integrity sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA== +tinypool@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-1.1.1.tgz#059f2d042bd37567fbc017d3d426bdd2a2612591" + integrity sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg== -tinyrainbow@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-1.2.0.tgz#5c57d2fc0fb3d1afd78465c33ca885d04f02abb5" - integrity sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ== +tinyrainbow@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-2.0.0.tgz#9509b2162436315e80e3eee0fcce4474d2444294" + integrity sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw== -tinyspy@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-3.0.2.tgz#86dd3cf3d737b15adcf17d7887c84a75201df20a" - integrity sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q== +tinyspy@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-4.0.3.tgz#d1d0f0602f4c15f1aae083a34d6d0df3363b1b52" + integrity sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A== titleize@^3.0.0: version "3.0.0" @@ -29124,7 +29155,18 @@ vite-hot-client@^0.2.3: resolved "https://registry.yarnpkg.com/vite-hot-client/-/vite-hot-client-0.2.3.tgz#db52aba46edbcfa7906dbca8255fd35b9a9270b2" integrity sha512-rOGAV7rUlUHX89fP2p2v0A2WWvV3QMX2UYq0fRqsWSvFvev4atHWqjwGoKaZT1VTKyLGk533ecu3eyd0o59CAg== -vite-node@2.1.8, vite-node@^2.1.1: +vite-node@3.2.4, vite-node@^3.1.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-3.2.4.tgz#f3676d94c4af1e76898c162c92728bca65f7bb07" + integrity sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg== + dependencies: + cac "^6.7.14" + debug "^4.4.1" + es-module-lexer "^1.7.0" + pathe "^2.0.3" + vite "^5.0.0 || ^6.0.0 || ^7.0.0-0" + +vite-node@^2.1.1: version "2.1.8" resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-2.1.8.tgz#9495ca17652f6f7f95ca7c4b568a235e0c8dbac5" integrity sha512-uPAwSr57kYjAUux+8E2j0q0Fxpn8M9VoyfGiRI8Kfktz9NcYMCenwY5RnZxnF1WTu3TGiYipirIzacLL3VVGFg== @@ -29135,17 +29177,6 @@ vite-node@2.1.8, vite-node@^2.1.1: pathe "^1.1.2" vite "^5.0.0" -vite-node@^3.1.4: - version "3.2.1" - resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-3.2.1.tgz#16bb67b9c53f23e1d1b5522b67ec3d9aeb441857" - integrity sha512-V4EyKQPxquurNJPtQJRZo8hKOoKNBRIhxcDbQFPFig0JdoWcUhwRgK8yoCXXrfYVPKS6XwirGHPszLnR8FbjCA== - dependencies: - cac "^6.7.14" - debug "^4.4.1" - es-module-lexer "^1.7.0" - pathe "^2.0.3" - vite "^5.0.0 || ^6.0.0 || ^7.0.0-0" - vite-plugin-checker@^0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/vite-plugin-checker/-/vite-plugin-checker-0.8.0.tgz#33419857a623b35c9483e4f603d4ca8b6984acde" @@ -29195,17 +29226,17 @@ vite-plugin-inspect@^0.8.7: picocolors "^1.0.1" sirv "^2.0.4" -vite-plugin-solid@^2.10.2, vite-plugin-solid@^2.8.2: - version "2.10.2" - resolved "https://registry.yarnpkg.com/vite-plugin-solid/-/vite-plugin-solid-2.10.2.tgz#180f5ec9d8ac03d19160dd5728b313fe9b62ee0d" - integrity sha512-AOEtwMe2baBSXMXdo+BUwECC8IFHcKS6WQV/1NEd+Q7vHPap5fmIhLcAzr+DUJ04/KHx/1UBU0l1/GWP+rMAPQ== +vite-plugin-solid@^2.10.2, vite-plugin-solid@^2.11.6: + version "2.11.6" + resolved "https://registry.yarnpkg.com/vite-plugin-solid/-/vite-plugin-solid-2.11.6.tgz#253d498affd9b07995bb113299a9da0e2cf38c55" + integrity sha512-Sl5CTqJTGyEeOsmdH6BOgalIZlwH3t4/y0RQuFLMGnvWMBvxb4+lq7x3BSiAw6etf0QexfNJW7HSOO/Qf7pigg== dependencies: "@babel/core" "^7.23.3" "@types/babel__core" "^7.20.4" babel-preset-solid "^1.8.4" merge-anything "^5.1.7" solid-refresh "^0.6.3" - vitefu "^0.2.5" + vitefu "^1.0.4" vite-plugin-vue-inspector@^5.2.0: version "5.2.0" @@ -29263,30 +29294,38 @@ vitefu@^0.2.2, vitefu@^0.2.4, vitefu@^0.2.5: resolved "https://registry.yarnpkg.com/vitefu/-/vitefu-0.2.5.tgz#c1b93c377fbdd3e5ddd69840ea3aa70b40d90969" integrity sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q== -vitest@^2.1.8: - version "2.1.8" - resolved "https://registry.yarnpkg.com/vitest/-/vitest-2.1.8.tgz#2e6a00bc24833574d535c96d6602fb64163092fa" - integrity sha512-1vBKTZskHw/aosXqQUlVWWlGUxSJR8YtiyZDJAFeW2kPAeX6S3Sool0mjspO+kXLuxVWlEDDowBAeqeAQefqLQ== - dependencies: - "@vitest/expect" "2.1.8" - "@vitest/mocker" "2.1.8" - "@vitest/pretty-format" "^2.1.8" - "@vitest/runner" "2.1.8" - "@vitest/snapshot" "2.1.8" - "@vitest/spy" "2.1.8" - "@vitest/utils" "2.1.8" - chai "^5.1.2" - debug "^4.3.7" - expect-type "^1.1.0" - magic-string "^0.30.12" - pathe "^1.1.2" - std-env "^3.8.0" +vitefu@^1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/vitefu/-/vitefu-1.0.7.tgz#430a6b178450ee90c1ea448cf3739ce100e9c54c" + integrity sha512-eRWXLBbJjW3X5z5P5IHcSm2yYbYRPb2kQuc+oqsbAl99WB5kVsPbiiox+cymo8twTzifA6itvhr2CmjnaZZp0Q== + +vitest@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-3.2.4.tgz#0637b903ad79d1539a25bc34c0ed54b5c67702ea" + integrity sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A== + dependencies: + "@types/chai" "^5.2.2" + "@vitest/expect" "3.2.4" + "@vitest/mocker" "3.2.4" + "@vitest/pretty-format" "^3.2.4" + "@vitest/runner" "3.2.4" + "@vitest/snapshot" "3.2.4" + "@vitest/spy" "3.2.4" + "@vitest/utils" "3.2.4" + chai "^5.2.0" + debug "^4.4.1" + expect-type "^1.2.1" + magic-string "^0.30.17" + pathe "^2.0.3" + picomatch "^4.0.2" + std-env "^3.9.0" tinybench "^2.9.0" - tinyexec "^0.3.1" - tinypool "^1.0.1" - tinyrainbow "^1.2.0" - vite "^5.0.0" - vite-node "2.1.8" + tinyexec "^0.3.2" + tinyglobby "^0.2.14" + tinypool "^1.1.1" + tinyrainbow "^2.0.0" + vite "^5.0.0 || ^6.0.0 || ^7.0.0-0" + vite-node "3.2.4" why-is-node-running "^2.3.0" vscode-jsonrpc@6.0.0: From 9659275f2e6c6aa51329892690dd28da748fa0fe Mon Sep 17 00:00:00 2001 From: Sigrid Huemer <32902192+s1gr1d@users.noreply.github.com> Date: Wed, 25 Jun 2025 10:08:58 +0200 Subject: [PATCH 21/22] feat(nuxt): Add alias for `@opentelemetry/resources` (#16727) Under certain circumstances, you'll get a "Cannot find module" error in Nuxt dev mode. This is because ESM requires the .js file extensions to make the imports work and there is a tracking issue in OTel to support the ESM spec (which also means adding the file extensions): https://github.com/open-telemetry/opentelemetry-js/issues/4898 Fixes https://github.com/getsentry/sentry-javascript/issues/15204 As the issue is very long, here are some relevant comments: - [ESM and OTel explanation](https://github.com/getsentry/sentry-javascript/issues/15204#issuecomment-2678498189) - [Potential Workarounds](https://github.com/getsentry/sentry-javascript/issues/15204#issuecomment-3000889008) --- packages/nuxt/src/module.ts | 4 ++- packages/nuxt/src/vite/utils.ts | 18 ++++++++++ packages/nuxt/test/vite/utils.test.ts | 48 +++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/packages/nuxt/src/module.ts b/packages/nuxt/src/module.ts index 377508e860a2..bb9843bd6ce2 100644 --- a/packages/nuxt/src/module.ts +++ b/packages/nuxt/src/module.ts @@ -4,7 +4,7 @@ import * as path from 'path'; import type { SentryNuxtModuleOptions } from './common/types'; import { addDynamicImportEntryFileWrapper, addSentryTopImport, addServerConfigToBuild } from './vite/addServerConfig'; import { setupSourceMaps } from './vite/sourceMaps'; -import { findDefaultSdkInitFile } from './vite/utils'; +import { addOTelCommonJSImportAlias, findDefaultSdkInitFile } from './vite/utils'; export type ModuleOptions = SentryNuxtModuleOptions; @@ -69,6 +69,8 @@ export default defineNuxtModule({ setupSourceMaps(moduleOptions, nuxt); } + addOTelCommonJSImportAlias(nuxt); + nuxt.hooks.hook('nitro:init', nitro => { if (serverConfigFile?.includes('.server.config')) { if (nitro.options.dev) { diff --git a/packages/nuxt/src/vite/utils.ts b/packages/nuxt/src/vite/utils.ts index f1ef1c9e4cf2..53d4e57d16e3 100644 --- a/packages/nuxt/src/vite/utils.ts +++ b/packages/nuxt/src/vite/utils.ts @@ -179,3 +179,21 @@ export function constructFunctionReExport(pathWithQuery: string, entryId: string ), ); } + +/** + * Sets up alias to work around OpenTelemetry's incomplete ESM imports. + * https://github.com/getsentry/sentry-javascript/issues/15204 + * + * OpenTelemetry's @opentelemetry/resources package has incomplete imports missing + * the .js file extensions (like execAsync for machine-id detection). This causes module resolution + * errors in certain Nuxt configurations, particularly when local Nuxt modules in Nuxt 4 are present. + * + * @see https://nuxt.com/docs/guide/concepts/esm#aliasing-libraries + */ +export function addOTelCommonJSImportAlias(nuxt: Nuxt): void { + if (!nuxt.options.alias) { + nuxt.options.alias = {}; + } + + nuxt.options.alias['@opentelemetry/resources'] = '@opentelemetry/resources/build/src/index.js'; +} diff --git a/packages/nuxt/test/vite/utils.test.ts b/packages/nuxt/test/vite/utils.test.ts index f19ec98b4b64..8bb77bc6e12b 100644 --- a/packages/nuxt/test/vite/utils.test.ts +++ b/packages/nuxt/test/vite/utils.test.ts @@ -2,6 +2,7 @@ import * as fs from 'fs'; import type { Nuxt } from 'nuxt/schema'; import { afterEach, describe, expect, it, vi } from 'vitest'; import { + addOTelCommonJSImportAlias, constructFunctionReExport, constructWrappedFunctionExportQuery, extractFunctionReexportQueryParameters, @@ -366,3 +367,50 @@ export { foo_sentryWrapped as foo }; expect(result).toBe(''); }); }); + +describe('addOTelCommonJSImportAlias', () => { + it('adds alias for @opentelemetry/resources when options.alias does not exist', () => { + const nuxtMock: Nuxt = { + options: {}, + } as unknown as Nuxt; + + addOTelCommonJSImportAlias(nuxtMock); + + expect(nuxtMock.options.alias).toEqual({ + '@opentelemetry/resources': '@opentelemetry/resources/build/src/index.js', + }); + }); + + it('adds alias for @opentelemetry/resources when options.alias already exists', () => { + const nuxtMock: Nuxt = { + options: { + alias: { + 'existing-alias': 'some-path', + }, + }, + } as unknown as Nuxt; + + addOTelCommonJSImportAlias(nuxtMock); + + expect(nuxtMock.options.alias).toEqual({ + 'existing-alias': 'some-path', + '@opentelemetry/resources': '@opentelemetry/resources/build/src/index.js', + }); + }); + + it('overwrites existing alias for @opentelemetry/resources if already present', () => { + const nuxtMock: Nuxt = { + options: { + alias: { + '@opentelemetry/resources': 'some-other-path', + }, + }, + } as unknown as Nuxt; + + addOTelCommonJSImportAlias(nuxtMock); + + expect(nuxtMock.options.alias).toEqual({ + '@opentelemetry/resources': '@opentelemetry/resources/build/src/index.js', + }); + }); +}); From 32076d3110759319b7539772fb1a19b87e1fd81c Mon Sep 17 00:00:00 2001 From: RulaKhaled Date: Wed, 25 Jun 2025 11:46:46 +0300 Subject: [PATCH 22/22] meta(changelog): Update changelog for 9.32.0 --- CHANGELOG.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67470eb2cc30..f8a8397098d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,33 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +## 9.32.0 + +### Important Changes + +- feat(browser): Add CLS sources to span attributes ([#16710](https://github.com/getsentry/sentry-javascript/pull/16710)) + +Enhances CLS (Cumulative Layout Shift) spans by adding attributes detailing the elements that caused layout shifts. + +- feat(cloudflare): Add `instrumentWorkflowWithSentry` to instrument workflows ([#16672](https://github.com/getsentry/sentry-javascript/pull/16672)) + +We've added support for Cloudflare Workflows, enabling comprehensive tracing for your workflow runs. This integration uses the workflow's instanceId as the Sentry trace_id and for sampling, linking all steps together. You'll now be able to see full traces, including retries with exponential backoff. + +- feat(pino-transport): Add functionality to send logs to sentry ([#16667](https://github.com/getsentry/sentry-javascript/pull/16667)) + +Adds the ability to send logs to Sentry via a pino transport. + +### Other Changes + +- feat(nextjs): Expose top level buildTime `errorHandler` option ([#16718](https://github.com/getsentry/sentry-javascript/pull/16718)) +- feat(node): update pipeline spans to use agent naming ([#16712](https://github.com/getsentry/sentry-javascript/pull/16712)) +- feat(deps): bump @prisma/instrumentation from 6.9.0 to 6.10.1 ([#16698](https://github.com/getsentry/sentry-javascript/pull/16698)) +- fix(sveltekit): Export logger from sveltekit worker ([#16716](https://github.com/getsentry/sentry-javascript/pull/16716)) +- fix(google-cloud-serverless): Make `CloudEventsContext` compatible with `CloudEvent` ([#16705](https://github.com/getsentry/sentry-javascript/pull/16705)) +- fix(nextjs): Stop injecting release value when create release options is set to `false` ([#16695](https://github.com/getsentry/sentry-javascript/pull/16695)) +- fix(node): account for Object. syntax with local variables matching ([#16702](https://github.com/getsentry/sentry-javascript/pull/16702)) +- fix(nuxt): Add alias for `@opentelemetry/resources` ([#16727](https://github.com/getsentry/sentry-javascript/pull/16727)) + Work in this release was contributed by @flaeppe. Thank you for your contribution! ## 9.31.0