From fa3cc61cc6d64b00e10823c1cafa6f51a50b04e1 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 2 Sep 2026 14:01:35 +0200 Subject: [PATCH 1/8] ref(server-utils): Move ServerRuntimeClient, node stack parser and server-only utils out of core MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Continues slimming `@sentry/core` down to its isomorphic surface by moving the remaining server-only APIs into `@sentry/server-utils`. Moved out of core: - `flushIfServerless` + `vercelWaitUntil` (used by the meta-framework SDKs) - `trpcMiddleware` - `callFrameToStackFrame` / `watchdogTimer` (the anr worker helpers) - `loadModule` (split out of `utils/node`; `isNodeEnv` stays, since core depends on it via `isBrowser`) - the base `ServerRuntimeClient` (with `ServerRuntimeClientOptions` / `ServerRuntimeOptions`) and the node stack-trace parser (`nodeStackLineParser`, `node`, `filenameIsInApp`) `@sentry/server-utils` is added as a dependency to `@sentry/node-native` and `@sentry/bundler-plugins`, the only two consumers that didn't already have it. Every other SDK already depended on server-utils and keeps re-exporting these under the same names, so there is no user-facing change. `mcp-server` and the `integrations/http/*` subtree stay in core. `ServerRuntimeClient` and the stack parser reach for a few core building blocks that aren't public. Rather than route them through the semi-internal `@sentry/core/server` entry, the three that are genuinely needed are exported from the public `@sentry/core` entry (`getTraceInfoFromScope`, `addUserAgentToTransportHeaders`, `normalizeStackTracePath`); the other two are dropped — the transport buffer size becomes a local constant, and the span-streaming integration name is read off the integration instance. No metric or transport internals are exposed. `ServerRuntimeOptions` was extracted from the shared `types/options.ts` (which stays in core) into its own server-utils file. Keeping the moved code out of edge/client bundles: relocating this into `@sentry/server-utils` surfaced Next.js bundling regressions (every `next build` e2e app failed with `UnhandledSchemeError` on `node:async_hooks` / `node:net`), because server-only server-utils code was reaching the edge and browser bundles, which can't resolve `node:` builtins. This was latent before the move (the same paths pulled these helpers from browser-safe `@sentry/core`). - Make the `@sentry/server-utils` barrel tree-shakeable: re-export the deprecated `attachHapiErrorHandler` normally with the deprecation moved onto the source function (matching `attachKoaErrorHandler`) instead of a non-shakeable `const` re-export that pinned the whole barrel graph. - Point `@sentry/vercel-edge` at `@sentry/server-utils/no-diagnostic-channels` for `ServerRuntimeClient` / `nodeStackLineParser` / `trpcMiddleware`, so the heavy barrel (Node integrations, `node:net`) never reaches the Next.js edge bundle. - Split the server-only App-Router wrappers and `captureRequestError` out of the client-reachable `common` barrel into `common/serverOnlyExports`, re-exported only from the server and edge entrypoints. Make `responseEnd` client-safe by inlining `vercelWaitUntil`, so the dual-bundled pages-router `_error` path no longer pulls `node:async_hooks` into the browser bundle. `vercelWaitUntil` stays in server-utils for `flushIfServerless`. Relocates the node-stack-parsing tests to `@sentry/server-utils`; keeps the core-internal unit tests (`metadata`, `debug-ids`, `third-party-errors-filter`) in core, fed by a local node stack parser fixture. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01ASNdTRtxNEjNMEBGCxENT7 --- packages/astro/src/server/middleware.ts | 2 +- packages/bun/src/client.ts | 4 +- packages/bun/src/types.ts | 2 +- packages/bundler-plugins/package.json | 1 + .../src/core/sentry/telemetry.ts | 6 +- packages/cloudflare/src/client.ts | 4 +- packages/cloudflare/src/index.ts | 3 +- packages/cloudflare/src/vendor/stacktrace.ts | 2 +- packages/core/src/index.ts | 3 + packages/core/src/logs/internal.ts | 4 +- packages/core/src/metrics/internal.ts | 4 +- packages/core/src/server.ts | 10 +- packages/core/src/types/options.ts | 72 ----- packages/core/src/utils/node.ts | 47 --- packages/core/src/utils/trace-info.ts | 2 +- .../third-party-errors-filter.test.ts | 2 +- packages/core/test/lib/metadata.test.ts | 2 +- .../core/test/lib/utils/debug-ids.test.ts | 2 +- .../core/test/lib/utils/stacktrace.test.ts | 295 +----------------- packages/core/test/mocks/nodeStackParser.ts | 124 ++++++++ packages/deno/src/client.ts | 4 +- .../deno/src/integrations/globalhandlers.ts | 2 +- .../deno/src/integrations/normalizepaths.ts | 2 +- packages/deno/src/sdk.ts | 4 +- packages/nextjs/src/common/index.ts | 10 +- .../nextjs/src/common/serverOnlyExports.ts | 12 + .../nextjs/src/common/utils/responseEnd.ts | 30 +- .../config/handleRunAfterProductionCompile.ts | 2 +- packages/nextjs/src/config/webpack.ts | 2 +- packages/nextjs/src/edge/index.ts | 2 + packages/nextjs/src/server/index.ts | 3 + .../test/common/utils/responseEnd.test.ts | 104 +++--- .../handleRunAfterProductionCompile.test.ts | 4 +- .../webpack/constructWebpackConfig.test.ts | 38 +-- packages/nextjs/test/config/wrappers.test.ts | 7 +- .../src/runtime/hooks/captureErrorHook.ts | 2 +- .../src/runtime/hooks/captureStorageEvents.ts | 2 +- .../runtime/hooks/captureErrorHook.test.ts | 16 +- packages/node-native/package.json | 3 +- .../src/event-loop-block-watchdog.ts | 2 +- packages/node/src/index.ts | 3 +- packages/node/src/integrations/anr/worker.ts | 2 +- packages/node/src/sdk/api.ts | 2 +- packages/node/src/sdk/client.ts | 4 +- packages/node/src/types.ts | 2 +- .../src/runtime/hooks/captureErrorHook.ts | 2 +- .../runtime/hooks/wrapMiddlewareHandler.ts | 2 +- .../src/runtime/utils/instrumentDatabase.ts | 3 +- .../src/runtime/utils/instrumentStorage.ts | 2 +- .../src/runtime/utils/patchEventHandler.ts | 2 +- .../runtime/hooks/captureErrorHook.test.ts | 14 +- .../hooks/wrapMiddlewareHandler.test.ts | 14 +- .../src/server/createSentryHandleError.ts | 2 +- .../src/server/createServerInstrumentation.ts | 2 +- .../src/server/wrapSentryHandleRequest.ts | 2 +- .../server/createSentryHandleError.test.ts | 6 +- .../createServerInstrumentation.test.ts | 10 +- .../server/wrapSentryHandleRequest.test.ts | 4 +- packages/remix/src/cloudflare/index.ts | 2 +- packages/remix/src/server/instrumentServer.ts | 3 +- packages/server-utils/src/exports.ts | 9 + packages/server-utils/src/index.ts | 8 +- .../integrations/hapi/hapi-error-handler.ts | 5 + .../src/integrations/hapi/index.ts | 2 + .../src/server-runtime-client.ts | 53 ++-- packages/{core => server-utils}/src/trpc.ts | 15 +- packages/server-utils/src/types/options.ts | 73 +++++ .../{core => server-utils}/src/utils/anr.ts | 4 +- .../src/utils/flushIfServerless.ts | 4 +- packages/server-utils/src/utils/loadModule.ts | 46 +++ .../src/utils/node-stack-trace.ts | 3 +- .../src/utils/vercelWaitUntil.ts | 2 +- .../test}/eventbuilder.test.ts | 14 +- .../test}/integrations/metadata.test.ts | 10 +- .../test}/server-runtime-client.test.ts | 65 ++-- .../lib => server-utils/test}/trpc.test.ts | 33 +- .../test}/utils/flushIfServerless.test.ts | 22 +- .../test/utils/node-stack-trace.test.ts | 294 +++++++++++++++++ .../test}/utils/vercelWaitUntil.test.ts | 4 +- .../server/withServerActionInstrumentation.ts | 2 +- .../withServerActionInstrumentation.test.ts | 4 +- .../sveltekit/src/server-common/handle.ts | 2 +- .../src/server-common/handleError.ts | 2 +- packages/sveltekit/src/server-common/load.ts | 2 +- .../src/server-common/serverRoute.ts | 2 +- .../src/server/wrapFetchWithSentry.ts | 2 +- .../test/server/wrapFetchWithSentry.test.ts | 10 +- packages/vercel-edge/src/client.ts | 4 +- packages/vercel-edge/src/index.ts | 3 +- packages/vercel-edge/src/sdk.ts | 2 +- 90 files changed, 893 insertions(+), 733 deletions(-) create mode 100644 packages/core/test/mocks/nodeStackParser.ts create mode 100644 packages/nextjs/src/common/serverOnlyExports.ts rename packages/{core => server-utils}/src/server-runtime-client.ts (86%) rename packages/{core => server-utils}/src/trpc.ts (90%) create mode 100644 packages/server-utils/src/types/options.ts rename packages/{core => server-utils}/src/utils/anr.ts (95%) rename packages/{core => server-utils}/src/utils/flushIfServerless.ts (96%) create mode 100644 packages/server-utils/src/utils/loadModule.ts rename packages/{core => server-utils}/src/utils/node-stack-trace.ts (97%) rename packages/{core => server-utils}/src/utils/vercelWaitUntil.ts (95%) rename packages/{core/test/lib/utils => server-utils/test}/eventbuilder.test.ts (96%) rename packages/{core/test/lib => server-utils/test}/integrations/metadata.test.ts (91%) rename packages/{core/test/lib => server-utils/test}/server-runtime-client.test.ts (87%) rename packages/{core/test/lib => server-utils/test}/trpc.test.ts (74%) rename packages/{core/test/lib => server-utils/test}/utils/flushIfServerless.test.ts (81%) create mode 100644 packages/server-utils/test/utils/node-stack-trace.test.ts rename packages/{core/test/lib => server-utils/test}/utils/vercelWaitUntil.test.ts (95%) diff --git a/packages/astro/src/server/middleware.ts b/packages/astro/src/server/middleware.ts index c75ffd6ed968..3912546a772f 100644 --- a/packages/astro/src/server/middleware.ts +++ b/packages/astro/src/server/middleware.ts @@ -25,7 +25,7 @@ import { filterCollectedUrl, filterCollectedUrlQuery, } from '@sentry/core'; -import { flushIfServerless } from '@sentry/core/server'; +import { flushIfServerless } from '@sentry/server-utils'; import { captureException, continueTrace, diff --git a/packages/bun/src/client.ts b/packages/bun/src/client.ts index 03d301f05f95..e61249201c0a 100644 --- a/packages/bun/src/client.ts +++ b/packages/bun/src/client.ts @@ -1,5 +1,5 @@ -import type { ServerRuntimeClientOptions } from '@sentry/core/server'; -import { ServerRuntimeClient } from '@sentry/core/server'; +import type { ServerRuntimeClientOptions } from '@sentry/server-utils'; +import { ServerRuntimeClient } from '@sentry/server-utils'; import { applySdkMetadata } from '@sentry/core'; import * as os from 'os'; import type { BunClientOptions } from './types'; diff --git a/packages/bun/src/types.ts b/packages/bun/src/types.ts index 34643a995ab1..d15f80022f5e 100644 --- a/packages/bun/src/types.ts +++ b/packages/bun/src/types.ts @@ -1,5 +1,5 @@ import type { BaseTransportOptions, ClientOptions, Options } from '@sentry/core'; -import type { ServerRuntimeOptions } from '@sentry/core/server'; +import type { ServerRuntimeOptions } from '@sentry/server-utils'; /** * Base options for the Sentry Bun SDK. diff --git a/packages/bundler-plugins/package.json b/packages/bundler-plugins/package.json index 1b632dd79cf9..86725ebdd4d6 100644 --- a/packages/bundler-plugins/package.json +++ b/packages/bundler-plugins/package.json @@ -112,6 +112,7 @@ "dependencies": { "@babel/core": "^7.18.5", "@sentry/core": "10.67.0", + "@sentry/server-utils": "10.67.0", "dotenv": "^17.4.2", "find-up": "^5.0.0", "glob": "^13.0.6", diff --git a/packages/bundler-plugins/src/core/sentry/telemetry.ts b/packages/bundler-plugins/src/core/sentry/telemetry.ts index a68f106e07b9..1dd08a0bc71a 100644 --- a/packages/bundler-plugins/src/core/sentry/telemetry.ts +++ b/packages/bundler-plugins/src/core/sentry/telemetry.ts @@ -1,12 +1,12 @@ import type { Client } from '@sentry/core'; -import type { ServerRuntimeClientOptions } from '@sentry/core/server'; +import type { ServerRuntimeClientOptions } from '@sentry/server-utils'; import { applySdkMetadata } from '@sentry/core'; -import { ServerRuntimeClient } from '@sentry/core/server'; +import { ServerRuntimeClient } from '@sentry/server-utils'; import type { NormalizedOptions } from '../options-mapping'; import { SENTRY_SAAS_URL } from '../options-mapping'; import { Scope } from '@sentry/core'; import { createStackParser } from '@sentry/core'; -import { nodeStackLineParser } from '@sentry/core/server'; +import { nodeStackLineParser } from '@sentry/server-utils'; import { makeOptionallyEnabledNodeTransport } from './transports'; import { SentryCliAdapter } from '../cli'; import { LIB_VERSION } from '../version'; diff --git a/packages/cloudflare/src/client.ts b/packages/cloudflare/src/client.ts index a71445b03f92..ec5d4401864e 100644 --- a/packages/cloudflare/src/client.ts +++ b/packages/cloudflare/src/client.ts @@ -7,8 +7,8 @@ import { debug, spanIsSampled, } from '@sentry/core'; -import type { ServerRuntimeClientOptions } from '@sentry/core/server'; -import { ServerRuntimeClient } from '@sentry/core/server'; +import type { ServerRuntimeClientOptions } from '@sentry/server-utils'; +import { ServerRuntimeClient } from '@sentry/server-utils'; import { DEBUG_BUILD } from './debug-build'; import type { ExecutionContextCompat } from './executionContext'; import type { makeFlushLock } from './flush'; diff --git a/packages/cloudflare/src/index.ts b/packages/cloudflare/src/index.ts index 4e24fd9b0ecb..7d4809bba808 100644 --- a/packages/cloudflare/src/index.ts +++ b/packages/cloudflare/src/index.ts @@ -106,7 +106,8 @@ export { withStreamedSpan, spanStreamingIntegration, } from '@sentry/core'; -export { instrumentPostgresJsSql, trpcMiddleware, wrapMcpServerWithSentry } from '@sentry/core/server'; +export { instrumentPostgresJsSql, wrapMcpServerWithSentry } from '@sentry/core/server'; +export { trpcMiddleware } from '@sentry/server-utils'; export { withSentry } from './withSentry'; export { defineCloudflareOptions } from './defineCloudflareOptions'; diff --git a/packages/cloudflare/src/vendor/stacktrace.ts b/packages/cloudflare/src/vendor/stacktrace.ts index d57ed8a7dacb..8e035b672705 100644 --- a/packages/cloudflare/src/vendor/stacktrace.ts +++ b/packages/cloudflare/src/vendor/stacktrace.ts @@ -4,7 +4,7 @@ import type { StackLineParser, StackLineParserFn, StackParser } from '@sentry/core'; import { basename, createStackParser } from '@sentry/core'; -import { nodeStackLineParser } from '@sentry/core/server'; +import { nodeStackLineParser } from '@sentry/server-utils'; type GetModuleFn = (filename: string | undefined) => string | undefined; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index dc01c23fda8f..aaa6cd6365c1 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -62,6 +62,7 @@ export { getEnvelopeEndpointWithUrlEncodedAuth, getReportDialogEndpoint, SENTRY_ export { Client } from './client'; export { initAndBind, setCurrentClient } from './sdk'; export { createTransport } from './transports/base'; +export { addUserAgentToTransportHeaders } from './transports/userAgent'; export { makeOfflineTransport } from './transports/offline'; export { makeMultiplexedTransport, MULTIPLEXED_TRANSPORT_EXTRA_KEY } from './transports/multiplexed'; export { @@ -118,6 +119,7 @@ export { INTERNAL_setSegmentNameSourceIfSegment, } from './utils/spanUtils'; export { _setSpanForScope as _INTERNAL_setSpanForScope } from './utils/spanOnScope'; +export { getTraceInfoFromScope } from './utils/trace-info'; export { parseSampleRate } from './utils/parseSampleRate'; export { applySdkMetadata } from './utils/sdkMetadata'; export { getTraceData } from './utils/traceData'; @@ -260,6 +262,7 @@ export { createStackParser, getFramesFromEvent, getFunctionName, + normalizeStackTracePath, stackParserFromStackParserOptions, stripSentryFramesAndReverse, } from './utils/stacktrace'; diff --git a/packages/core/src/logs/internal.ts b/packages/core/src/logs/internal.ts index 3ad106dfbb32..50df3fc69614 100644 --- a/packages/core/src/logs/internal.ts +++ b/packages/core/src/logs/internal.ts @@ -13,7 +13,7 @@ import { getCombinedScopeData } from '../utils/scopeData'; import { getActiveSpan } from '../utils/spanUtils'; import { timestampInSeconds } from '../utils/time'; import { getSequenceAttribute } from '../utils/timestampSequence'; -import { _getTraceInfoFromScope } from '../utils/trace-info'; +import { getTraceInfoFromScope } from '../utils/trace-info'; import { SEVERITY_TEXT_TO_SEVERITY_NUMBER } from './constants'; import { createLogEnvelope } from './envelope'; @@ -87,7 +87,7 @@ export function _INTERNAL_captureLog( const { release, environment, beforeSendLog } = client.getOptions(); - const [, traceContext] = _getTraceInfoFromScope(client, currentScope); + const [, traceContext] = getTraceInfoFromScope(client, currentScope); const processedLogAttributes = { ...beforeLog.attributes, diff --git a/packages/core/src/metrics/internal.ts b/packages/core/src/metrics/internal.ts index 621992b2ed70..28923b004d96 100644 --- a/packages/core/src/metrics/internal.ts +++ b/packages/core/src/metrics/internal.ts @@ -13,7 +13,7 @@ import { getCombinedScopeData } from '../utils/scopeData'; import { getActiveSpan } from '../utils/spanUtils'; import { timestampInSeconds } from '../utils/time'; import { getSequenceAttribute } from '../utils/timestampSequence'; -import { _getTraceInfoFromScope } from '../utils/trace-info'; +import { getTraceInfoFromScope } from '../utils/trace-info'; import { createMetricEnvelope } from './envelope'; const MAX_METRIC_BUFFER_SIZE = 1000; @@ -132,7 +132,7 @@ function _buildSerializedMetric( scopeAttributes: RawAttributes>, ): SerializedMetric { // Get trace context - const [, traceContext] = _getTraceInfoFromScope(client, currentScope); + const [, traceContext] = getTraceInfoFromScope(client, currentScope); const span = getActiveSpan(currentScope); const traceId = span ? span.spanContext().traceId : traceContext?.trace_id; const spanId = span ? span.spanContext().spanId : undefined; diff --git a/packages/core/src/server.ts b/packages/core/src/server.ts index 2d2d76127f6a..b35d6af49e1f 100644 --- a/packages/core/src/server.ts +++ b/packages/core/src/server.ts @@ -4,16 +4,8 @@ * @module */ -export type { ServerRuntimeClientOptions } from './server-runtime-client'; -export { ServerRuntimeClient } from './server-runtime-client'; -export type { ServerRuntimeOptions } from './types/options'; -export { trpcMiddleware } from './trpc'; export { wrapMcpServerWithSentry } from './integrations/mcp-server'; -export { isNodeEnv, loadModule } from './utils/node'; -export { filenameIsInApp, node, nodeStackLineParser } from './utils/node-stack-trace'; -export { vercelWaitUntil } from './utils/vercelWaitUntil'; -export { flushIfServerless } from './utils/flushIfServerless'; -export { callFrameToStackFrame, watchdogTimer } from './utils/anr'; +export { isNodeEnv } from './utils/node'; export { safeUnref as _INTERNAL_safeUnref } from './utils/timer'; /* oxlint-disable typescript/no-deprecated -- deprecated Express exports, kept until the next major */ export { patchExpressModule } from './integrations/express/index'; diff --git a/packages/core/src/types/options.ts b/packages/core/src/types/options.ts index 8ea3944f1613..6ef64670bb1b 100644 --- a/packages/core/src/types/options.ts +++ b/packages/core/src/types/options.ts @@ -12,78 +12,6 @@ import type { StackLineParser, StackParser } from './stacktrace'; import type { TracePropagationTargets } from './tracing'; import type { BaseTransportOptions, Transport } from './transport'; -/** - * Base options for WinterTC-compatible server-side JavaScript runtimes. - * This interface contains common configuration options shared between - * SDKs. - */ -export interface ServerRuntimeOptions { - /** - * List of strings/regex controlling to which outgoing requests - * the SDK will attach tracing headers. - * - * By default the SDK will attach those headers to all outgoing - * requests. If this option is provided, the SDK will match the - * request URL of outgoing requests against the items in this - * array, and only attach tracing headers if a match was found. - * - * @example - * ```js - * Sentry.init({ - * tracePropagationTargets: ['api.site.com'], - * }); - * ``` - */ - tracePropagationTargets?: TracePropagationTargets; - - /** - * Sets an optional server name (device name). - * - * This is useful for identifying which server or instance is sending events. - */ - serverName?: string; - - /** - * If you use Spotlight by Sentry during development, use - * this option to forward captured Sentry events to Spotlight. - * - * Either set it to true, or provide a specific Spotlight Sidecar URL. - * - * More details: https://spotlightjs.com/ - * - * IMPORTANT: Only set this option to `true` while developing, not in production! - */ - spotlight?: boolean | string; - - /** - * If set to `false`, the SDK will not automatically detect the `serverName`. - * - * This is useful if you are using the SDK in a CLI app or Electron where the - * hostname might be considered PII. - * - * @default true - */ - includeServerName?: boolean; - - /** - * Controls how many milliseconds to wait before shutting down. The default is 2 seconds. Setting this too low can cause - * problems for sending events from command line applications. Setting it too - * high can cause the application to block for users with network connectivity - * problems. - */ - shutdownTimeout?: number; - - /** - * Configures in which interval client reports will be flushed. Defaults to `60_000` (milliseconds). - */ - clientReportFlushInterval?: number; - - /** - * Callback that is executed when a fatal global error occurs. - */ - onFatalError?(this: void, error: Error): void; -} - /** * Allowed attribute value matchers in `ignoreSpans` filters. * String span attributes use pattern matching (substring or RegExp). diff --git a/packages/core/src/utils/node.ts b/packages/core/src/utils/node.ts index 6060700c2b03..80704595213e 100644 --- a/packages/core/src/utils/node.ts +++ b/packages/core/src/utils/node.ts @@ -18,50 +18,3 @@ export function isNodeEnv(): boolean { Object.prototype.toString.call(typeof process !== 'undefined' ? process : 0) === '[object process]' ); } - -/** - * Requires a module which is protected against bundler minification. - * - * @param request The module path to resolve - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function dynamicRequire(mod: any, request: string): any { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - return mod.require(request); -} - -/** - * Helper for dynamically loading module that should work with linked dependencies. - * The problem is that we _should_ be using `require(require.resolve(moduleName, { paths: [cwd()] }))` - * However it's _not possible_ to do that with Webpack, as it has to know all the dependencies during - * build time. `require.resolve` is also not available in any other way, so we cannot create, - * a fake helper like we do with `dynamicRequire`. - * - * We always prefer to use local package, thus the value is not returned early from each `try/catch` block. - * That is to mimic the behavior of `require.resolve` exactly. - * - * @param moduleName module name to require - * @param existingModule module to use for requiring - * @returns possibly required module - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function loadModule(moduleName: string, existingModule: any = module): T | undefined { - let mod: T | undefined; - - try { - mod = dynamicRequire(existingModule, moduleName); - } catch { - // no-empty - } - - if (!mod) { - try { - const { cwd } = dynamicRequire(existingModule, 'process'); - mod = dynamicRequire(existingModule, `${cwd()}/node_modules/${moduleName}`) as T; - } catch { - // no-empty - } - } - - return mod; -} diff --git a/packages/core/src/utils/trace-info.ts b/packages/core/src/utils/trace-info.ts index 084a311d844d..daa50de3e95c 100644 --- a/packages/core/src/utils/trace-info.ts +++ b/packages/core/src/utils/trace-info.ts @@ -10,7 +10,7 @@ import type { DynamicSamplingContext } from '../types/envelope'; import { getActiveSpan, spanToTraceContext } from './spanUtils'; /** Extract trace information from scope */ -export function _getTraceInfoFromScope( +export function getTraceInfoFromScope( client: Client, scope: Scope | undefined, ): [dynamicSamplingContext: Partial | undefined, traceContext: TraceContext | undefined] { diff --git a/packages/core/test/lib/integrations/third-party-errors-filter.test.ts b/packages/core/test/lib/integrations/third-party-errors-filter.test.ts index ff2d2e966e4d..df21415327e5 100644 --- a/packages/core/test/lib/integrations/third-party-errors-filter.test.ts +++ b/packages/core/test/lib/integrations/third-party-errors-filter.test.ts @@ -3,7 +3,7 @@ import type { Client } from '../../../src/client'; import { thirdPartyErrorFilterIntegration } from '../../../src/integrations/third-party-errors-filter'; import { addMetadataToStackFrames } from '../../../src/metadata'; import type { Event } from '../../../src/types/event'; -import { nodeStackLineParser } from '../../../src/utils/node-stack-trace'; +import { nodeStackLineParser } from '../../mocks/nodeStackParser'; import { createStackParser } from '../../../src/utils/stacktrace'; import { GLOBAL_OBJ } from '../../../src/utils/worldwide'; diff --git a/packages/core/test/lib/metadata.test.ts b/packages/core/test/lib/metadata.test.ts index 0036b68657ba..31b36b73b547 100644 --- a/packages/core/test/lib/metadata.test.ts +++ b/packages/core/test/lib/metadata.test.ts @@ -6,7 +6,7 @@ import { stripMetadataFromStackFrames, } from '../../src/metadata'; import type { Event } from '../../src/types/event'; -import { nodeStackLineParser } from '../../src/utils/node-stack-trace'; +import { nodeStackLineParser } from '../mocks/nodeStackParser'; import { createStackParser } from '../../src/utils/stacktrace'; import { GLOBAL_OBJ } from '../../src/utils/worldwide'; diff --git a/packages/core/test/lib/utils/debug-ids.test.ts b/packages/core/test/lib/utils/debug-ids.test.ts index 4917ac233569..d9e81d8dea39 100644 --- a/packages/core/test/lib/utils/debug-ids.test.ts +++ b/packages/core/test/lib/utils/debug-ids.test.ts @@ -1,5 +1,5 @@ import { beforeEach, describe, expect, it } from 'vitest'; -import { nodeStackLineParser } from '../../../src/server'; +import { nodeStackLineParser } from '../../mocks/nodeStackParser'; import { clearDebugIdCache, getDebugImagesForResources, getFilenameToDebugIdMap } from '../../../src/utils/debug-ids'; import { createStackParser } from '../../../src/utils/stacktrace'; diff --git a/packages/core/test/lib/utils/stacktrace.test.ts b/packages/core/test/lib/utils/stacktrace.test.ts index 2a1700b262a7..0d4514448b30 100644 --- a/packages/core/test/lib/utils/stacktrace.test.ts +++ b/packages/core/test/lib/utils/stacktrace.test.ts @@ -1,5 +1,4 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { nodeStackLineParser } from '../../../src/utils/node-stack-trace'; +import { describe, expect, it, vi } from 'vitest'; import { createStackParser, stripSentryFramesAndReverse } from '../../../src/utils/stacktrace'; describe('Stacktrace', () => { @@ -182,295 +181,3 @@ describe('Stacktrace', () => { }); }); }); - -describe('node', () => { - const mockGetModule = vi.fn(); - const parser = nodeStackLineParser(mockGetModule); - const node = parser[1]; - - beforeEach(() => { - mockGetModule.mockReset(); - }); - - it('should return undefined for invalid input', () => { - expect(node('invalid input')).toBeUndefined(); - }); - - it('should extract function, module, filename, lineno, colno, and in_app from valid input', () => { - const input = 'at myFunction (/path/to/file.js:10:5)'; - - const expectedOutput = { - filename: '/path/to/file.js', - module: undefined, - function: 'myFunction', - lineno: 10, - colno: 5, - in_app: true, - }; - - expect(node(input)).toEqual(expectedOutput); - }); - - it('extracts module from getModule', () => { - const input = 'at myFunction (/path/to/file.js:10:5)'; - mockGetModule.mockReturnValue('myModule'); - expect(node(input)?.module).toEqual('myModule'); - }); - - it('should extract anonymous function name correctly', () => { - const input = 'at /path/to/file.js:10:5'; - - const expectedOutput = { - filename: '/path/to/file.js', - module: undefined, - function: '?', - lineno: 10, - colno: 5, - in_app: true, - }; - - expect(node(input)).toEqual(expectedOutput); - }); - - it('should extract method name and type name correctly', () => { - const input = 'at myObject.myMethod (/path/to/file.js:10:5)'; - - const expectedOutput = { - filename: '/path/to/file.js', - module: undefined, - function: 'myObject.myMethod', - lineno: 10, - colno: 5, - in_app: true, - }; - - expect(node(input)).toEqual(expectedOutput); - }); - - it('should handle input with file:// protocol', () => { - const input = 'at myFunction (file:///path/to/file.js:10:5)'; - - const expectedOutput = { - filename: '/path/to/file.js', - module: undefined, - function: 'myFunction', - lineno: 10, - colno: 5, - in_app: true, - }; - - expect(node(input)).toEqual(expectedOutput); - }); - - it('should handle input with no line or column number', () => { - const input = 'at myFunction (/path/to/file.js)'; - - const expectedOutput = { - filename: '/path/to/file.js', - module: undefined, - function: 'myFunction', - lineno: undefined, - colno: undefined, - in_app: true, - }; - - expect(node(input)).toEqual(expectedOutput); - }); - - it('should handle input with "native" flag', () => { - const input = 'at myFunction (native)'; - - const expectedOutput = { - filename: undefined, - module: undefined, - function: 'myFunction', - lineno: undefined, - colno: undefined, - in_app: false, - }; - - expect(node(input)).toEqual(expectedOutput); - }); - - it('should correctly parse a stack trace line with a function name and file URL', () => { - const line = 'at myFunction (file:///path/to/myFile.js:10:20)'; - const result = node(line); - expect(result).toEqual({ - filename: '/path/to/myFile.js', - function: 'myFunction', - lineno: 10, - colno: 20, - in_app: true, - }); - }); - - it('should correctly parse a stack trace line with a method name and filename', () => { - const line = 'at MyClass.myMethod (/path/to/myFile.js:10:20)'; - const result = node(line); - expect(result).toEqual({ - filename: '/path/to/myFile.js', - module: undefined, - function: 'MyClass.myMethod', - lineno: 10, - colno: 20, - in_app: true, - }); - }); - - it('should correctly parse a stack trace line with an anonymous function', () => { - const line = 'at Object. (/path/to/myFile.js:10:20)'; - const result = node(line); - - expect(result).toEqual({ - filename: '/path/to/myFile.js', - function: 'Object.?', - lineno: 10, - colno: 20, - in_app: true, - }); - }); - - it('should correctly parse a stack trace line with no function or filename', () => { - const line = 'at /path/to/myFile.js:10:20'; - const result = node(line); - expect(result).toEqual({ - filename: '/path/to/myFile.js', - function: '?', - lineno: 10, - colno: 20, - in_app: true, - }); - }); - - it('should correctly parse a stack trace line with a native function', () => { - const line = 'at Object. (native)'; - const result = node(line); - expect(result).toEqual({ - filename: undefined, - function: 'Object.?', - lineno: undefined, - colno: undefined, - in_app: false, - }); - }); - - it('should correctly parse a stack trace line with a module filename', () => { - const line = 'at Object. (/path/to/node_modules/myModule/index.js:10:20)'; - const result = node(line); - - expect(result).toEqual({ - filename: '/path/to/node_modules/myModule/index.js', - function: 'Object.?', - lineno: 10, - colno: 20, - in_app: false, - }); - }); - - it('should correctly parse a stack trace line with a Windows filename', () => { - const line = 'at Object. (C:\\path\\to\\myFile.js:10:20)'; - const result = node(line); - expect(result).toEqual({ - filename: 'C:\\path\\to\\myFile.js', - function: 'Object.?', - lineno: 10, - colno: 20, - in_app: true, - }); - }); - - it('should mark frames with protocols as in_app: true', () => { - const line = 'at Object. (app:///_next/server/pages/[error].js:10:20)'; - const result = node(line); - expect(result?.in_app).toBe(true); - }); - - it('parses frame filename paths with spaces and characters in file name', () => { - const input = 'at myObject.myMethod (/path/to/file with space(1).js:10:5)'; - - const expectedOutput = { - filename: '/path/to/file with space(1).js', - module: undefined, - function: 'myObject.myMethod', - lineno: 10, - colno: 5, - in_app: true, - }; - - expect(node(input)).toEqual(expectedOutput); - }); - - it('parses frame filename paths with spaces and characters in file path', () => { - const input = 'at myObject.myMethod (/path with space(1)/to/file.js:10:5)'; - - const expectedOutput = { - filename: '/path with space(1)/to/file.js', - module: undefined, - function: 'myObject.myMethod', - lineno: 10, - colno: 5, - in_app: true, - }; - - expect(node(input)).toEqual(expectedOutput); - }); - - it('parses encoded frame filename paths with spaces and characters in file name', () => { - const input = 'at myObject.myMethod (/path/to/file%20with%20space(1).js:10:5)'; - - const expectedOutput = { - filename: '/path/to/file with space(1).js', - module: undefined, - function: 'myObject.myMethod', - lineno: 10, - colno: 5, - in_app: true, - }; - - expect(node(input)).toEqual(expectedOutput); - }); - - it('parses encoded frame filename paths with spaces and characters in file path', () => { - const input = 'at myObject.myMethod (/path%20with%20space(1)/to/file.js:10:5)'; - - const expectedOutput = { - filename: '/path with space(1)/to/file.js', - module: undefined, - function: 'myObject.myMethod', - lineno: 10, - colno: 5, - in_app: true, - }; - - expect(node(input)).toEqual(expectedOutput); - }); - - it('parses function name when filename is a data uri ', () => { - const input = - "at dynamicFn (data:application/javascript,export function dynamicFn() { throw new Error('Error from data-uri module');};:1:38)"; - - const expectedOutput = { - function: 'dynamicFn', - filename: '', - }; - - expect(node(input)).toEqual(expectedOutput); - }); - - it('returns the raw filename when decodeURI throws a URIError', () => { - const malformedFilename = '/path/to/%file%.js'; - const input = `at myFunction (${malformedFilename}:10:5)`; - - const result = node(input); - - expect(result?.filename).toBe('/path/to/%file%.js'); - }); - - it('decodes a valid percent-encoded filename', () => { - const input = 'at myFunction (/path/to/my%20file.js:10:5)'; - - const result = node(input); - - expect(result?.filename).toBe('/path/to/my file.js'); - }); -}); diff --git a/packages/core/test/mocks/nodeStackParser.ts b/packages/core/test/mocks/nodeStackParser.ts new file mode 100644 index 000000000000..6f643ea07154 --- /dev/null +++ b/packages/core/test/mocks/nodeStackParser.ts @@ -0,0 +1,124 @@ +import type { StackLineParser, StackLineParserFn } from '../../src/types/stacktrace'; +import { normalizeStackTracePath, UNKNOWN_FUNCTION } from '../../src/utils/stacktrace'; + +// A node-style stack-line parser used purely as a realistic fixture for core tests +// (event building, module metadata, debug ids). The production parser lives in +// `@sentry/server-utils`; core must not depend on it, so this mirrors just enough of +// its behaviour to exercise the core code under test. + +type GetModuleFn = (filename: string | undefined) => string | undefined; + +function filenameIsInApp(filename: string, isNative: boolean = false): boolean { + const isInternal = + isNative || + (filename && + !filename.startsWith('/') && + !filename.match(/^[A-Z]:/) && + !filename.startsWith('.') && + !filename.match(/^[a-zA-Z]([a-zA-Z0-9.\-+])*:\/\//)); + + return !isInternal && filename !== undefined && !filename.includes('node_modules/'); +} + +function node(getModule?: GetModuleFn): StackLineParserFn { + const FILENAME_MATCH = /^\s*[-]{4,}$/; + const FULL_MATCH = /at (?:async )?(?:(.+?)\s+\()?(?:(.+):(\d+):(\d+)?|([^)]+))\)?/; + const DATA_URI_MATCH = /at (?:async )?(.+?) \(data:(.*?),/; + + return (line: string) => { + const dataUriMatch = line.match(DATA_URI_MATCH); + if (dataUriMatch) { + return { + filename: ``, + function: dataUriMatch[1], + }; + } + + const lineMatch = line.match(FULL_MATCH); + + if (lineMatch) { + let object: string | undefined; + let method: string | undefined; + let functionName: string | undefined; + let typeName: string | undefined; + let methodName: string | undefined; + + if (lineMatch[1]) { + functionName = lineMatch[1]; + + let methodStart = functionName.lastIndexOf('.'); + if (functionName[methodStart - 1] === '.') { + methodStart--; + } + + if (methodStart > 0) { + object = functionName.slice(0, methodStart); + method = functionName.slice(methodStart + 1); + const objectEnd = object.indexOf('.Module'); + if (objectEnd > 0) { + functionName = functionName.slice(objectEnd + 1); + object = object.slice(0, objectEnd); + } + } + typeName = undefined; + } + + if (method) { + typeName = object; + methodName = method; + } + + if (method === '') { + methodName = undefined; + functionName = undefined; + } + + if (functionName === undefined) { + methodName = methodName || UNKNOWN_FUNCTION; + functionName = typeName ? `${typeName}.${methodName}` : methodName; + } + + let filename = normalizeStackTracePath(lineMatch[2]); + const isNative = lineMatch[5] === 'native'; + + if (!filename && lineMatch[5] && !isNative) { + filename = lineMatch[5]; + } + + const maybeDecodedFilename = filename ? _safeDecodeURI(filename) : undefined; + return { + filename: maybeDecodedFilename ?? filename, + module: maybeDecodedFilename && getModule?.(maybeDecodedFilename), + function: functionName, + lineno: _parseIntOrUndefined(lineMatch[3]), + colno: _parseIntOrUndefined(lineMatch[4]), + in_app: filenameIsInApp(filename || '', isNative), + }; + } + + if (line.match(FILENAME_MATCH)) { + return { + filename: line, + }; + } + + return undefined; + }; +} + +/** Node stack line parser for use as a test fixture. */ +export function nodeStackLineParser(getModule?: GetModuleFn): StackLineParser { + return [90, node(getModule)]; +} + +function _parseIntOrUndefined(input: string | undefined): number | undefined { + return parseInt(input || '', 10) || undefined; +} + +function _safeDecodeURI(filename: string): string | undefined { + try { + return decodeURI(filename); + } catch { + return undefined; + } +} diff --git a/packages/deno/src/client.ts b/packages/deno/src/client.ts index 160396df57af..0eb349fe2710 100644 --- a/packages/deno/src/client.ts +++ b/packages/deno/src/client.ts @@ -1,6 +1,6 @@ -import type { ServerRuntimeClientOptions } from '@sentry/core/server'; +import type { ServerRuntimeClientOptions } from '@sentry/server-utils'; import { _INTERNAL_flushLogsBuffer, SDK_VERSION } from '@sentry/core'; -import { ServerRuntimeClient } from '@sentry/core/server'; +import { ServerRuntimeClient } from '@sentry/server-utils'; import { setAsyncLocalStorageAsyncContextStrategy } from '@sentry/server-utils'; import type { DenoClientOptions } from './types'; diff --git a/packages/deno/src/integrations/globalhandlers.ts b/packages/deno/src/integrations/globalhandlers.ts index 868591e35059..95b111166935 100644 --- a/packages/deno/src/integrations/globalhandlers.ts +++ b/packages/deno/src/integrations/globalhandlers.ts @@ -1,5 +1,5 @@ import type { Client, Event, IntegrationFn, Primitive, StackParser } from '@sentry/core'; -import type { ServerRuntimeClient } from '@sentry/core/server'; +import type { ServerRuntimeClient } from '@sentry/server-utils'; import { captureEvent, defineIntegration, eventFromUnknownInput, flush, getClient, isPrimitive } from '@sentry/core'; type GlobalHandlersIntegrationsOptionKeys = 'error' | 'unhandledrejection'; diff --git a/packages/deno/src/integrations/normalizepaths.ts b/packages/deno/src/integrations/normalizepaths.ts index 408f524c9ee9..cf48af45c772 100644 --- a/packages/deno/src/integrations/normalizepaths.ts +++ b/packages/deno/src/integrations/normalizepaths.ts @@ -1,6 +1,6 @@ import type { IntegrationFn } from '@sentry/core'; import { createStackParser, defineIntegration, dirname } from '@sentry/core'; -import { nodeStackLineParser } from '@sentry/core/server'; +import { nodeStackLineParser } from '@sentry/server-utils'; const INTEGRATION_NAME = 'NormalizePaths' as const; diff --git a/packages/deno/src/sdk.ts b/packages/deno/src/sdk.ts index 07d5e8366184..3c05526e5616 100644 --- a/packages/deno/src/sdk.ts +++ b/packages/deno/src/sdk.ts @@ -1,5 +1,5 @@ import type { Client, Integration, Options, StackParser } from '@sentry/core'; -import type { ServerRuntimeClientOptions } from '@sentry/core/server'; +import type { ServerRuntimeClientOptions } from '@sentry/server-utils'; import { createStackParser, dedupeIntegration, @@ -13,7 +13,7 @@ import { } from '@sentry/core'; import { getTracingIntegrations, getErrorIntegrations } from '@sentry/server-utils'; import { DenoClient } from './client'; -import { nodeStackLineParser } from '@sentry/core/server'; +import { nodeStackLineParser } from '@sentry/server-utils'; import { breadcrumbsIntegration } from './integrations/breadcrumbs'; import { denoContextIntegration } from './integrations/context'; import { contextLinesIntegration } from './integrations/contextlines'; diff --git a/packages/nextjs/src/common/index.ts b/packages/nextjs/src/common/index.ts index b9a652522349..978369eb86be 100644 --- a/packages/nextjs/src/common/index.ts +++ b/packages/nextjs/src/common/index.ts @@ -4,11 +4,9 @@ export { wrapAppGetInitialPropsWithSentry } from './pages-router-instrumentation export { wrapDocumentGetInitialPropsWithSentry } from './pages-router-instrumentation/wrapDocumentGetInitialPropsWithSentry'; export { wrapErrorGetInitialPropsWithSentry } from './pages-router-instrumentation/wrapErrorGetInitialPropsWithSentry'; export { wrapGetServerSidePropsWithSentry } from './pages-router-instrumentation/wrapGetServerSidePropsWithSentry'; -export { wrapServerComponentWithSentry } from './wrapServerComponentWithSentry'; -export { wrapRouteHandlerWithSentry } from './wrapRouteHandlerWithSentry'; export { wrapApiHandlerWithSentryVercelCrons } from './pages-router-instrumentation/wrapApiHandlerWithSentryVercelCrons'; -export { wrapMiddlewareWithSentry } from './wrapMiddlewareWithSentry'; export { wrapPageComponentWithSentry } from './pages-router-instrumentation/wrapPageComponentWithSentry'; -export { wrapGenerationFunctionWithSentry } from './wrapGenerationFunctionWithSentry'; -export { withServerActionInstrumentation } from './withServerActionInstrumentation'; -export { captureRequestError } from './captureRequestError'; + +// Server-only App-Router wrappers that pull in `./utils/responseEnd` live in `./serverOnlyExports` +// and are re-exported only from the server and edge entrypoints, keeping them out of the browser +// bundle. See that file for details. diff --git a/packages/nextjs/src/common/serverOnlyExports.ts b/packages/nextjs/src/common/serverOnlyExports.ts new file mode 100644 index 000000000000..238a6c8f4ba6 --- /dev/null +++ b/packages/nextjs/src/common/serverOnlyExports.ts @@ -0,0 +1,12 @@ +// These App-Router instrumentation wrappers transitively import `./utils/responseEnd` (its +// `flush`/`waitUntil` server-flush chain). They wrap server-only primitives (server components, +// route handlers, middleware, generation functions, server actions, `onRequestError`) that are +// never used from browser code, so they are kept out of the shared `common` barrel and re-exported +// only from the server and edge entrypoints — never from the client entry's `export * from +// '../common'`, which would pull them (and their server-only dependencies) into the browser bundle. +export { captureRequestError } from './captureRequestError'; +export { wrapServerComponentWithSentry } from './wrapServerComponentWithSentry'; +export { wrapRouteHandlerWithSentry } from './wrapRouteHandlerWithSentry'; +export { wrapMiddlewareWithSentry } from './wrapMiddlewareWithSentry'; +export { wrapGenerationFunctionWithSentry } from './wrapGenerationFunctionWithSentry'; +export { withServerActionInstrumentation } from './withServerActionInstrumentation'; diff --git a/packages/nextjs/src/common/utils/responseEnd.ts b/packages/nextjs/src/common/utils/responseEnd.ts index 31e3fa698433..48d75668160b 100644 --- a/packages/nextjs/src/common/utils/responseEnd.ts +++ b/packages/nextjs/src/common/utils/responseEnd.ts @@ -1,6 +1,5 @@ import type { Span } from '@sentry/core'; import { debug, fill, flush, GLOBAL_OBJ, setHttpStatus } from '@sentry/core'; -import { vercelWaitUntil } from '@sentry/core/server'; import type { ServerResponse } from 'http'; import { DEBUG_BUILD } from '../debug-build'; import type { ResponseEndMethod, WrappedResponseEndMethod } from '../types'; @@ -70,6 +69,35 @@ export function waitUntil(task: Promise): void { vercelWaitUntil(task); } +declare const EdgeRuntime: string | undefined; + +interface VercelRequestContextGlobal { + get?(): { waitUntil?: (task: Promise) => void } | undefined; +} + +/** + * Delays closing of a Vercel lambda until the provided task resolves. + * + * Inlined (rather than imported from `@sentry/server-utils`) because `responseEnd` is reachable from + * the browser bundle via the pages-router `_error` instrumentation, and every `@sentry/server-utils` + * entrypoint transitively pulls in `node:async_hooks`, which the client webpack build cannot resolve. + * Vendored from https://www.npmjs.com/package/@vercel/functions + */ +function vercelWaitUntil(task: Promise): void { + // We only flush manually in Vercel Edge runtime; in Node runtime we use `process.on('SIGTERM')`. + if (typeof EdgeRuntime !== 'string') { + return; + } + + const vercelRequestContextGlobal: VercelRequestContextGlobal | undefined = + // @ts-expect-error This is not typed + GLOBAL_OBJ[Symbol.for('@vercel/request-context')]; + + const ctx = vercelRequestContextGlobal?.get?.(); + + ctx?.waitUntil?.(task); +} + type MinimalCloudflareContext = { // eslint-disable-next-line @typescript-eslint/no-explicit-any waitUntil(promise: Promise): void; diff --git a/packages/nextjs/src/config/handleRunAfterProductionCompile.ts b/packages/nextjs/src/config/handleRunAfterProductionCompile.ts index ae4b0a91d84a..c742aba3dde1 100644 --- a/packages/nextjs/src/config/handleRunAfterProductionCompile.ts +++ b/packages/nextjs/src/config/handleRunAfterProductionCompile.ts @@ -1,5 +1,5 @@ import type { createSentryBuildPluginManager as createSentryBuildPluginManagerType } from '@sentry/bundler-plugins/core'; -import { loadModule } from '@sentry/core/server'; +import { loadModule } from '@sentry/server-utils'; import * as fs from 'fs'; import * as path from 'path'; import { getBuildLogger } from './buildLogger'; diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index 9f594973b41c..5e005ac1d27a 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -2,7 +2,7 @@ /* eslint-disable max-lines */ import { debug, escapeStringForRegex, parseSemver } from '@sentry/core'; -import { loadModule } from '@sentry/core/server'; +import { loadModule } from '@sentry/server-utils'; import * as fs from 'fs'; import { createRequire } from 'module'; import * as path from 'path'; diff --git a/packages/nextjs/src/edge/index.ts b/packages/nextjs/src/edge/index.ts index c971bbafec91..d07a3f6dd3f0 100644 --- a/packages/nextjs/src/edge/index.ts +++ b/packages/nextjs/src/edge/index.ts @@ -46,6 +46,8 @@ import { HTTP_SERVER, MIDDLEWARE } from '@sentry/conventions/op'; export * from '@sentry/vercel-edge'; export * from '../common'; +// Server-only wrappers kept out of the shared `common` barrel (and thus the browser bundle). +export * from '../common/serverOnlyExports'; export { captureUnderscoreErrorException } from '../common/pages-router-instrumentation/_error'; export { pinoIntegration } from '../common/pinoIntegrationShim'; diff --git a/packages/nextjs/src/server/index.ts b/packages/nextjs/src/server/index.ts index 33d417507b39..404ecea0a98c 100644 --- a/packages/nextjs/src/server/index.ts +++ b/packages/nextjs/src/server/index.ts @@ -293,4 +293,7 @@ function sdkAlreadyInitialized(): boolean { export * from '../common'; +// Server-only wrappers kept out of the shared `common` barrel (and thus the browser bundle). +export * from '../common/serverOnlyExports'; + export { wrapApiHandlerWithSentry } from '../common/pages-router-instrumentation/wrapApiHandlerWithSentry'; diff --git a/packages/nextjs/test/common/utils/responseEnd.test.ts b/packages/nextjs/test/common/utils/responseEnd.test.ts index 8b5c7a98dc19..543319cc8b68 100644 --- a/packages/nextjs/test/common/utils/responseEnd.test.ts +++ b/packages/nextjs/test/common/utils/responseEnd.test.ts @@ -1,5 +1,5 @@ import { GLOBAL_OBJ } from '@sentry/core'; -import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { waitUntil } from '../../../src/common/utils/responseEnd'; vi.mock('@sentry/core', async () => { @@ -13,91 +13,89 @@ vi.mock('@sentry/core', async () => { }; }); -vi.mock('@sentry/core/server', async () => { - const actual = await vi.importActual('@sentry/core/server'); - return { - ...actual, - vercelWaitUntil: vi.fn(), +const globalWithEdgeRuntime = globalThis as typeof globalThis & { EdgeRuntime?: string }; + +const CLOUDFLARE_CONTEXT_SYMBOL = Symbol.for('__cloudflare-context__'); +const VERCEL_CONTEXT_SYMBOL = Symbol.for('@vercel/request-context'); + +function setVercelWaitUntil(): ReturnType { + const vercelWaitUntilMock = vi.fn(); + (GLOBAL_OBJ as any)[VERCEL_CONTEXT_SYMBOL] = { + get: () => ({ waitUntil: vercelWaitUntilMock }), }; -}); + return vercelWaitUntilMock; +} + +function setCloudflareWaitUntil(): ReturnType { + const cfWaitUntilMock = vi.fn(); + (GLOBAL_OBJ as any)[CLOUDFLARE_CONTEXT_SYMBOL] = { + ctx: { waitUntil: cfWaitUntilMock }, + }; + return cfWaitUntilMock; +} describe('responseEnd utils', () => { + const originalEdgeRuntime = globalWithEdgeRuntime.EdgeRuntime; + beforeEach(() => { vi.clearAllMocks(); - // Clear Cloudflare context - const cfContextSymbol = Symbol.for('__cloudflare-context__'); - (GLOBAL_OBJ as any)[cfContextSymbol] = undefined; - // Clear Vercel context - const vercelContextSymbol = Symbol.for('@vercel/request-context'); - (GLOBAL_OBJ as any)[vercelContextSymbol] = undefined; + // `vercelWaitUntil` only acts in the Vercel Edge runtime, detected via the `EdgeRuntime` global. + globalWithEdgeRuntime.EdgeRuntime = 'edge-runtime'; + (GLOBAL_OBJ as any)[CLOUDFLARE_CONTEXT_SYMBOL] = undefined; + (GLOBAL_OBJ as any)[VERCEL_CONTEXT_SYMBOL] = undefined; + }); + + afterEach(() => { + if (originalEdgeRuntime === undefined) { + delete globalWithEdgeRuntime.EdgeRuntime; + } else { + globalWithEdgeRuntime.EdgeRuntime = originalEdgeRuntime; + } }); describe('waitUntil', () => { - it('should use cloudflareWaitUntil when Cloudflare context is available', async () => { - const cfContextSymbol = Symbol.for('__cloudflare-context__'); - const cfWaitUntilMock = vi.fn(); - (GLOBAL_OBJ as any)[cfContextSymbol] = { - ctx: { - waitUntil: cfWaitUntilMock, - }, - }; + it('should use cloudflareWaitUntil when Cloudflare context is available', () => { + const cfWaitUntilMock = setCloudflareWaitUntil(); + const vercelWaitUntilMock = setVercelWaitUntil(); const testTask = Promise.resolve('test'); waitUntil(testTask); expect(cfWaitUntilMock).toHaveBeenCalledWith(testTask); expect(cfWaitUntilMock).toHaveBeenCalledTimes(1); - - // Should not call vercelWaitUntil when Cloudflare is available - const { vercelWaitUntil } = await import('@sentry/core/server'); - expect(vercelWaitUntil).not.toHaveBeenCalled(); + // Should not use Vercel when Cloudflare is available + expect(vercelWaitUntilMock).not.toHaveBeenCalled(); }); - it('should use vercelWaitUntil when Cloudflare context is not available', async () => { - const { vercelWaitUntil } = await import('@sentry/core/server'); - const testTask = Promise.resolve('test'); + it('should use vercelWaitUntil when Cloudflare context is not available', () => { + const vercelWaitUntilMock = setVercelWaitUntil(); + const testTask = Promise.resolve('test'); waitUntil(testTask); - expect(vercelWaitUntil).toHaveBeenCalledWith(testTask); - expect(vercelWaitUntil).toHaveBeenCalledTimes(1); + expect(vercelWaitUntilMock).toHaveBeenCalledWith(testTask); + expect(vercelWaitUntilMock).toHaveBeenCalledTimes(1); }); - it('should prefer Cloudflare over Vercel when both are available', async () => { - // Set up Cloudflare context - const cfContextSymbol = Symbol.for('__cloudflare-context__'); - const cfWaitUntilMock = vi.fn(); - (GLOBAL_OBJ as any)[cfContextSymbol] = { - ctx: { - waitUntil: cfWaitUntilMock, - }, - }; - - // Set up Vercel context - const vercelWaitUntilMock = vi.fn(); - (GLOBAL_OBJ as any)[Symbol.for('@vercel/request-context')] = { - get: () => ({ waitUntil: vercelWaitUntilMock }), - }; + it('should prefer Cloudflare over Vercel when both are available', () => { + const cfWaitUntilMock = setCloudflareWaitUntil(); + const vercelWaitUntilMock = setVercelWaitUntil(); const testTask = Promise.resolve('test'); waitUntil(testTask); - // Should use Cloudflare expect(cfWaitUntilMock).toHaveBeenCalledWith(testTask); expect(cfWaitUntilMock).toHaveBeenCalledTimes(1); - - // Should not use Vercel - const { vercelWaitUntil } = await import('@sentry/core/server'); - expect(vercelWaitUntil).not.toHaveBeenCalled(); + expect(vercelWaitUntilMock).not.toHaveBeenCalled(); }); - it('should handle errors gracefully when waitUntil is called with a rejected promise', async () => { - const { vercelWaitUntil } = await import('@sentry/core/server'); + it('should handle errors gracefully when waitUntil is called with a rejected promise', () => { + const vercelWaitUntilMock = setVercelWaitUntil(); const testTask = Promise.reject(new Error('test error')); // Should not throw synchronously expect(() => waitUntil(testTask)).not.toThrow(); - expect(vercelWaitUntil).toHaveBeenCalledWith(testTask); + expect(vercelWaitUntilMock).toHaveBeenCalledWith(testTask); // Prevent unhandled rejection in test testTask.catch(() => {}); diff --git a/packages/nextjs/test/config/handleRunAfterProductionCompile.test.ts b/packages/nextjs/test/config/handleRunAfterProductionCompile.test.ts index ebff37a09c5a..d66a0d083663 100644 --- a/packages/nextjs/test/config/handleRunAfterProductionCompile.test.ts +++ b/packages/nextjs/test/config/handleRunAfterProductionCompile.test.ts @@ -1,4 +1,4 @@ -import { loadModule } from '@sentry/core/server'; +import { loadModule } from '@sentry/server-utils'; import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; @@ -11,7 +11,7 @@ import { } from '../../src/config/handleRunAfterProductionCompile'; import type { SentryBuildOptions } from '../../src/config/types'; -vi.mock('@sentry/core/server', () => ({ +vi.mock('@sentry/server-utils', () => ({ loadModule: vi.fn(), })); diff --git a/packages/nextjs/test/config/webpack/constructWebpackConfig.test.ts b/packages/nextjs/test/config/webpack/constructWebpackConfig.test.ts index 3e224c76947f..c4f17e5641f5 100644 --- a/packages/nextjs/test/config/webpack/constructWebpackConfig.test.ts +++ b/packages/nextjs/test/config/webpack/constructWebpackConfig.test.ts @@ -1,6 +1,6 @@ // mock helper functions not tested directly in this file import '../mocks'; -import * as coreServer from '@sentry/core/server'; +import * as serverUtils from '@sentry/server-utils'; import { describe, expect, it, vi } from 'vitest'; import * as getBuildPluginOptionsModule from '../../../src/config/getBuildPluginOptions'; import { @@ -24,7 +24,7 @@ vi.mock('@sentry/server-utils/orchestrion/webpack', async importOriginal => ({ describe('constructWebpackConfigFunction()', () => { it('includes expected properties', async () => { - vi.spyOn(coreServer, 'loadModule').mockImplementation(() => ({ + vi.spyOn(serverUtils, 'loadModule').mockImplementation(() => ({ sentryWebpackPlugin: () => ({ _name: 'sentry-webpack-plugin', }), @@ -62,7 +62,7 @@ describe('constructWebpackConfigFunction()', () => { it('automatically enables deleteSourcemapsAfterUpload for client builds when not explicitly set', async () => { const getBuildPluginOptionsSpy = vi.spyOn(getBuildPluginOptionsModule, 'getBuildPluginOptions'); - vi.spyOn(coreServer, 'loadModule').mockImplementation(() => ({ + vi.spyOn(serverUtils, 'loadModule').mockImplementation(() => ({ sentryWebpackPlugin: () => ({ _name: 'sentry-webpack-plugin', }), @@ -116,7 +116,7 @@ describe('constructWebpackConfigFunction()', () => { it('passes useRunAfterProductionCompileHook to getBuildPluginOptions when enabled', async () => { const getBuildPluginOptionsSpy = vi.spyOn(getBuildPluginOptionsModule, 'getBuildPluginOptions'); - vi.spyOn(coreServer, 'loadModule').mockImplementation(() => ({ + vi.spyOn(serverUtils, 'loadModule').mockImplementation(() => ({ sentryWebpackPlugin: () => ({ _name: 'sentry-webpack-plugin', }), @@ -142,7 +142,7 @@ describe('constructWebpackConfigFunction()', () => { it('passes useRunAfterProductionCompileHook to getBuildPluginOptions when disabled', async () => { const getBuildPluginOptionsSpy = vi.spyOn(getBuildPluginOptionsModule, 'getBuildPluginOptions'); - vi.spyOn(coreServer, 'loadModule').mockImplementation(() => ({ + vi.spyOn(serverUtils, 'loadModule').mockImplementation(() => ({ sentryWebpackPlugin: () => ({ _name: 'sentry-webpack-plugin', }), @@ -168,7 +168,7 @@ describe('constructWebpackConfigFunction()', () => { it('passes useRunAfterProductionCompileHook as undefined when not specified', async () => { const getBuildPluginOptionsSpy = vi.spyOn(getBuildPluginOptionsModule, 'getBuildPluginOptions'); - vi.spyOn(coreServer, 'loadModule').mockImplementation(() => ({ + vi.spyOn(serverUtils, 'loadModule').mockImplementation(() => ({ sentryWebpackPlugin: () => ({ _name: 'sentry-webpack-plugin', }), @@ -230,7 +230,7 @@ describe('constructWebpackConfigFunction()', () => { }); it('uses `hidden-source-map` as `devtool` value for client-side builds', async () => { - vi.spyOn(coreServer, 'loadModule').mockImplementation(() => ({ + vi.spyOn(serverUtils, 'loadModule').mockImplementation(() => ({ sentryWebpackPlugin: () => ({ _name: 'sentry-webpack-plugin', }), @@ -294,7 +294,7 @@ describe('constructWebpackConfigFunction()', () => { describe('treeshaking flags', () => { it('does not add DefinePlugin when treeshake option is not set', async () => { - vi.spyOn(coreServer, 'loadModule').mockImplementation(() => ({ + vi.spyOn(serverUtils, 'loadModule').mockImplementation(() => ({ sentryWebpackPlugin: () => ({ _name: 'sentry-webpack-plugin', }), @@ -322,7 +322,7 @@ describe('constructWebpackConfigFunction()', () => { }); it('does not add DefinePlugin when treeshake option is empty object', async () => { - vi.spyOn(coreServer, 'loadModule').mockImplementation(() => ({ + vi.spyOn(serverUtils, 'loadModule').mockImplementation(() => ({ sentryWebpackPlugin: () => ({ _name: 'sentry-webpack-plugin', }), @@ -354,7 +354,7 @@ describe('constructWebpackConfigFunction()', () => { }); it('adds __SENTRY_DEBUG__ flag when debugLogging is true', async () => { - vi.spyOn(coreServer, 'loadModule').mockImplementation(() => ({ + vi.spyOn(serverUtils, 'loadModule').mockImplementation(() => ({ sentryWebpackPlugin: () => ({ _name: 'sentry-webpack-plugin', }), @@ -382,7 +382,7 @@ describe('constructWebpackConfigFunction()', () => { }); it('adds __SENTRY_TRACING__ flag when tracing is true', async () => { - vi.spyOn(coreServer, 'loadModule').mockImplementation(() => ({ + vi.spyOn(serverUtils, 'loadModule').mockImplementation(() => ({ sentryWebpackPlugin: () => ({ _name: 'sentry-webpack-plugin', }), @@ -410,7 +410,7 @@ describe('constructWebpackConfigFunction()', () => { }); it('adds __RRWEB_EXCLUDE_IFRAME__ flag when excludeReplayIframe is true', async () => { - vi.spyOn(coreServer, 'loadModule').mockImplementation(() => ({ + vi.spyOn(serverUtils, 'loadModule').mockImplementation(() => ({ sentryWebpackPlugin: () => ({ _name: 'sentry-webpack-plugin', }), @@ -439,7 +439,7 @@ describe('constructWebpackConfigFunction()', () => { }); it('adds __RRWEB_EXCLUDE_SHADOW_DOM__ flag when excludeReplayShadowDOM is true', async () => { - vi.spyOn(coreServer, 'loadModule').mockImplementation(() => ({ + vi.spyOn(serverUtils, 'loadModule').mockImplementation(() => ({ sentryWebpackPlugin: () => ({ _name: 'sentry-webpack-plugin', }), @@ -468,7 +468,7 @@ describe('constructWebpackConfigFunction()', () => { }); it('adds __SENTRY_EXCLUDE_REPLAY_WORKER__ flag when excludeReplayCompressionWorker is true', async () => { - vi.spyOn(coreServer, 'loadModule').mockImplementation(() => ({ + vi.spyOn(serverUtils, 'loadModule').mockImplementation(() => ({ sentryWebpackPlugin: () => ({ _name: 'sentry-webpack-plugin', }), @@ -498,7 +498,7 @@ describe('constructWebpackConfigFunction()', () => { }); it('adds all flags when all treeshake options are enabled', async () => { - vi.spyOn(coreServer, 'loadModule').mockImplementation(() => ({ + vi.spyOn(serverUtils, 'loadModule').mockImplementation(() => ({ sentryWebpackPlugin: () => ({ _name: 'sentry-webpack-plugin', }), @@ -544,7 +544,7 @@ describe('constructWebpackConfigFunction()', () => { }); it('does not add flags when treeshake options are false', async () => { - vi.spyOn(coreServer, 'loadModule').mockImplementation(() => ({ + vi.spyOn(serverUtils, 'loadModule').mockImplementation(() => ({ sentryWebpackPlugin: () => ({ _name: 'sentry-webpack-plugin', }), @@ -582,7 +582,7 @@ describe('constructWebpackConfigFunction()', () => { }); it('works for client builds', async () => { - vi.spyOn(coreServer, 'loadModule').mockImplementation(() => ({ + vi.spyOn(serverUtils, 'loadModule').mockImplementation(() => ({ sentryWebpackPlugin: () => ({ _name: 'sentry-webpack-plugin', }), @@ -617,7 +617,7 @@ describe('constructWebpackConfigFunction()', () => { }); it('works for edge builds', async () => { - vi.spyOn(coreServer, 'loadModule').mockImplementation(() => ({ + vi.spyOn(serverUtils, 'loadModule').mockImplementation(() => ({ sentryWebpackPlugin: () => ({ _name: 'sentry-webpack-plugin', }), @@ -653,7 +653,7 @@ describe('constructWebpackConfigFunction()', () => { }); it('only adds flags for enabled options', async () => { - vi.spyOn(coreServer, 'loadModule').mockImplementation(() => ({ + vi.spyOn(serverUtils, 'loadModule').mockImplementation(() => ({ sentryWebpackPlugin: () => ({ _name: 'sentry-webpack-plugin', }), diff --git a/packages/nextjs/test/config/wrappers.test.ts b/packages/nextjs/test/config/wrappers.test.ts index 7d5f4029bd94..7588348ba549 100644 --- a/packages/nextjs/test/config/wrappers.test.ts +++ b/packages/nextjs/test/config/wrappers.test.ts @@ -2,11 +2,8 @@ import type { Client } from '@sentry/core'; import * as SentryCore from '@sentry/core'; import type { IncomingMessage, ServerResponse } from 'http'; import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; -import { - wrapGetInitialPropsWithSentry, - wrapGetServerSidePropsWithSentry, - wrapMiddlewareWithSentry, -} from '../../src/common'; +import { wrapGetInitialPropsWithSentry, wrapGetServerSidePropsWithSentry } from '../../src/common'; +import { wrapMiddlewareWithSentry } from '../../src/common/serverOnlyExports'; import type { EdgeRouteHandler } from '../../src/edge/types'; const startSpanManualSpy = vi.spyOn(SentryCore, 'startSpanManual'); diff --git a/packages/nitro/src/runtime/hooks/captureErrorHook.ts b/packages/nitro/src/runtime/hooks/captureErrorHook.ts index 2f7a602f3af1..d8366d813c35 100644 --- a/packages/nitro/src/runtime/hooks/captureErrorHook.ts +++ b/packages/nitro/src/runtime/hooks/captureErrorHook.ts @@ -1,5 +1,5 @@ import { captureException, getClient, parseUrl } from '@sentry/core'; -import { flushIfServerless } from '@sentry/core/server'; +import { flushIfServerless } from '@sentry/server-utils'; import { HTTPError } from 'h3'; import type { CapturedErrorContext } from 'nitro/types'; diff --git a/packages/nitro/src/runtime/hooks/captureStorageEvents.ts b/packages/nitro/src/runtime/hooks/captureStorageEvents.ts index 066ec79bad45..bbb8bef169ae 100644 --- a/packages/nitro/src/runtime/hooks/captureStorageEvents.ts +++ b/packages/nitro/src/runtime/hooks/captureStorageEvents.ts @@ -12,7 +12,7 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, startInactiveSpan, } from '@sentry/core'; -import { flushIfServerless } from '@sentry/core/server'; +import { flushIfServerless } from '@sentry/server-utils'; import { bindTracingChannelToSpan } from '@sentry/server-utils'; import type { TraceContext } from 'unstorage/tracing'; diff --git a/packages/nitro/test/runtime/hooks/captureErrorHook.test.ts b/packages/nitro/test/runtime/hooks/captureErrorHook.test.ts index f83395b6c7db..9b07a9e9b9f4 100644 --- a/packages/nitro/test/runtime/hooks/captureErrorHook.test.ts +++ b/packages/nitro/test/runtime/hooks/captureErrorHook.test.ts @@ -1,5 +1,5 @@ import * as SentryCore from '@sentry/core'; -import * as SentryCoreServer from '@sentry/core/server'; +import * as serverUtils from '@sentry/server-utils'; import { HTTPError } from 'h3'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { captureErrorHook } from '../../../src/runtime/hooks/captureErrorHook'; @@ -16,13 +16,9 @@ vi.mock('@sentry/core', async importOriginal => { }; }); -vi.mock('@sentry/core/server', async importOriginal => { - const mod = await importOriginal(); - return { - ...(mod as any), - flushIfServerless: vi.fn(), - }; -}); +vi.mock('@sentry/server-utils', () => ({ + flushIfServerless: vi.fn(), +})); describe('captureErrorHook', () => { const mockErrorContext = { @@ -36,7 +32,7 @@ describe('captureErrorHook', () => { (SentryCore.getClient as any).mockReturnValue({ getOptions: () => ({}), }); - (SentryCoreServer.flushIfServerless as any).mockResolvedValue(undefined); + (serverUtils.flushIfServerless as any).mockResolvedValue(undefined); }); it('should capture regular errors', async () => { @@ -115,7 +111,7 @@ describe('captureErrorHook', () => { await captureErrorHook(error, mockErrorContext); - expect(SentryCoreServer.flushIfServerless).toHaveBeenCalled(); + expect(serverUtils.flushIfServerless).toHaveBeenCalled(); }); it('should handle missing event in error context', async () => { diff --git a/packages/node-native/package.json b/packages/node-native/package.json index c0c3492f7f46..4a4c679afda4 100644 --- a/packages/node-native/package.json +++ b/packages/node-native/package.json @@ -57,7 +57,8 @@ "dependencies": { "@sentry/node-native-stacktrace": "^0.5.1", "@sentry/core": "10.67.0", - "@sentry/node": "10.67.0" + "@sentry/node": "10.67.0", + "@sentry/server-utils": "10.67.0" }, "devDependencies": { "@types/node": "^18.19.1" diff --git a/packages/node-native/src/event-loop-block-watchdog.ts b/packages/node-native/src/event-loop-block-watchdog.ts index 0145a1ff7809..f23d3840ffd6 100644 --- a/packages/node-native/src/event-loop-block-watchdog.ts +++ b/packages/node-native/src/event-loop-block-watchdog.ts @@ -14,7 +14,7 @@ import { updateSession, uuid4, } from '@sentry/core'; -import { filenameIsInApp } from '@sentry/core/server'; +import { filenameIsInApp } from '@sentry/server-utils'; import { makeNodeTransport } from '@sentry/node'; import { captureStackTrace, getThreadsLastSeen } from '@sentry/node-native-stacktrace'; import type { ThreadState, WorkerStartData } from './common'; diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 7eafc647ae0d..32d1551e6be7 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -154,7 +154,8 @@ export { featureFlagsIntegration, spanStreamingIntegration, } from '@sentry/core'; -export { trpcMiddleware, wrapMcpServerWithSentry } from '@sentry/core/server'; +export { wrapMcpServerWithSentry } from '@sentry/core/server'; +export { trpcMiddleware } from '@sentry/server-utils'; export type { Breadcrumb, diff --git a/packages/node/src/integrations/anr/worker.ts b/packages/node/src/integrations/anr/worker.ts index fa6054c850e6..f9c671190979 100644 --- a/packages/node/src/integrations/anr/worker.ts +++ b/packages/node/src/integrations/anr/worker.ts @@ -13,7 +13,7 @@ import { updateSession, uuid4, } from '@sentry/core'; -import { callFrameToStackFrame, watchdogTimer } from '@sentry/core/server'; +import { callFrameToStackFrame, watchdogTimer } from '@sentry/server-utils'; import { makeNodeTransport } from '../../transports'; import { createGetModuleFromFilename } from '../../utils/module'; import type { WorkerStartData } from './common'; diff --git a/packages/node/src/sdk/api.ts b/packages/node/src/sdk/api.ts index f3d02265ea4d..90b5a8dbd55c 100644 --- a/packages/node/src/sdk/api.ts +++ b/packages/node/src/sdk/api.ts @@ -2,7 +2,7 @@ import type { StackParser } from '@sentry/core'; import { createStackParser, GLOBAL_OBJ } from '@sentry/core'; -import { nodeStackLineParser } from '@sentry/core/server'; +import { nodeStackLineParser } from '@sentry/server-utils'; import { createGetModuleFromFilename } from '../utils/module'; /** diff --git a/packages/node/src/sdk/client.ts b/packages/node/src/sdk/client.ts index 76a4323f3f75..a81e2fd5cb5b 100644 --- a/packages/node/src/sdk/client.ts +++ b/packages/node/src/sdk/client.ts @@ -1,7 +1,7 @@ import * as os from 'node:os'; import type { Tracer } from '@opentelemetry/api'; import { trace } from '@opentelemetry/api'; -import type { ServerRuntimeClientOptions } from '@sentry/core/server'; +import type { ServerRuntimeClientOptions } from '@sentry/server-utils'; import { _INTERNAL_clearAiProviderSkips, _INTERNAL_flushLogsBuffer, @@ -10,7 +10,7 @@ import { debug, SDK_VERSION, } from '@sentry/core'; -import { ServerRuntimeClient } from '@sentry/core/server'; +import { ServerRuntimeClient } from '@sentry/server-utils'; import { type AsyncLocalStorageLookup, registerPrepareSpanScope, diff --git a/packages/node/src/types.ts b/packages/node/src/types.ts index 220c194c6546..810ab3f630ab 100644 --- a/packages/node/src/types.ts +++ b/packages/node/src/types.ts @@ -1,5 +1,5 @@ import type { ClientOptions, Options, Scope } from '@sentry/core'; -import type { ServerRuntimeOptions } from '@sentry/core/server'; +import type { ServerRuntimeOptions } from '@sentry/server-utils'; import type { NodeTransportOptions } from './transports'; /** diff --git a/packages/nuxt/src/runtime/hooks/captureErrorHook.ts b/packages/nuxt/src/runtime/hooks/captureErrorHook.ts index 50d5a61a2828..111f7c8d7bbd 100644 --- a/packages/nuxt/src/runtime/hooks/captureErrorHook.ts +++ b/packages/nuxt/src/runtime/hooks/captureErrorHook.ts @@ -1,5 +1,5 @@ import { captureException, getClient, getCurrentScope } from '@sentry/core'; -import { flushIfServerless } from '@sentry/core/server'; +import { flushIfServerless } from '@sentry/server-utils'; // eslint-disable-next-line import/no-extraneous-dependencies import { H3Error } from 'h3'; import type { CapturedErrorContext } from 'nitropack/types'; diff --git a/packages/nuxt/src/runtime/hooks/wrapMiddlewareHandler.ts b/packages/nuxt/src/runtime/hooks/wrapMiddlewareHandler.ts index f5adf02ea0eb..d54f2f8a6658 100644 --- a/packages/nuxt/src/runtime/hooks/wrapMiddlewareHandler.ts +++ b/packages/nuxt/src/runtime/hooks/wrapMiddlewareHandler.ts @@ -11,7 +11,7 @@ import { type SpanAttributes, startSpan, } from '@sentry/core'; -import { flushIfServerless } from '@sentry/core/server'; +import { flushIfServerless } from '@sentry/server-utils'; import type { _ResponseMiddleware as ResponseMiddleware, EventHandler, diff --git a/packages/nuxt/src/runtime/utils/instrumentDatabase.ts b/packages/nuxt/src/runtime/utils/instrumentDatabase.ts index 9bb3d25e4fe1..8c1946c228fc 100644 --- a/packages/nuxt/src/runtime/utils/instrumentDatabase.ts +++ b/packages/nuxt/src/runtime/utils/instrumentDatabase.ts @@ -13,7 +13,8 @@ import { startSpan, type StartSpanOptions, } from '@sentry/core'; -import { _INTERNAL_getSqlQuerySummary, _INTERNAL_sanitizeSqlQuery, flushIfServerless } from '@sentry/core/server'; +import { _INTERNAL_getSqlQuerySummary, _INTERNAL_sanitizeSqlQuery } from '@sentry/core/server'; +import { flushIfServerless } from '@sentry/server-utils'; import type { Database, PreparedStatement } from 'db0'; import { type DatabaseConnectionConfig, type DatabaseSpanData, getDatabaseSpanData } from './database-span-data'; import { DB_NAMESPACE, DB_QUERY_SUMMARY, DB_QUERY_TEXT, DB_SYSTEM_NAME } from '@sentry/conventions/attributes'; diff --git a/packages/nuxt/src/runtime/utils/instrumentStorage.ts b/packages/nuxt/src/runtime/utils/instrumentStorage.ts index 621dc3024c1f..76c83a17c4e2 100644 --- a/packages/nuxt/src/runtime/utils/instrumentStorage.ts +++ b/packages/nuxt/src/runtime/utils/instrumentStorage.ts @@ -16,7 +16,7 @@ import { startSpan, type StartSpanOptions, } from '@sentry/core'; -import { flushIfServerless } from '@sentry/core/server'; +import { flushIfServerless } from '@sentry/server-utils'; import type { Driver, Storage } from 'unstorage'; /** diff --git a/packages/nuxt/src/runtime/utils/patchEventHandler.ts b/packages/nuxt/src/runtime/utils/patchEventHandler.ts index 9349e29d1add..77a050dacf42 100644 --- a/packages/nuxt/src/runtime/utils/patchEventHandler.ts +++ b/packages/nuxt/src/runtime/utils/patchEventHandler.ts @@ -1,5 +1,5 @@ import { debug, getDefaultIsolationScope, getIsolationScope, withIsolationScope } from '@sentry/core'; -import { flushIfServerless } from '@sentry/core/server'; +import { flushIfServerless } from '@sentry/server-utils'; /** * Patches the H3 event handler of Nitro. diff --git a/packages/nuxt/test/runtime/hooks/captureErrorHook.test.ts b/packages/nuxt/test/runtime/hooks/captureErrorHook.test.ts index 8e166a5ff4cc..1591a5acc671 100644 --- a/packages/nuxt/test/runtime/hooks/captureErrorHook.test.ts +++ b/packages/nuxt/test/runtime/hooks/captureErrorHook.test.ts @@ -1,5 +1,5 @@ import * as SentryCore from '@sentry/core'; -import * as SentryCoreServer from '@sentry/core/server'; +import * as serverUtils from '@sentry/server-utils'; import { H3Error } from 'h3'; import type { CapturedErrorContext } from 'nitropack/types'; import { beforeEach, describe, expect, it, vi } from 'vitest'; @@ -17,13 +17,9 @@ vi.mock('@sentry/core', async importOriginal => { }; }); -vi.mock('@sentry/core/server', async importOriginal => { - const mod = await importOriginal(); - return { - ...(mod as any), - flushIfServerless: vi.fn(), - }; -}); +vi.mock('@sentry/server-utils', () => ({ + flushIfServerless: vi.fn(), +})); vi.mock('../../../src/runtime/utils', () => ({ extractErrorContext: vi.fn(() => ({ test: 'context' })), @@ -42,7 +38,7 @@ describe('sentryCaptureErrorHook', () => { (SentryCore.getClient as any).mockReturnValue({ getOptions: () => ({}), }); - (SentryCoreServer.flushIfServerless as any).mockResolvedValue(undefined); + (serverUtils.flushIfServerless as any).mockResolvedValue(undefined); }); it('should capture regular errors', async () => { diff --git a/packages/nuxt/test/runtime/hooks/wrapMiddlewareHandler.test.ts b/packages/nuxt/test/runtime/hooks/wrapMiddlewareHandler.test.ts index 04d61994579e..b691c2d9bd5a 100644 --- a/packages/nuxt/test/runtime/hooks/wrapMiddlewareHandler.test.ts +++ b/packages/nuxt/test/runtime/hooks/wrapMiddlewareHandler.test.ts @@ -1,5 +1,5 @@ import * as SentryCore from '@sentry/core'; -import * as SentryCoreServer from '@sentry/core/server'; +import * as serverUtils from '@sentry/server-utils'; import type { EventHandler, EventHandlerRequest, H3Event } from 'h3'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { wrapMiddlewareHandlerWithSentry } from '../../../src/runtime/hooks/wrapMiddlewareHandler'; @@ -17,13 +17,9 @@ vi.mock('@sentry/core', async importOriginal => { }; }); -vi.mock('@sentry/core/server', async importOriginal => { - const mod = await importOriginal(); - return { - ...(mod as any), - flushIfServerless: vi.fn(), - }; -}); +vi.mock('@sentry/server-utils', () => ({ + flushIfServerless: vi.fn(), +})); describe('wrapMiddlewareHandlerWithSentry', () => { const mockEvent: H3Event = { @@ -63,7 +59,7 @@ describe('wrapMiddlewareHandlerWithSentry', () => { }), }); (SentryCore.httpHeadersToSpanAttributes as any).mockReturnValue({ 'http.request.header.user_agent': 'test-agent' }); - (SentryCoreServer.flushIfServerless as any).mockResolvedValue(undefined); + (serverUtils.flushIfServerless as any).mockResolvedValue(undefined); }); describe('function handler wrapping', () => { diff --git a/packages/react-router/src/server/createSentryHandleError.ts b/packages/react-router/src/server/createSentryHandleError.ts index 481cbfab1b92..ff3893da60c7 100644 --- a/packages/react-router/src/server/createSentryHandleError.ts +++ b/packages/react-router/src/server/createSentryHandleError.ts @@ -1,5 +1,5 @@ import { captureException } from '@sentry/core'; -import { flushIfServerless } from '@sentry/core/server'; +import { flushIfServerless } from '@sentry/server-utils'; import type { HandleErrorFunction } from 'react-router'; export type SentryHandleErrorOptions = { diff --git a/packages/react-router/src/server/createServerInstrumentation.ts b/packages/react-router/src/server/createServerInstrumentation.ts index 436ade2dd6d9..18839cae0f6e 100644 --- a/packages/react-router/src/server/createServerInstrumentation.ts +++ b/packages/react-router/src/server/createServerInstrumentation.ts @@ -22,7 +22,7 @@ import { updateSpanName, filterCollectedUrl, } from '@sentry/core'; -import { flushIfServerless } from '@sentry/core/server'; +import { flushIfServerless } from '@sentry/server-utils'; import type { ServerInstrumentation } from 'react-router'; import { DEBUG_BUILD } from '../common/debug-build'; import { captureInstrumentationError, getPathFromRequest, getPattern, normalizeRoutePath } from '../common/utils'; diff --git a/packages/react-router/src/server/wrapSentryHandleRequest.ts b/packages/react-router/src/server/wrapSentryHandleRequest.ts index 8a4af55ce62e..ae4df4083148 100644 --- a/packages/react-router/src/server/wrapSentryHandleRequest.ts +++ b/packages/react-router/src/server/wrapSentryHandleRequest.ts @@ -6,7 +6,7 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, updateSpanName, } from '@sentry/core'; -import { flushIfServerless } from '@sentry/core/server'; +import { flushIfServerless } from '@sentry/server-utils'; import type { AppLoadContext, EntryContext, RouterContextProvider } from 'react-router'; import { isInstrumentationApiUsed } from './serverGlobals'; diff --git a/packages/react-router/test/server/createSentryHandleError.test.ts b/packages/react-router/test/server/createSentryHandleError.test.ts index e93e60228684..3af557288249 100644 --- a/packages/react-router/test/server/createSentryHandleError.test.ts +++ b/packages/react-router/test/server/createSentryHandleError.test.ts @@ -1,5 +1,5 @@ import * as core from '@sentry/core'; -import * as coreServer from '@sentry/core/server'; +import * as serverUtils from '@sentry/server-utils'; import type { ActionFunctionArgs, LoaderFunctionArgs } from 'react-router'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { createSentryHandleError } from '../../src/server/createSentryHandleError'; @@ -8,7 +8,7 @@ vi.mock('@sentry/core', () => ({ captureException: vi.fn(), })); -vi.mock('@sentry/core/server', () => ({ +vi.mock('@sentry/server-utils', () => ({ flushIfServerless: vi.fn().mockResolvedValue(undefined), })); @@ -19,7 +19,7 @@ const mechanism = { describe('createSentryHandleError', () => { const mockCaptureException = vi.mocked(core.captureException); - const mockFlushIfServerless = vi.mocked(coreServer.flushIfServerless); + const mockFlushIfServerless = vi.mocked(serverUtils.flushIfServerless); const mockConsoleError = vi.spyOn(console, 'error').mockImplementation(() => {}); const mockError = new Error('Test error'); diff --git a/packages/react-router/test/server/createServerInstrumentation.test.ts b/packages/react-router/test/server/createServerInstrumentation.test.ts index c7b82679430e..d3e4ccf1b3e7 100644 --- a/packages/react-router/test/server/createServerInstrumentation.test.ts +++ b/packages/react-router/test/server/createServerInstrumentation.test.ts @@ -1,6 +1,6 @@ import { URL_FULL, URL_PATH } from '@sentry/conventions/attributes'; import * as core from '@sentry/core'; -import * as coreServer from '@sentry/core/server'; +import * as serverUtils from '@sentry/server-utils'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { createSentryServerInstrumentation, @@ -24,7 +24,7 @@ vi.mock('@sentry/core', async () => { }; }); -vi.mock('@sentry/core/server', () => ({ +vi.mock('@sentry/server-utils', () => ({ flushIfServerless: vi.fn(), })); @@ -150,7 +150,7 @@ describe('createSentryServerInstrumentation', () => { [URL_PATH]: '/test-path', }); expect(mockHandleRequest).toHaveBeenCalled(); - expect(coreServer.flushIfServerless).toHaveBeenCalled(); + expect(serverUtils.flushIfServerless).toHaveBeenCalled(); }); it('should create own root span when no active span exists', async () => { @@ -185,7 +185,7 @@ describe('createSentryServerInstrumentation', () => { expect.any(Function), ); expect(mockHandleRequest).toHaveBeenCalled(); - expect(coreServer.flushIfServerless).toHaveBeenCalled(); + expect(serverUtils.flushIfServerless).toHaveBeenCalled(); }); it('should capture errors and set span status when root span exists', async () => { @@ -258,7 +258,7 @@ describe('createSentryServerInstrumentation', () => { // Handler should still be called even if URL parsing fails expect(mockHandleRequest).toHaveBeenCalled(); - expect(coreServer.flushIfServerless).toHaveBeenCalled(); + expect(serverUtils.flushIfServerless).toHaveBeenCalled(); }); it('should handle relative URLs by using a dummy base', async () => { diff --git a/packages/react-router/test/server/wrapSentryHandleRequest.test.ts b/packages/react-router/test/server/wrapSentryHandleRequest.test.ts index c48bedef8181..849679b1380a 100644 --- a/packages/react-router/test/server/wrapSentryHandleRequest.test.ts +++ b/packages/react-router/test/server/wrapSentryHandleRequest.test.ts @@ -1,7 +1,7 @@ import { PassThrough } from 'node:stream'; import { SENTRY_SEGMENT_NAME_SOURCE, HTTP_ROUTE } from '@sentry/conventions/attributes'; import { getActiveSpan, getRootSpan, getTraceMetaTags, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core'; -import { flushIfServerless } from '@sentry/core/server'; +import { flushIfServerless } from '@sentry/server-utils'; import { beforeEach, describe, expect, test, vi } from 'vitest'; import { getMetaTagTransformer } from '../../src/server/getMetaTagTransformer'; import { wrapSentryHandleRequest } from '../../src/server/wrapSentryHandleRequest'; @@ -16,7 +16,7 @@ vi.mock('@sentry/core', () => ({ GLOBAL_OBJ: globalThis, })); -vi.mock('@sentry/core/server', () => ({ +vi.mock('@sentry/server-utils', () => ({ flushIfServerless: vi.fn(), })); diff --git a/packages/remix/src/cloudflare/index.ts b/packages/remix/src/cloudflare/index.ts index 3436abae61f2..a0f4276f0c95 100644 --- a/packages/remix/src/cloudflare/index.ts +++ b/packages/remix/src/cloudflare/index.ts @@ -120,4 +120,4 @@ export { withStreamedSpan, featureFlagsIntegration, } from '@sentry/core'; -export { trpcMiddleware } from '@sentry/core/server'; +export { trpcMiddleware } from '@sentry/server-utils'; diff --git a/packages/remix/src/server/instrumentServer.ts b/packages/remix/src/server/instrumentServer.ts index a336a7f72c2a..9010010900c7 100644 --- a/packages/remix/src/server/instrumentServer.ts +++ b/packages/remix/src/server/instrumentServer.ts @@ -34,7 +34,8 @@ import { withIsolationScope, filterCollectedUrl, } from '@sentry/core'; -import { isNodeEnv, loadModule } from '@sentry/core/server'; +import { isNodeEnv } from '@sentry/core/server'; +import { loadModule } from '@sentry/server-utils'; import { DEBUG_BUILD } from '../utils/debug-build'; import { createRoutes, getTransactionName, isCloudflareEnv } from '../utils/utils'; import { extractData, isResponse, json } from '../utils/vendor/response'; diff --git a/packages/server-utils/src/exports.ts b/packages/server-utils/src/exports.ts index 10e0f23e0989..856a9da14e51 100644 --- a/packages/server-utils/src/exports.ts +++ b/packages/server-utils/src/exports.ts @@ -1,5 +1,14 @@ // Shared exports not using diagnostics channels export { setHttpServerSpanRouteAttribute } from './utils/setHttpServerSpanRouteAttribute'; +export { vercelWaitUntil } from './utils/vercelWaitUntil'; +export { flushIfServerless } from './utils/flushIfServerless'; +export { loadModule } from './utils/loadModule'; +export { callFrameToStackFrame, watchdogTimer } from './utils/anr'; +export { filenameIsInApp, node, nodeStackLineParser } from './utils/node-stack-trace'; +export { ServerRuntimeClient } from './server-runtime-client'; +export type { ServerRuntimeClientOptions } from './server-runtime-client'; +export type { ServerRuntimeOptions } from './types/options'; +export { trpcMiddleware } from './trpc'; export { setAsyncLocalStorageAsyncContextStrategy } from './async-context'; export { openTelemetryIntegration, getOtlpTracesEndpoint } from './opentelemetry'; export * from './ai'; diff --git a/packages/server-utils/src/index.ts b/packages/server-utils/src/index.ts index e772e02c5c3d..38c2f6dc9480 100644 --- a/packages/server-utils/src/index.ts +++ b/packages/server-utils/src/index.ts @@ -4,7 +4,8 @@ export * from './exports'; export { detectOrchestrionSetup } from './orchestrion/detect'; // oxlint-disable-next-line typescript/no-deprecated -- re-exported so the deprecated `setupKoaErrorHandler` can delegate export { attachKoaErrorHandler } from './integrations/koa/koa-error-handler'; -import { attachHapiErrorHandler as _attachHapiErrorHandler } from './integrations/hapi/hapi-error-handler'; +// oxlint-disable-next-line typescript/no-deprecated -- re-exported so the deprecated `setupHapiErrorHandler` can delegate +export { attachHapiErrorHandler } from './integrations/hapi/hapi-error-handler'; export { bindTracingChannelToSpan } from './tracing-channel'; export type { TracingChannelPayloadWithSpan } from './tracing-channel'; export type { InstrumentationConfig } from './orchestrion/apmTypes'; @@ -19,11 +20,6 @@ export { setupFastifyErrorHandler, } from './integrations/fastify'; -/** - * @deprecated This is a temporary export to avoid breaking changes. It will be removed in the next major version. - */ -export const attachHapiErrorHandler = _attachHapiErrorHandler; - // Integrations export { prismaIntegration } from './integrations/prisma'; export { amqplibIntegration } from './integrations/amqplib'; diff --git a/packages/server-utils/src/integrations/hapi/hapi-error-handler.ts b/packages/server-utils/src/integrations/hapi/hapi-error-handler.ts index d79b35b4502a..f358d227ee61 100644 --- a/packages/server-utils/src/integrations/hapi/hapi-error-handler.ts +++ b/packages/server-utils/src/integrations/hapi/hapi-error-handler.ts @@ -49,6 +49,11 @@ function isErrorEvent(event: HapiRequestEvent): boolean { * predicate is left untouched; when provided, it overrides whatever was set * before — so the integration's configured predicate wins over a prior * default-valued attach, regardless of ordering. + * + * @deprecated Internal. The error handler is registered automatically by the hapi + * instrumentation; there is no need to call this directly. It is exported only + * so the deprecated `setupHapiErrorHandler` can delegate to it, and will be + * removed in a future major version. */ export function attachHapiErrorHandler(server: HapiServer, shouldHandleError?: HapiShouldHandleError): void { const events = server?.events as MarkedServerEvents | undefined; diff --git a/packages/server-utils/src/integrations/hapi/index.ts b/packages/server-utils/src/integrations/hapi/index.ts index 70c462ca0324..7c98f7ab004e 100644 --- a/packages/server-utils/src/integrations/hapi/index.ts +++ b/packages/server-utils/src/integrations/hapi/index.ts @@ -4,6 +4,7 @@ import { defineIntegration } from '@sentry/core'; import { CHANNELS } from '../../orchestrion/channels'; import { hapiModuleNames } from '../../orchestrion/config/hapi'; import { invokeOrchestrionInstrumentation } from '../../orchestrion/instrumentation'; +// oxlint-disable-next-line typescript/no-deprecated import { attachHapiErrorHandler } from './hapi-error-handler'; import type { HapiServer, HapiShouldHandleError } from './hapi-types'; import { wrapExtArguments, wrapRouteArguments } from './hapi-utils'; @@ -102,6 +103,7 @@ function instrumentHapi(shouldHandleError?: HapiShouldHandleError): void { start(rawCtx: unknown) { const server = (rawCtx as HapiServerContext).self; if (server) { + // oxlint-disable-next-line typescript/no-deprecated -- internal delegation to the shared implementation attachHapiErrorHandler(server, shouldHandleError); } }, diff --git a/packages/core/src/server-runtime-client.ts b/packages/server-utils/src/server-runtime-client.ts similarity index 86% rename from packages/core/src/server-runtime-client.ts rename to packages/server-utils/src/server-runtime-client.ts index 159c4aeb3b93..50c9dbd6e4b7 100644 --- a/packages/core/src/server-runtime-client.ts +++ b/packages/server-utils/src/server-runtime-client.ts @@ -1,26 +1,35 @@ -import { createCheckInEnvelope } from './checkin'; -import { Client } from './client'; -import { getIsolationScope } from './currentScopes'; -import { DEBUG_BUILD } from './debug-build'; import { + addUserAgentToTransportHeaders, + type BaseTransportOptions, + type CheckIn, + Client, + type ClientOptions, + createCheckInEnvelope, + debug, + type Event, + type EventHint, + eventFromMessage, + eventFromUnknownInput, + getIsolationScope, + getTraceInfoFromScope, + makePromiseBuffer, + type MonitorConfig, + type ParameterizedString, + resolvedSyncPromise, + type Scope, + type SerializedCheckIn, + type SeverityLevel, spanStreamingIntegration, - INTEGRATION_NAME as SPAN_STREAMING_INTEGRATION_NAME, -} from './integrations/spanStreaming'; -import type { Scope } from './scope'; -import { DEFAULT_TRANSPORT_BUFFER_SIZE } from './transports/base'; -import { addUserAgentToTransportHeaders } from './transports/userAgent'; -import type { CheckIn, MonitorConfig, SerializedCheckIn } from './types/checkin'; -import type { Event, EventHint } from './types/event'; -import type { ClientOptions } from './types/options'; -import type { ParameterizedString } from './types/parameterize'; -import type { SeverityLevel } from './types/severity'; -import type { BaseTransportOptions, Transport } from './types/transport'; -import { debug } from './utils/debug-logger'; -import { eventFromMessage, eventFromUnknownInput } from './utils/eventbuilder'; -import { uuid4 } from './utils/misc'; -import { makePromiseBuffer } from './utils/promisebuffer'; -import { resolvedSyncPromise } from './utils/syncpromise'; -import { _getTraceInfoFromScope } from './utils/trace-info'; + type Transport, + uuid4, +} from '@sentry/core'; +import { DEBUG_BUILD } from './debug-build'; + +// The base `Client`'s promise buffer size, mirrored here for the client-level +// buffer. Kept in sync with `@sentry/core`'s transport default. +const DEFAULT_TRANSPORT_BUFFER_SIZE = 64; + +const SPAN_STREAMING_INTEGRATION_NAME = 'SpanStreaming'; export interface ServerRuntimeClientOptions extends ClientOptions { platform?: string; @@ -145,7 +154,7 @@ export class ServerRuntimeClient< }; } - const [dynamicSamplingContext, traceContext] = _getTraceInfoFromScope(this, scope); + const [dynamicSamplingContext, traceContext] = getTraceInfoFromScope(this, scope); if (traceContext) { serializedCheckIn.contexts = { trace: traceContext, diff --git a/packages/core/src/trpc.ts b/packages/server-utils/src/trpc.ts similarity index 90% rename from packages/core/src/trpc.ts rename to packages/server-utils/src/trpc.ts index 55e428f60458..318e5de81795 100644 --- a/packages/core/src/trpc.ts +++ b/packages/server-utils/src/trpc.ts @@ -7,12 +7,15 @@ import { TRPC_PROCEDURE_TYPE, } from '@sentry/conventions/attributes'; import { RPC } from '@sentry/conventions/op'; -import { getClient, withIsolationScope } from './currentScopes'; -import { captureException } from './exports'; -import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from './semanticAttributes'; -import { startSpanManual } from './tracing/trace'; -import { normalize } from './utils/normalize'; -import { setNormalizationDepthOverrideHint } from './utils/normalizationHints'; +import { + captureException, + getClient, + normalize, + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + setNormalizationDepthOverrideHint, + startSpanManual, + withIsolationScope, +} from '@sentry/core'; interface SentryTrpcMiddlewareOptions { /** Whether to include procedure inputs in reported events. Defaults to `false`. */ diff --git a/packages/server-utils/src/types/options.ts b/packages/server-utils/src/types/options.ts new file mode 100644 index 000000000000..d1bb6738dca0 --- /dev/null +++ b/packages/server-utils/src/types/options.ts @@ -0,0 +1,73 @@ +import type { TracePropagationTargets } from '@sentry/core'; + +/** + * Base options for WinterTC-compatible server-side JavaScript runtimes. + * This interface contains common configuration options shared between + * SDKs. + */ +export interface ServerRuntimeOptions { + /** + * List of strings/regex controlling to which outgoing requests + * the SDK will attach tracing headers. + * + * By default the SDK will attach those headers to all outgoing + * requests. If this option is provided, the SDK will match the + * request URL of outgoing requests against the items in this + * array, and only attach tracing headers if a match was found. + * + * @example + * ```js + * Sentry.init({ + * tracePropagationTargets: ['api.site.com'], + * }); + * ``` + */ + tracePropagationTargets?: TracePropagationTargets; + + /** + * Sets an optional server name (device name). + * + * This is useful for identifying which server or instance is sending events. + */ + serverName?: string; + + /** + * If you use Spotlight by Sentry during development, use + * this option to forward captured Sentry events to Spotlight. + * + * Either set it to true, or provide a specific Spotlight Sidecar URL. + * + * More details: https://spotlightjs.com/ + * + * IMPORTANT: Only set this option to `true` while developing, not in production! + */ + spotlight?: boolean | string; + + /** + * If set to `false`, the SDK will not automatically detect the `serverName`. + * + * This is useful if you are using the SDK in a CLI app or Electron where the + * hostname might be considered PII. + * + * @default true + */ + includeServerName?: boolean; + + /** + * Controls how many milliseconds to wait before shutting down. The default is 2 seconds. Setting this too low can cause + * problems for sending events from command line applications. Setting it too + * high can cause the application to block for users with network connectivity + * problems. + */ + shutdownTimeout?: number; + + /** + * Configures in which interval client reports will be flushed. Defaults to `60_000` (milliseconds). + */ + clientReportFlushInterval?: number; + + /** + * Callback that is executed when a fatal global error occurs. + */ + onFatalError?(this: void, error: Error): void; +} diff --git a/packages/core/src/utils/anr.ts b/packages/server-utils/src/utils/anr.ts similarity index 95% rename from packages/core/src/utils/anr.ts rename to packages/server-utils/src/utils/anr.ts index ad6db13a1bf9..504b111fb7ea 100644 --- a/packages/core/src/utils/anr.ts +++ b/packages/server-utils/src/utils/anr.ts @@ -1,6 +1,6 @@ -import type { StackFrame } from '../types/stackframe'; +import type { StackFrame } from '@sentry/core'; import { filenameIsInApp } from './node-stack-trace'; -import { UNKNOWN_FUNCTION } from './stacktrace'; +import { UNKNOWN_FUNCTION } from '@sentry/core'; type WatchdogReturn = { /** Resets the watchdog timer */ diff --git a/packages/core/src/utils/flushIfServerless.ts b/packages/server-utils/src/utils/flushIfServerless.ts similarity index 96% rename from packages/core/src/utils/flushIfServerless.ts rename to packages/server-utils/src/utils/flushIfServerless.ts index 5ffd86612243..6491cc5af277 100644 --- a/packages/core/src/utils/flushIfServerless.ts +++ b/packages/server-utils/src/utils/flushIfServerless.ts @@ -1,7 +1,5 @@ -import { flush } from '../exports'; -import { debug } from './debug-logger'; +import { debug, flush, GLOBAL_OBJ } from '@sentry/core'; import { vercelWaitUntil } from './vercelWaitUntil'; -import { GLOBAL_OBJ } from './worldwide'; type MinimalCloudflareContext = { // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/packages/server-utils/src/utils/loadModule.ts b/packages/server-utils/src/utils/loadModule.ts new file mode 100644 index 000000000000..fb5585f25fa9 --- /dev/null +++ b/packages/server-utils/src/utils/loadModule.ts @@ -0,0 +1,46 @@ +/** + * Requires a module which is protected against bundler minification. + * + * @param request The module path to resolve + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function dynamicRequire(mod: any, request: string): any { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + return mod.require(request); +} + +/** + * Helper for dynamically loading module that should work with linked dependencies. + * The problem is that we _should_ be using `require(require.resolve(moduleName, { paths: [cwd()] }))` + * However it's _not possible_ to do that with Webpack, as it has to know all the dependencies during + * build time. `require.resolve` is also not available in any other way, so we cannot create, + * a fake helper like we do with `dynamicRequire`. + * + * We always prefer to use local package, thus the value is not returned early from each `try/catch` block. + * That is to mimic the behavior of `require.resolve` exactly. + * + * @param moduleName module name to require + * @param existingModule module to use for requiring + * @returns possibly required module + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function loadModule(moduleName: string, existingModule: any = module): T | undefined { + let mod: T | undefined; + + try { + mod = dynamicRequire(existingModule, moduleName); + } catch { + // no-empty + } + + if (!mod) { + try { + const { cwd } = dynamicRequire(existingModule, 'process'); + mod = dynamicRequire(existingModule, `${cwd()}/node_modules/${moduleName}`) as T; + } catch { + // no-empty + } + } + + return mod; +} diff --git a/packages/core/src/utils/node-stack-trace.ts b/packages/server-utils/src/utils/node-stack-trace.ts similarity index 97% rename from packages/core/src/utils/node-stack-trace.ts rename to packages/server-utils/src/utils/node-stack-trace.ts index 3861a84bdeba..9812c580b31d 100644 --- a/packages/core/src/utils/node-stack-trace.ts +++ b/packages/server-utils/src/utils/node-stack-trace.ts @@ -21,8 +21,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -import type { StackLineParser, StackLineParserFn } from '../types/stacktrace'; -import { normalizeStackTracePath, UNKNOWN_FUNCTION } from './stacktrace'; +import { normalizeStackTracePath, type StackLineParser, type StackLineParserFn, UNKNOWN_FUNCTION } from '@sentry/core'; export type GetModuleFn = (filename: string | undefined) => string | undefined; diff --git a/packages/core/src/utils/vercelWaitUntil.ts b/packages/server-utils/src/utils/vercelWaitUntil.ts similarity index 95% rename from packages/core/src/utils/vercelWaitUntil.ts rename to packages/server-utils/src/utils/vercelWaitUntil.ts index 32d801a6723c..76d26ecbd5a0 100644 --- a/packages/core/src/utils/vercelWaitUntil.ts +++ b/packages/server-utils/src/utils/vercelWaitUntil.ts @@ -1,4 +1,4 @@ -import { GLOBAL_OBJ } from './worldwide'; +import { GLOBAL_OBJ } from '@sentry/core'; declare const EdgeRuntime: string | undefined; diff --git a/packages/core/test/lib/utils/eventbuilder.test.ts b/packages/server-utils/test/eventbuilder.test.ts similarity index 96% rename from packages/core/test/lib/utils/eventbuilder.test.ts rename to packages/server-utils/test/eventbuilder.test.ts index 2a08f073117e..d5500f9ad66c 100644 --- a/packages/core/test/lib/utils/eventbuilder.test.ts +++ b/packages/server-utils/test/eventbuilder.test.ts @@ -1,10 +1,14 @@ import { runInNewContext } from 'node:vm'; +import { + addNonEnumerableProperty, + type Client, + createStackParser, + eventFromMessage, + eventFromUnknownInput, + exceptionFromError, +} from '@sentry/core'; import { describe, expect, it, test } from 'vitest'; -import type { Client } from '../../../src/client'; -import { eventFromMessage, eventFromUnknownInput, exceptionFromError } from '../../../src/utils/eventbuilder'; -import { nodeStackLineParser } from '../../../src/utils/node-stack-trace'; -import { addNonEnumerableProperty } from '../../../src/utils/object'; -import { createStackParser } from '../../../src/utils/stacktrace'; +import { nodeStackLineParser } from '../src/utils/node-stack-trace'; const stackParser = createStackParser(nodeStackLineParser()); diff --git a/packages/core/test/lib/integrations/metadata.test.ts b/packages/server-utils/test/integrations/metadata.test.ts similarity index 91% rename from packages/core/test/lib/integrations/metadata.test.ts rename to packages/server-utils/test/integrations/metadata.test.ts index 21f9f950e207..3abefa19fb2b 100644 --- a/packages/core/test/lib/integrations/metadata.test.ts +++ b/packages/server-utils/test/integrations/metadata.test.ts @@ -1,16 +1,16 @@ -import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; import { captureException, createStackParser, createTransport, + type Event, GLOBAL_OBJ, moduleMetadataIntegration, parseEnvelope, setCurrentClient, -} from '../../../src'; -import { nodeStackLineParser } from '../../../src/server'; -import type { Event } from '../../../src/types/event'; -import { getDefaultTestClientOptions, TestClient } from '../../mocks/client'; +} from '@sentry/core'; +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; +import { nodeStackLineParser } from '../../src/utils/node-stack-trace'; +import { getDefaultTestClientOptions, TestClient } from '../mocks/client'; const stackParser = createStackParser(nodeStackLineParser()); diff --git a/packages/core/test/lib/server-runtime-client.test.ts b/packages/server-utils/test/server-runtime-client.test.ts similarity index 87% rename from packages/core/test/lib/server-runtime-client.test.ts rename to packages/server-utils/test/server-runtime-client.test.ts index 12ae4d625503..81435fe0a7c3 100644 --- a/packages/core/test/lib/server-runtime-client.test.ts +++ b/packages/server-utils/test/server-runtime-client.test.ts @@ -1,9 +1,15 @@ +import { + applySdkMetadata, + createTransport, + type Event, + type EventHint, + type Metric, + Scope, + SDK_VERSION, +} from '@sentry/core'; import { describe, expect, it, test, vi } from 'vitest'; -import { applySdkMetadata, createTransport, Scope } from '../../src'; -import { _INTERNAL_captureMetric, _INTERNAL_getMetricBuffer } from '../../src/metrics/internal'; -import type { ServerRuntimeClientOptions } from '../../src/server-runtime-client'; -import { ServerRuntimeClient } from '../../src/server-runtime-client'; -import type { Event, EventHint } from '../../src/types/event'; +import type { ServerRuntimeClientOptions } from '../src/server-runtime-client'; +import { ServerRuntimeClient } from '../src/server-runtime-client'; const PUBLIC_DSN = 'https://username@domain/123'; @@ -217,7 +223,7 @@ describe('ServerRuntimeClient', () => { client = new ServerRuntimeClient(options); expect(client.getOptions().transportOptions?.headers).toEqual({ - 'user-agent': 'sentry.javascript.core/0.0.0-unknown.0', + 'user-agent': `sentry.javascript.core/${SDK_VERSION}`, }); }); @@ -242,18 +248,13 @@ describe('ServerRuntimeClient', () => { it('adds server.address attribute to metrics when serverName is set', () => { const options = getDefaultClientOptions({ dsn: PUBLIC_DSN, serverName: 'my-server.example.com' }); client = new ServerRuntimeClient(options); - const scope = new Scope(); - scope.setClient(client); - _INTERNAL_captureMetric({ type: 'counter', name: 'test.metric', value: 1 }, { scope }); + const metric = { type: 'counter', name: 'test.metric', value: 1 } as Metric; + client.emit('processMetric', metric); - const metricAttributes = _INTERNAL_getMetricBuffer(client)?.[0]?.attributes; - expect(metricAttributes).toEqual( + expect(metric.attributes).toEqual( expect.objectContaining({ - 'server.address': { - value: 'my-server.example.com', - type: 'string', - }, + 'server.address': 'my-server.example.com', }), ); }); @@ -261,13 +262,11 @@ describe('ServerRuntimeClient', () => { it('does not add server.address attribute when serverName is not set', () => { const options = getDefaultClientOptions({ dsn: PUBLIC_DSN }); client = new ServerRuntimeClient(options); - const scope = new Scope(); - scope.setClient(client); - _INTERNAL_captureMetric({ type: 'counter', name: 'test.metric', value: 1 }, { scope }); + const metric = { type: 'counter', name: 'test.metric', value: 1 } as Metric; + client.emit('processMetric', metric); - const metricAttributes = _INTERNAL_getMetricBuffer(client)?.[0]?.attributes; - expect(metricAttributes).not.toEqual( + expect(metric.attributes ?? {}).not.toEqual( expect.objectContaining({ 'server.address': expect.anything(), }), @@ -277,26 +276,18 @@ describe('ServerRuntimeClient', () => { it('does not overwrite existing server.address attribute', () => { const options = getDefaultClientOptions({ dsn: PUBLIC_DSN, serverName: 'my-server.example.com' }); client = new ServerRuntimeClient(options); - const scope = new Scope(); - scope.setClient(client); - _INTERNAL_captureMetric( - { - type: 'counter', - name: 'test.metric', - value: 1, - attributes: { 'server.address': 'existing-server.example.com' }, - }, - { scope }, - ); + const metric = { + type: 'counter', + name: 'test.metric', + value: 1, + attributes: { 'server.address': 'existing-server.example.com' }, + } as Metric; + client.emit('processMetric', metric); - const metricAttributes = _INTERNAL_getMetricBuffer(client)?.[0]?.attributes; - expect(metricAttributes).toEqual( + expect(metric.attributes).toEqual( expect.objectContaining({ - 'server.address': { - value: 'existing-server.example.com', - type: 'string', - }, + 'server.address': 'existing-server.example.com', }), ); }); diff --git a/packages/core/test/lib/trpc.test.ts b/packages/server-utils/test/trpc.test.ts similarity index 74% rename from packages/core/test/lib/trpc.test.ts rename to packages/server-utils/test/trpc.test.ts index ecd0382bbdbe..5acf1f7ab52f 100644 --- a/packages/core/test/lib/trpc.test.ts +++ b/packages/server-utils/test/trpc.test.ts @@ -1,11 +1,8 @@ +import { type Client, setCurrentClient, type Span } from '@sentry/core'; +import * as SentryCore from '@sentry/core'; import { beforeEach, describe, expect, test, vi } from 'vitest'; -import { type Client, setCurrentClient, type Span } from '../../src'; -import { trpcMiddleware } from '../../src/server'; -import * as currentScopes from '../../src/currentScopes'; -import * as exports from '../../src/exports'; -import * as tracing from '../../src/tracing/trace'; -import { resolveDataCollectionOptions } from '../../src/utils/data-collection/resolveDataCollectionOptions'; -import { getDefaultTestClientOptions, TestClient } from '../mocks/client'; +import { trpcMiddleware } from '../src/trpc'; +import { getDefaultTestClientOptions, TestClient } from './mocks/client'; describe('trpcMiddleware', () => { let client: Client; @@ -15,9 +12,7 @@ describe('trpcMiddleware', () => { normalizeDepth: 3, dataCollection: { httpBodies: [] }, }), - getDataCollectionOptions: vi - .fn() - .mockReturnValue(resolveDataCollectionOptions({ dataCollection: { httpBodies: [] } })), + getDataCollectionOptions: vi.fn().mockReturnValue({ httpBodies: [] }), captureException: vi.fn(), } as unknown as Client; @@ -41,10 +36,10 @@ describe('trpcMiddleware', () => { client = new TestClient(options); setCurrentClient(client); client.init(); - vi.spyOn(currentScopes, 'getClient').mockReturnValue(mockClient); - vi.spyOn(tracing, 'startSpanManual').mockImplementation((name, callback) => callback(mockSpan, () => {})); - vi.spyOn(currentScopes, 'withIsolationScope').mockImplementation(withIsolationScope); - vi.spyOn(exports, 'captureException').mockImplementation(() => 'mock-event-id'); + vi.spyOn(SentryCore, 'getClient').mockReturnValue(mockClient); + vi.spyOn(SentryCore, 'startSpanManual').mockImplementation((name, callback) => callback(mockSpan, () => {})); + vi.spyOn(SentryCore, 'withIsolationScope').mockImplementation(withIsolationScope); + vi.spyOn(SentryCore, 'captureException').mockImplementation(() => 'mock-event-id'); }); test('creates span with correct attributes', async () => { @@ -57,7 +52,7 @@ describe('trpcMiddleware', () => { next, }); - expect(tracing.startSpanManual).toHaveBeenCalledWith( + expect(SentryCore.startSpanManual).toHaveBeenCalledWith( { name: 'trpc/test.procedure', attributes: { @@ -81,7 +76,7 @@ describe('trpcMiddleware', () => { await middleware({ path: 'test.procedure', type: 'query', next }); - expect(tracing.startSpanManual).toHaveBeenCalledWith( + expect(SentryCore.startSpanManual).toHaveBeenCalledWith( expect.objectContaining({ attributes: expect.objectContaining({ 'sentry.segment.name.source': 'route', @@ -102,7 +97,7 @@ describe('trpcMiddleware', () => { next, }); - expect(exports.captureException).toHaveBeenCalledWith(error, { + expect(SentryCore.captureException).toHaveBeenCalledWith(error, { mechanism: { handled: false, type: 'auto.rpc.trpc.middleware' }, }); }); @@ -139,7 +134,7 @@ describe('trpcMiddleware', () => { }), ).rejects.toThrow(error); - expect(exports.captureException).toHaveBeenCalledWith(error, { + expect(SentryCore.captureException).toHaveBeenCalledWith(error, { mechanism: { handled: false, type: 'auto.rpc.trpc.middleware' }, }); }); @@ -154,7 +149,7 @@ describe('trpcMiddleware', () => { next, }); - expect(tracing.startSpanManual).toHaveBeenCalledWith( + expect(SentryCore.startSpanManual).toHaveBeenCalledWith( expect.objectContaining({ forceTransaction: true, }), diff --git a/packages/core/test/lib/utils/flushIfServerless.test.ts b/packages/server-utils/test/utils/flushIfServerless.test.ts similarity index 81% rename from packages/core/test/lib/utils/flushIfServerless.test.ts rename to packages/server-utils/test/utils/flushIfServerless.test.ts index aa0314f183dc..4650bfce1735 100644 --- a/packages/core/test/lib/utils/flushIfServerless.test.ts +++ b/packages/server-utils/test/utils/flushIfServerless.test.ts @@ -1,8 +1,8 @@ import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; -import * as flushModule from '../../../src/exports'; -import { flushIfServerless } from '../../../src/utils/flushIfServerless'; -import * as vercelWaitUntilModule from '../../../src/utils/vercelWaitUntil'; -import { GLOBAL_OBJ } from '../../../src/utils/worldwide'; +import * as SentryCore from '@sentry/core'; +import { flushIfServerless } from '../../src/utils/flushIfServerless'; +import * as vercelWaitUntilModule from '../../src/utils/vercelWaitUntil'; +import { GLOBAL_OBJ } from '@sentry/core'; describe('flushIfServerless', () => { let originalProcess: typeof process; @@ -17,7 +17,7 @@ describe('flushIfServerless', () => { }); test('should bind context (preserve `this`) when calling waitUntil from the Cloudflare execution context', async () => { - const flushMock = vi.spyOn(flushModule, 'flush').mockResolvedValue(true); + const flushMock = vi.spyOn(SentryCore, 'flush').mockResolvedValue(true); // Mock Cloudflare context with `waitUntil` (which should be called if `this` is bound correctly) const mockCloudflareCtx = { @@ -38,7 +38,7 @@ describe('flushIfServerless', () => { }); test('should use cloudflare waitUntil when valid cloudflare context is provided', async () => { - const flushMock = vi.spyOn(flushModule, 'flush').mockResolvedValue(true); + const flushMock = vi.spyOn(SentryCore, 'flush').mockResolvedValue(true); const mockCloudflareCtx = { waitUntil: vi.fn(), }; @@ -50,7 +50,7 @@ describe('flushIfServerless', () => { }); test('should use cloudflare waitUntil when Cloudflare `waitUntil` is provided', async () => { - const flushMock = vi.spyOn(flushModule, 'flush').mockResolvedValue(true); + const flushMock = vi.spyOn(SentryCore, 'flush').mockResolvedValue(true); const mockCloudflareCtx = { waitUntil: vi.fn(), }; @@ -62,7 +62,7 @@ describe('flushIfServerless', () => { }); test('should ignore cloudflare context when waitUntil is not a function (and use Vercel waitUntil instead)', async () => { - const flushMock = vi.spyOn(flushModule, 'flush').mockResolvedValue(true); + const flushMock = vi.spyOn(SentryCore, 'flush').mockResolvedValue(true); const vercelWaitUntilSpy = vi.spyOn(vercelWaitUntilModule, 'vercelWaitUntil').mockImplementation(() => {}); // Mock Vercel environment @@ -81,7 +81,7 @@ describe('flushIfServerless', () => { }); test('should handle multiple serverless environment variables simultaneously', async () => { - const flushMock = vi.spyOn(flushModule, 'flush').mockResolvedValue(true); + const flushMock = vi.spyOn(SentryCore, 'flush').mockResolvedValue(true); global.process = { ...originalProcess, @@ -100,7 +100,7 @@ describe('flushIfServerless', () => { }); test('should use default timeout when not specified', async () => { - const flushMock = vi.spyOn(flushModule, 'flush').mockResolvedValue(true); + const flushMock = vi.spyOn(SentryCore, 'flush').mockResolvedValue(true); const mockCloudflareCtx = { waitUntil: vi.fn(), }; @@ -111,7 +111,7 @@ describe('flushIfServerless', () => { }); test('should handle zero timeout value', async () => { - const flushMock = vi.spyOn(flushModule, 'flush').mockResolvedValue(true); + const flushMock = vi.spyOn(SentryCore, 'flush').mockResolvedValue(true); global.process = { ...originalProcess, diff --git a/packages/server-utils/test/utils/node-stack-trace.test.ts b/packages/server-utils/test/utils/node-stack-trace.test.ts new file mode 100644 index 000000000000..b3e097631187 --- /dev/null +++ b/packages/server-utils/test/utils/node-stack-trace.test.ts @@ -0,0 +1,294 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { nodeStackLineParser } from '../../src/utils/node-stack-trace'; + +describe('node', () => { + const mockGetModule = vi.fn(); + const parser = nodeStackLineParser(mockGetModule); + const node = parser[1]; + + beforeEach(() => { + mockGetModule.mockReset(); + }); + + it('should return undefined for invalid input', () => { + expect(node('invalid input')).toBeUndefined(); + }); + + it('should extract function, module, filename, lineno, colno, and in_app from valid input', () => { + const input = 'at myFunction (/path/to/file.js:10:5)'; + + const expectedOutput = { + filename: '/path/to/file.js', + module: undefined, + function: 'myFunction', + lineno: 10, + colno: 5, + in_app: true, + }; + + expect(node(input)).toEqual(expectedOutput); + }); + + it('extracts module from getModule', () => { + const input = 'at myFunction (/path/to/file.js:10:5)'; + mockGetModule.mockReturnValue('myModule'); + expect(node(input)?.module).toEqual('myModule'); + }); + + it('should extract anonymous function name correctly', () => { + const input = 'at /path/to/file.js:10:5'; + + const expectedOutput = { + filename: '/path/to/file.js', + module: undefined, + function: '?', + lineno: 10, + colno: 5, + in_app: true, + }; + + expect(node(input)).toEqual(expectedOutput); + }); + + it('should extract method name and type name correctly', () => { + const input = 'at myObject.myMethod (/path/to/file.js:10:5)'; + + const expectedOutput = { + filename: '/path/to/file.js', + module: undefined, + function: 'myObject.myMethod', + lineno: 10, + colno: 5, + in_app: true, + }; + + expect(node(input)).toEqual(expectedOutput); + }); + + it('should handle input with file:// protocol', () => { + const input = 'at myFunction (file:///path/to/file.js:10:5)'; + + const expectedOutput = { + filename: '/path/to/file.js', + module: undefined, + function: 'myFunction', + lineno: 10, + colno: 5, + in_app: true, + }; + + expect(node(input)).toEqual(expectedOutput); + }); + + it('should handle input with no line or column number', () => { + const input = 'at myFunction (/path/to/file.js)'; + + const expectedOutput = { + filename: '/path/to/file.js', + module: undefined, + function: 'myFunction', + lineno: undefined, + colno: undefined, + in_app: true, + }; + + expect(node(input)).toEqual(expectedOutput); + }); + + it('should handle input with "native" flag', () => { + const input = 'at myFunction (native)'; + + const expectedOutput = { + filename: undefined, + module: undefined, + function: 'myFunction', + lineno: undefined, + colno: undefined, + in_app: false, + }; + + expect(node(input)).toEqual(expectedOutput); + }); + + it('should correctly parse a stack trace line with a function name and file URL', () => { + const line = 'at myFunction (file:///path/to/myFile.js:10:20)'; + const result = node(line); + expect(result).toEqual({ + filename: '/path/to/myFile.js', + function: 'myFunction', + lineno: 10, + colno: 20, + in_app: true, + }); + }); + + it('should correctly parse a stack trace line with a method name and filename', () => { + const line = 'at MyClass.myMethod (/path/to/myFile.js:10:20)'; + const result = node(line); + expect(result).toEqual({ + filename: '/path/to/myFile.js', + module: undefined, + function: 'MyClass.myMethod', + lineno: 10, + colno: 20, + in_app: true, + }); + }); + + it('should correctly parse a stack trace line with an anonymous function', () => { + const line = 'at Object. (/path/to/myFile.js:10:20)'; + const result = node(line); + + expect(result).toEqual({ + filename: '/path/to/myFile.js', + function: 'Object.?', + lineno: 10, + colno: 20, + in_app: true, + }); + }); + + it('should correctly parse a stack trace line with no function or filename', () => { + const line = 'at /path/to/myFile.js:10:20'; + const result = node(line); + expect(result).toEqual({ + filename: '/path/to/myFile.js', + function: '?', + lineno: 10, + colno: 20, + in_app: true, + }); + }); + + it('should correctly parse a stack trace line with a native function', () => { + const line = 'at Object. (native)'; + const result = node(line); + expect(result).toEqual({ + filename: undefined, + function: 'Object.?', + lineno: undefined, + colno: undefined, + in_app: false, + }); + }); + + it('should correctly parse a stack trace line with a module filename', () => { + const line = 'at Object. (/path/to/node_modules/myModule/index.js:10:20)'; + const result = node(line); + + expect(result).toEqual({ + filename: '/path/to/node_modules/myModule/index.js', + function: 'Object.?', + lineno: 10, + colno: 20, + in_app: false, + }); + }); + + it('should correctly parse a stack trace line with a Windows filename', () => { + const line = 'at Object. (C:\\path\\to\\myFile.js:10:20)'; + const result = node(line); + expect(result).toEqual({ + filename: 'C:\\path\\to\\myFile.js', + function: 'Object.?', + lineno: 10, + colno: 20, + in_app: true, + }); + }); + + it('should mark frames with protocols as in_app: true', () => { + const line = 'at Object. (app:///_next/server/pages/[error].js:10:20)'; + const result = node(line); + expect(result?.in_app).toBe(true); + }); + + it('parses frame filename paths with spaces and characters in file name', () => { + const input = 'at myObject.myMethod (/path/to/file with space(1).js:10:5)'; + + const expectedOutput = { + filename: '/path/to/file with space(1).js', + module: undefined, + function: 'myObject.myMethod', + lineno: 10, + colno: 5, + in_app: true, + }; + + expect(node(input)).toEqual(expectedOutput); + }); + + it('parses frame filename paths with spaces and characters in file path', () => { + const input = 'at myObject.myMethod (/path with space(1)/to/file.js:10:5)'; + + const expectedOutput = { + filename: '/path with space(1)/to/file.js', + module: undefined, + function: 'myObject.myMethod', + lineno: 10, + colno: 5, + in_app: true, + }; + + expect(node(input)).toEqual(expectedOutput); + }); + + it('parses encoded frame filename paths with spaces and characters in file name', () => { + const input = 'at myObject.myMethod (/path/to/file%20with%20space(1).js:10:5)'; + + const expectedOutput = { + filename: '/path/to/file with space(1).js', + module: undefined, + function: 'myObject.myMethod', + lineno: 10, + colno: 5, + in_app: true, + }; + + expect(node(input)).toEqual(expectedOutput); + }); + + it('parses encoded frame filename paths with spaces and characters in file path', () => { + const input = 'at myObject.myMethod (/path%20with%20space(1)/to/file.js:10:5)'; + + const expectedOutput = { + filename: '/path with space(1)/to/file.js', + module: undefined, + function: 'myObject.myMethod', + lineno: 10, + colno: 5, + in_app: true, + }; + + expect(node(input)).toEqual(expectedOutput); + }); + + it('parses function name when filename is a data uri ', () => { + const input = + "at dynamicFn (data:application/javascript,export function dynamicFn() { throw new Error('Error from data-uri module');};:1:38)"; + + const expectedOutput = { + function: 'dynamicFn', + filename: '', + }; + + expect(node(input)).toEqual(expectedOutput); + }); + + it('returns the raw filename when decodeURI throws a URIError', () => { + const malformedFilename = '/path/to/%file%.js'; + const input = `at myFunction (${malformedFilename}:10:5)`; + + const result = node(input); + + expect(result?.filename).toBe('/path/to/%file%.js'); + }); + + it('decodes a valid percent-encoded filename', () => { + const input = 'at myFunction (/path/to/my%20file.js:10:5)'; + + const result = node(input); + + expect(result?.filename).toBe('/path/to/my file.js'); + }); +}); diff --git a/packages/core/test/lib/utils/vercelWaitUntil.test.ts b/packages/server-utils/test/utils/vercelWaitUntil.test.ts similarity index 95% rename from packages/core/test/lib/utils/vercelWaitUntil.test.ts rename to packages/server-utils/test/utils/vercelWaitUntil.test.ts index 1f6be3b7924f..78e5f574c872 100644 --- a/packages/core/test/lib/utils/vercelWaitUntil.test.ts +++ b/packages/server-utils/test/utils/vercelWaitUntil.test.ts @@ -1,6 +1,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { vercelWaitUntil } from '../../../src/utils/vercelWaitUntil'; -import { GLOBAL_OBJ } from '../../../src/utils/worldwide'; +import { GLOBAL_OBJ } from '@sentry/core'; +import { vercelWaitUntil } from '../../src/utils/vercelWaitUntil'; describe('vercelWaitUntil', () => { const VERCEL_REQUEST_CONTEXT_SYMBOL = Symbol.for('@vercel/request-context'); diff --git a/packages/solidstart/src/server/withServerActionInstrumentation.ts b/packages/solidstart/src/server/withServerActionInstrumentation.ts index 8951d440637d..810fb7b66759 100644 --- a/packages/solidstart/src/server/withServerActionInstrumentation.ts +++ b/packages/solidstart/src/server/withServerActionInstrumentation.ts @@ -1,5 +1,5 @@ import { handleCallbackErrors, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SPAN_STATUS_ERROR } from '@sentry/core'; -import { flushIfServerless } from '@sentry/core/server'; +import { flushIfServerless } from '@sentry/server-utils'; import { captureException, getActiveSpan, spanToJSON, startSpan } from '@sentry/node'; import { isRedirect } from './utils'; import { diff --git a/packages/solidstart/test/server/withServerActionInstrumentation.test.ts b/packages/solidstart/test/server/withServerActionInstrumentation.test.ts index 32d197d1bba1..94ab6eb5902d 100644 --- a/packages/solidstart/test/server/withServerActionInstrumentation.test.ts +++ b/packages/solidstart/test/server/withServerActionInstrumentation.test.ts @@ -1,6 +1,6 @@ import { SENTRY_SEGMENT_NAME_SOURCE } from '@sentry/conventions/attributes'; import * as SentryCore from '@sentry/core'; -import * as SentryCoreServer from '@sentry/core/server'; +import * as serverUtils from '@sentry/server-utils'; import * as SentryNode from '@sentry/node'; import { createTransport, @@ -15,7 +15,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { withServerActionInstrumentation } from '../../src/server'; const mockCaptureException = vi.spyOn(SentryNode, 'captureException').mockImplementation(() => ''); -const mockFlush = vi.spyOn(SentryCoreServer, 'flushIfServerless').mockImplementation(async () => {}); +const mockFlush = vi.spyOn(serverUtils, 'flushIfServerless').mockImplementation(async () => {}); const mockGetActiveSpan = vi.spyOn(SentryCore, 'getActiveSpan'); const mockGetRequestEvent = vi.fn(); diff --git a/packages/sveltekit/src/server-common/handle.ts b/packages/sveltekit/src/server-common/handle.ts index a7f5645c2747..c37f3565ae9c 100644 --- a/packages/sveltekit/src/server-common/handle.ts +++ b/packages/sveltekit/src/server-common/handle.ts @@ -20,7 +20,7 @@ import { withIsolationScope, filterCollectedUrl, } from '@sentry/core'; -import { flushIfServerless } from '@sentry/core/server'; +import { flushIfServerless } from '@sentry/server-utils'; import type { Handle, ResolveOptions } from '@sveltejs/kit'; import { DEBUG_BUILD } from '../common/debug-build'; import { getTracePropagationData, sendErrorToSentry } from './utils'; diff --git a/packages/sveltekit/src/server-common/handleError.ts b/packages/sveltekit/src/server-common/handleError.ts index ddf18dd80396..be4ad20f7fa0 100644 --- a/packages/sveltekit/src/server-common/handleError.ts +++ b/packages/sveltekit/src/server-common/handleError.ts @@ -1,5 +1,5 @@ import { captureException, consoleSandbox } from '@sentry/core'; -import { flushIfServerless } from '@sentry/core/server'; +import { flushIfServerless } from '@sentry/server-utils'; import type { AnyErrorHandler, SentryHandleServerErrorInput } from '../common/handleErrorTypes'; import { shouldCaptureError } from '../common/handleErrorTypes'; import { getCloudflareExecutionContext } from './utils'; diff --git a/packages/sveltekit/src/server-common/load.ts b/packages/sveltekit/src/server-common/load.ts index 64106c47d157..e1e2748b3f47 100644 --- a/packages/sveltekit/src/server-common/load.ts +++ b/packages/sveltekit/src/server-common/load.ts @@ -1,5 +1,5 @@ import { addNonEnumerableProperty, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, startSpan } from '@sentry/core'; -import { flushIfServerless } from '@sentry/core/server'; +import { flushIfServerless } from '@sentry/server-utils'; import { SENTRY_SEGMENT_NAME_SOURCE, CODE_FUNCTION_NAME, diff --git a/packages/sveltekit/src/server-common/serverRoute.ts b/packages/sveltekit/src/server-common/serverRoute.ts index 268556d0b96b..5c410779df63 100644 --- a/packages/sveltekit/src/server-common/serverRoute.ts +++ b/packages/sveltekit/src/server-common/serverRoute.ts @@ -1,5 +1,5 @@ import { addNonEnumerableProperty, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, startSpan } from '@sentry/core'; -import { flushIfServerless } from '@sentry/core/server'; +import { flushIfServerless } from '@sentry/server-utils'; import { CODE_FUNCTION_NAME, HTTP_REQUEST_METHOD, SENTRY_OP } from '@sentry/conventions/attributes'; import { FUNCTION } from '@sentry/conventions/op'; import type { RequestEvent } from '@sveltejs/kit'; diff --git a/packages/tanstackstart-react/src/server/wrapFetchWithSentry.ts b/packages/tanstackstart-react/src/server/wrapFetchWithSentry.ts index 935c03fb76e0..144a7d8ab21d 100644 --- a/packages/tanstackstart-react/src/server/wrapFetchWithSentry.ts +++ b/packages/tanstackstart-react/src/server/wrapFetchWithSentry.ts @@ -1,5 +1,5 @@ import { getTraceMetaTags } from '@sentry/core'; -import { flushIfServerless } from '@sentry/core/server'; +import { flushIfServerless } from '@sentry/server-utils'; import { captureException, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, startSpan } from '@sentry/node'; import { SENTRY_OP } from '@sentry/conventions/attributes'; import { FUNCTION } from '@sentry/conventions/op'; diff --git a/packages/tanstackstart-react/test/server/wrapFetchWithSentry.test.ts b/packages/tanstackstart-react/test/server/wrapFetchWithSentry.test.ts index cb1a809cafb2..49232b6d4a39 100644 --- a/packages/tanstackstart-react/test/server/wrapFetchWithSentry.test.ts +++ b/packages/tanstackstart-react/test/server/wrapFetchWithSentry.test.ts @@ -28,13 +28,9 @@ vi.mock('@sentry/core', async importOriginal => { }; }); -vi.mock('@sentry/core/server', async importOriginal => { - const original = await importOriginal(); - return { - ...original, - flushIfServerless: (...args: unknown[]) => flushIfServerlessSpy(...args), - }; -}); +vi.mock('@sentry/server-utils', () => ({ + flushIfServerless: (...args: unknown[]) => flushIfServerlessSpy(...args), +})); // Import after mocks are set up const { wrapFetchWithSentry } = await import('../../src/server/wrapFetchWithSentry'); diff --git a/packages/vercel-edge/src/client.ts b/packages/vercel-edge/src/client.ts index f5ca5aa1948b..e2ea0335e0ce 100644 --- a/packages/vercel-edge/src/client.ts +++ b/packages/vercel-edge/src/client.ts @@ -1,6 +1,6 @@ -import type { ServerRuntimeClientOptions } from '@sentry/core/server'; +import type { ServerRuntimeClientOptions } from '@sentry/server-utils/no-diagnostic-channels'; import { applySdkMetadata } from '@sentry/core'; -import { ServerRuntimeClient } from '@sentry/core/server'; +import { ServerRuntimeClient } from '@sentry/server-utils/no-diagnostic-channels'; import type { VercelEdgeClientOptions } from './types'; import { registerPrepareSpanScope, type SentryTracerProvider } from '@sentry/opentelemetry'; diff --git a/packages/vercel-edge/src/index.ts b/packages/vercel-edge/src/index.ts index bc9be3e2aeef..ffba9cd5d049 100644 --- a/packages/vercel-edge/src/index.ts +++ b/packages/vercel-edge/src/index.ts @@ -101,8 +101,9 @@ export { withStreamedSpan, spanStreamingIntegration, } from '@sentry/core'; -export { trpcMiddleware, wrapMcpServerWithSentry } from '@sentry/core/server'; +export { wrapMcpServerWithSentry } from '@sentry/core/server'; export { + trpcMiddleware, openTelemetryIntegration, getOtlpTracesEndpoint, instrumentOpenAiClient, diff --git a/packages/vercel-edge/src/sdk.ts b/packages/vercel-edge/src/sdk.ts index 5e6c2926ccbb..39ee973ce93d 100644 --- a/packages/vercel-edge/src/sdk.ts +++ b/packages/vercel-edge/src/sdk.ts @@ -15,7 +15,7 @@ import { requestDataIntegration, stackParserFromStackParserOptions, } from '@sentry/core'; -import { nodeStackLineParser } from '@sentry/core/server'; +import { nodeStackLineParser } from '@sentry/server-utils/no-diagnostic-channels'; import { SentryPropagator, SentryTracerProvider, From 2f8e96c39bc3e22af0d7770179f9fb8a91d9677e Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 2 Sep 2026 15:33:03 +0200 Subject: [PATCH 2/8] fix(cloudflare): Keep server-utils off the edge request-handler graph MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After `ServerRuntimeClient` and `nodeStackLineParser` moved from the edge-safe `@sentry/core/server` entry into `@sentry/server-utils`, `client.ts` and `vendor/stacktrace.ts` pulled the full `@sentry/server-utils` barrel (which subscribes to `node:diagnostics_channel`) into the `wrapRequestHandler` graph, breaking runtimes without `nodejs_compat` (e.g. Shopify Oxygen / the remix-hydrogen app). Route those two imports through `@sentry/server-utils/no-diagnostic-channels` — the edge-safe subset that carries `ServerRuntimeClient` and the node stack parser without the channel subscription — matching how `@sentry/vercel-edge` already consumes them. Update the `requestModuleGraph` guard to allow that one edge-safe entry while still forbidding the heavy barrel and `@sentry/node`. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01D7JDQBD9J2okCe1hkWCanU --- packages/cloudflare/src/client.ts | 4 ++-- packages/cloudflare/src/vendor/stacktrace.ts | 2 +- packages/cloudflare/test/requestModuleGraph.test.ts | 12 ++++++++++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/cloudflare/src/client.ts b/packages/cloudflare/src/client.ts index ec5d4401864e..3356bb31c3b2 100644 --- a/packages/cloudflare/src/client.ts +++ b/packages/cloudflare/src/client.ts @@ -7,8 +7,8 @@ import { debug, spanIsSampled, } from '@sentry/core'; -import type { ServerRuntimeClientOptions } from '@sentry/server-utils'; -import { ServerRuntimeClient } from '@sentry/server-utils'; +import type { ServerRuntimeClientOptions } from '@sentry/server-utils/no-diagnostic-channels'; +import { ServerRuntimeClient } from '@sentry/server-utils/no-diagnostic-channels'; import { DEBUG_BUILD } from './debug-build'; import type { ExecutionContextCompat } from './executionContext'; import type { makeFlushLock } from './flush'; diff --git a/packages/cloudflare/src/vendor/stacktrace.ts b/packages/cloudflare/src/vendor/stacktrace.ts index 8e035b672705..4ca4f96a355d 100644 --- a/packages/cloudflare/src/vendor/stacktrace.ts +++ b/packages/cloudflare/src/vendor/stacktrace.ts @@ -4,7 +4,7 @@ import type { StackLineParser, StackLineParserFn, StackParser } from '@sentry/core'; import { basename, createStackParser } from '@sentry/core'; -import { nodeStackLineParser } from '@sentry/server-utils'; +import { nodeStackLineParser } from '@sentry/server-utils/no-diagnostic-channels'; type GetModuleFn = (filename: string | undefined) => string | undefined; diff --git a/packages/cloudflare/test/requestModuleGraph.test.ts b/packages/cloudflare/test/requestModuleGraph.test.ts index 2100f4a47311..9b6f26322d24 100644 --- a/packages/cloudflare/test/requestModuleGraph.test.ts +++ b/packages/cloudflare/test/requestModuleGraph.test.ts @@ -68,8 +68,16 @@ describe('module graph of `wrapRequestHandler`', () => { it('contains no package that depends on Node.js APIs', () => { // `@sentry/server-utils` subscribes to `node:diagnostics_channel` and `@sentry/node` needs Node.js - // throughout. Both are only allowed in `sdk.ts`, `index.ts` and `vite/` (see `.oxlintrc.json`). - expect(externals.filter(specifier => /^@sentry\/(node|server-utils)(\/|$)/.test(specifier))).toEqual([]); + // throughout. The `@sentry/server-utils/no-diagnostic-channels` entry is the edge-safe subset + // (it carries `ServerRuntimeClient` and the node stack parser without the channel subscription), so + // it is the one exception the request-handler graph may reach. + expect( + externals.filter( + specifier => + /^@sentry\/node(\/|$)/.test(specifier) || + (/^@sentry\/server-utils(\/|$)/.test(specifier) && specifier !== '@sentry/server-utils/no-diagnostic-channels'), + ), + ).toEqual([]); }); it('does not reach `async.ts`, the only shipped module importing `node:async_hooks`', () => { From 2e70a1233d774ed80b5c184bc6aeb76e63e4b0f2 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 2 Sep 2026 15:33:12 +0200 Subject: [PATCH 3/8] test: Fix node and tanstackstart-react unit tests after server-utils move MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - node `httpServerIntegration` test imported `ServerRuntimeClient` from `@sentry/core/server`, which no longer exports it — import it from `@sentry/server-utils`. - tanstackstart-react `wrapFetchWithSentry` test mocked `@sentry/server-utils` without spreading the original module, so `nodeStackLineParser` (now pulled from server-utils by the `@sentry/node` init path) was undefined and the mock factory threw. Spread `importOriginal()` like the sibling mocks. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01D7JDQBD9J2okCe1hkWCanU --- .../test/integrations/httpServerIntegration.test.ts | 2 +- .../test/server/wrapFetchWithSentry.test.ts | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/node/test/integrations/httpServerIntegration.test.ts b/packages/node/test/integrations/httpServerIntegration.test.ts index 0a677246773b..409050b803a8 100644 --- a/packages/node/test/integrations/httpServerIntegration.test.ts +++ b/packages/node/test/integrations/httpServerIntegration.test.ts @@ -1,6 +1,6 @@ import type { Client } from '@sentry/core'; import { createTransport, Scope, withScope } from '@sentry/core'; -import { ServerRuntimeClient } from '@sentry/core/server'; +import { ServerRuntimeClient } from '@sentry/server-utils'; import { EventEmitter } from 'stream'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { recordRequestSession } from '../../src/integrations/http/httpServerIntegration'; diff --git a/packages/tanstackstart-react/test/server/wrapFetchWithSentry.test.ts b/packages/tanstackstart-react/test/server/wrapFetchWithSentry.test.ts index 49232b6d4a39..e6d17aa94b5b 100644 --- a/packages/tanstackstart-react/test/server/wrapFetchWithSentry.test.ts +++ b/packages/tanstackstart-react/test/server/wrapFetchWithSentry.test.ts @@ -28,9 +28,13 @@ vi.mock('@sentry/core', async importOriginal => { }; }); -vi.mock('@sentry/server-utils', () => ({ - flushIfServerless: (...args: unknown[]) => flushIfServerlessSpy(...args), -})); +vi.mock('@sentry/server-utils', async importOriginal => { + const original = await importOriginal(); + return { + ...original, + flushIfServerless: (...args: unknown[]) => flushIfServerlessSpy(...args), + }; +}); // Import after mocks are set up const { wrapFetchWithSentry } = await import('../../src/server/wrapFetchWithSentry'); From 1cda655fd462ad9879dce08757a5d4a27da505d3 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 2 Sep 2026 15:33:20 +0200 Subject: [PATCH 4/8] test(deno): Import nodeStackLineParser from server-utils in deno tests `nodeStackLineParser` moved out of `@sentry/core/server` into `@sentry/server-utils`. Update the deno unit tests (`mod`, `deno-runtime-metrics`) and the deno integration `direct-client-acs` scenario to import it from `@sentry/server-utils`; the deno SDK src already does. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01D7JDQBD9J2okCe1hkWCanU --- .../suites/direct-client-acs/scenario.mjs | 2 +- packages/deno/test/deno-runtime-metrics.test.ts | 2 +- packages/deno/test/mod.test.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dev-packages/deno-integration-tests/suites/direct-client-acs/scenario.mjs b/dev-packages/deno-integration-tests/suites/direct-client-acs/scenario.mjs index 31ad355c989e..5c81de04c21b 100644 --- a/dev-packages/deno-integration-tests/suites/direct-client-acs/scenario.mjs +++ b/dev-packages/deno-integration-tests/suites/direct-client-acs/scenario.mjs @@ -8,7 +8,7 @@ // `DenoClient.init()` installs that strategy on the direct-construction path. // Without it, the subscriber never binds and no span is produced. import { createStackParser } from '@sentry/core'; -import { nodeStackLineParser } from '@sentry/core/server'; +import { nodeStackLineParser } from '@sentry/server-utils'; import { DenoClient, getCurrentScope, getDefaultIntegrations, startSpan } from '@sentry/deno'; import { tracingChannel } from 'node:diagnostics_channel'; diff --git a/packages/deno/test/deno-runtime-metrics.test.ts b/packages/deno/test/deno-runtime-metrics.test.ts index 0dfdc5d35d6a..830ea1b5c6c8 100644 --- a/packages/deno/test/deno-runtime-metrics.test.ts +++ b/packages/deno/test/deno-runtime-metrics.test.ts @@ -2,7 +2,7 @@ import type { Envelope } from '@sentry/core'; import { createStackParser, forEachEnvelopeItem } from '@sentry/core'; -import { nodeStackLineParser } from '@sentry/core/server'; +import { nodeStackLineParser } from '@sentry/server-utils'; import { assertEquals, assertNotEquals, assertStringIncludes } from 'https://deno.land/std@0.212.0/assert/mod.ts'; import { DenoClient, diff --git a/packages/deno/test/mod.test.ts b/packages/deno/test/mod.test.ts index 8e860c931e41..2f9493a88460 100644 --- a/packages/deno/test/mod.test.ts +++ b/packages/deno/test/mod.test.ts @@ -1,6 +1,6 @@ import type { Envelope, Event, Log } from '@sentry/core'; import { createStackParser, forEachEnvelopeItem } from '@sentry/core'; -import { nodeStackLineParser } from '@sentry/core/server'; +import { nodeStackLineParser } from '@sentry/server-utils'; import { assertEquals } from 'https://deno.land/std@0.202.0/assert/assert_equals.ts'; import { assertSnapshot } from 'https://deno.land/std@0.202.0/testing/snapshot.ts'; import { DenoClient, getCurrentScope, getDefaultIntegrations, logger, metrics, Scope } from '../build/esm/index.js'; From 7e0d1ff7be9237b6188ee06361dc6365b1cef95a Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 2 Sep 2026 15:33:29 +0200 Subject: [PATCH 5/8] test(bundler-plugins): Pin local server-utils tarball in fixtures `@sentry/bundler-plugins` now externalizes `@sentry/server-utils` (it `require`s it at build time for `ServerRuntimeClient` / `nodeStackLineParser`). The fixtures pinned `@sentry/core` and `@sentry/bundler-plugins` to local tarballs but not server-utils, so pnpm pulled a published `@sentry/server-utils` from the registry that mismatched the local `@sentry/core` build (`SPAN_KIND` export error), failing every bundler run. Pack `@sentry/server-utils` in `setup.mjs` and add a `pnpm.overrides` entry for it to every fixture, pointing at the local tarball like core and bundler-plugins. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01D7JDQBD9J2okCe1hkWCanU --- .../fixtures/esbuild/package.json | 1 + .../fixtures/rolldown/package.json | 1 + .../fixtures/rollup3/package.json | 1 + .../fixtures/rollup4/package.json | 1 + .../fixtures/vite4/package.json | 1 + .../fixtures/vite6/package.json | 1 + .../fixtures/vite7/package.json | 1 + .../fixtures/vite8/package.json | 1 + .../fixtures/webpack5/package.json | 1 + dev-packages/bundler-plugin-integration-tests/setup.mjs | 9 +++++++++ 10 files changed, 18 insertions(+) diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/package.json b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/package.json index cd13efd2a1a3..d37558636e56 100644 --- a/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/package.json +++ b/dev-packages/bundler-plugin-integration-tests/fixtures/esbuild/package.json @@ -11,6 +11,7 @@ "overrides": { "@sentry/bundler-plugins": "file:../../../../packages/bundler-plugins/sentry-bundler-plugins-10.67.0.tgz", "@sentry/core": "file:../../../../packages/core/sentry-core-10.67.0.tgz", + "@sentry/server-utils": "file:../../../../packages/server-utils/sentry-server-utils-10.67.0.tgz", "sentry": "file:../sentry-stub" } } diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/package.json b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/package.json index 81bcfb4ccfe3..3ae542023adf 100644 --- a/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/package.json +++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rolldown/package.json @@ -12,6 +12,7 @@ "overrides": { "@sentry/bundler-plugins": "file:../../../../packages/bundler-plugins/sentry-bundler-plugins-10.67.0.tgz", "@sentry/core": "file:../../../../packages/core/sentry-core-10.67.0.tgz", + "@sentry/server-utils": "file:../../../../packages/server-utils/sentry-server-utils-10.67.0.tgz", "sentry": "file:../sentry-stub" } } diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/package.json b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/package.json index edf910f23e3f..a0af94ec91bf 100644 --- a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/package.json +++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup3/package.json @@ -14,6 +14,7 @@ "overrides": { "@sentry/bundler-plugins": "file:../../../../packages/bundler-plugins/sentry-bundler-plugins-10.67.0.tgz", "@sentry/core": "file:../../../../packages/core/sentry-core-10.67.0.tgz", + "@sentry/server-utils": "file:../../../../packages/server-utils/sentry-server-utils-10.67.0.tgz", "sentry": "file:../sentry-stub" } } diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/package.json b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/package.json index be72e1b2d1f8..088d70d499b7 100644 --- a/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/package.json +++ b/dev-packages/bundler-plugin-integration-tests/fixtures/rollup4/package.json @@ -14,6 +14,7 @@ "overrides": { "@sentry/bundler-plugins": "file:../../../../packages/bundler-plugins/sentry-bundler-plugins-10.67.0.tgz", "@sentry/core": "file:../../../../packages/core/sentry-core-10.67.0.tgz", + "@sentry/server-utils": "file:../../../../packages/server-utils/sentry-server-utils-10.67.0.tgz", "sentry": "file:../sentry-stub" } } diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/package.json b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/package.json index 5ea315aea92d..070320dfd435 100644 --- a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/package.json +++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/package.json @@ -13,6 +13,7 @@ "overrides": { "@sentry/bundler-plugins": "file:../../../../packages/bundler-plugins/sentry-bundler-plugins-10.67.0.tgz", "@sentry/core": "file:../../../../packages/core/sentry-core-10.67.0.tgz", + "@sentry/server-utils": "file:../../../../packages/server-utils/sentry-server-utils-10.67.0.tgz", "sentry": "file:../sentry-stub" } } diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite6/package.json b/dev-packages/bundler-plugin-integration-tests/fixtures/vite6/package.json index 282017b87c29..aa19642892a8 100644 --- a/dev-packages/bundler-plugin-integration-tests/fixtures/vite6/package.json +++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite6/package.json @@ -11,6 +11,7 @@ "overrides": { "@sentry/bundler-plugins": "file:../../../../packages/bundler-plugins/sentry-bundler-plugins-10.67.0.tgz", "@sentry/core": "file:../../../../packages/core/sentry-core-10.67.0.tgz", + "@sentry/server-utils": "file:../../../../packages/server-utils/sentry-server-utils-10.67.0.tgz", "sentry": "file:../sentry-stub" } } diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/package.json b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/package.json index ac70089d8902..5e9789da70b2 100644 --- a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/package.json +++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/package.json @@ -13,6 +13,7 @@ "overrides": { "@sentry/bundler-plugins": "file:../../../../packages/bundler-plugins/sentry-bundler-plugins-10.67.0.tgz", "@sentry/core": "file:../../../../packages/core/sentry-core-10.67.0.tgz", + "@sentry/server-utils": "file:../../../../packages/server-utils/sentry-server-utils-10.67.0.tgz", "sentry": "file:../sentry-stub" } } diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/package.json b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/package.json index 28c5437684e9..29d96a618222 100644 --- a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/package.json +++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/package.json @@ -13,6 +13,7 @@ "overrides": { "@sentry/bundler-plugins": "file:../../../../packages/bundler-plugins/sentry-bundler-plugins-10.67.0.tgz", "@sentry/core": "file:../../../../packages/core/sentry-core-10.67.0.tgz", + "@sentry/server-utils": "file:../../../../packages/server-utils/sentry-server-utils-10.67.0.tgz", "sentry": "file:../sentry-stub" } } diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/package.json b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/package.json index 33a4b1933834..c4455a97a634 100644 --- a/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/package.json +++ b/dev-packages/bundler-plugin-integration-tests/fixtures/webpack5/package.json @@ -14,6 +14,7 @@ "overrides": { "@sentry/bundler-plugins": "file:../../../../packages/bundler-plugins/sentry-bundler-plugins-10.67.0.tgz", "@sentry/core": "file:../../../../packages/core/sentry-core-10.67.0.tgz", + "@sentry/server-utils": "file:../../../../packages/server-utils/sentry-server-utils-10.67.0.tgz", "sentry": "file:../sentry-stub" } } diff --git a/dev-packages/bundler-plugin-integration-tests/setup.mjs b/dev-packages/bundler-plugin-integration-tests/setup.mjs index e1b5146b9b33..d0385b7147a2 100644 --- a/dev-packages/bundler-plugin-integration-tests/setup.mjs +++ b/dev-packages/bundler-plugin-integration-tests/setup.mjs @@ -26,6 +26,15 @@ execSync('yarn build:dev:filter @sentry/core', { cwd: repoRoot, stdio: 'inherit' console.log('Packing @sentry/core...'); execSync('yarn build:tarball', { cwd: coreDir, stdio: 'inherit' }); +// `@sentry/bundler-plugins` externalizes `@sentry/server-utils` (it `require`s it at build time), so +// the fixtures need the local tarball to match the local `@sentry/core` build above — otherwise pnpm +// pulls a mismatched published version from the registry. +const serverUtilsDir = join(repoRoot, 'packages', 'server-utils'); +console.log('Building @sentry/server-utils and its workspace dependencies...'); +execSync('yarn build:dev:filter @sentry/server-utils', { cwd: repoRoot, stdio: 'inherit' }); +console.log('Packing @sentry/server-utils...'); +execSync('yarn build:tarball', { cwd: serverUtilsDir, stdio: 'inherit' }); + console.log('Building @sentry/bundler-plugins and its workspace dependencies...'); execSync('yarn build:dev:filter @sentry/bundler-plugins', { cwd: repoRoot, stdio: 'inherit' }); console.log('Packing @sentry/bundler-plugins...'); From d315bf67a40639e970161924a05260dbb44021bf Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 2 Sep 2026 15:33:37 +0200 Subject: [PATCH 6/8] test(e2e): Use Sentry.flush() in nuxt-3 custom error handler The nuxt-3 app imported `flushIfServerless` from `@sentry/core/server`, which no longer exports it after the move to `@sentry/server-utils`. Use the public `Sentry.flush()` (already imported as `SentryNode`) instead of reaching into an internal entry point. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01D7JDQBD9J2okCe1hkWCanU --- .../nuxt-3/server/plugins/customNitroErrorHandler.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/nuxt-3/server/plugins/customNitroErrorHandler.ts b/dev-packages/e2e-tests/test-applications/nuxt-3/server/plugins/customNitroErrorHandler.ts index 650b69215830..1cf9e49fe48b 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-3/server/plugins/customNitroErrorHandler.ts +++ b/dev-packages/e2e-tests/test-applications/nuxt-3/server/plugins/customNitroErrorHandler.ts @@ -1,5 +1,4 @@ import { Context } from '@sentry/core'; -import { flushIfServerless } from '@sentry/core/server'; import * as SentryNode from '@sentry/node'; import { H3Error } from 'h3'; import type { CapturedErrorContext } from 'nitropack'; @@ -32,7 +31,7 @@ export default defineNitroPlugin(nitroApp => { mechanism: { handled: false }, }); - await flushIfServerless(); + await SentryNode.flush(); }); }); From 6877e7273120477e518c2813f5a5cc1205fe8e4c Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 2 Sep 2026 16:10:53 +0200 Subject: [PATCH 7/8] fix(server-utils): Keep node:async_hooks out of the edge/browser barrels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Hydrogen (Oxygen) e2e app `remix-hydrogen` failed to build: its client bundle pulled `@sentry/server-utils/async-context` (`import { AsyncLocalStorage } from 'node:async_hooks'`), which Vite externalizes into an empty stub, throwing `"AsyncLocalStorage" is not exported by "__vite-browser-external"`. `async-context` is the only module in the shared `exports.ts` surface that statically imports a `node:` builtin, yet it was re-exported from both the `index` and `no-diagnostic-channels` barrels — so any browser/edge bundle importing *any* helper from those barrels dragged `node:async_hooks` into the graph. - Move `setAsyncLocalStorageAsyncContextStrategy` out of the shared `exports.ts` into a dedicated `@sentry/server-utils/async-context` entry, so `index` / `no-diagnostic-channels` are free of `node:` builtins. Update its consumers (Node/Deno/Cloudflare SDKs + tests), all of which run where `node:async_hooks` resolves. - Point the two `@sentry/remix/cloudflare`-reachable imports (`instrumentServer`'s `loadModule`, `cloudflare/index`'s `trpcMiddleware`) at the lean `no-diagnostic-channels` barrel instead of the full `@sentry/server-utils` barrel, whose Node-only integrations (`tedious` → `node:events`, …) otherwise reach the bundled Hydrogen client build. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01D7JDQBD9J2okCe1hkWCanU --- packages/cloudflare/src/durableobject.ts | 2 +- packages/cloudflare/src/index.ts | 2 +- .../src/instrumentations/instrumentWorkerEntrypoint.ts | 2 +- packages/cloudflare/src/pages-plugin.ts | 2 +- packages/cloudflare/src/withSentry.ts | 2 +- packages/cloudflare/src/workflows.ts | 2 +- packages/cloudflare/test/client.test.ts | 2 +- .../cloudflare/test/instrumentCloudflareAgent.test.ts | 2 +- .../instrumentations/instrumentWorkerEntrypoint.test.ts | 2 +- packages/cloudflare/test/opentelemetry.test.ts | 2 +- packages/cloudflare/test/request.test.ts | 2 +- packages/cloudflare/test/requestModuleGraph.test.ts | 3 ++- packages/cloudflare/test/utils/invocationContext.test.ts | 2 +- packages/cloudflare/test/utils/invocationScope.test.ts | 2 +- packages/deno/src/client.ts | 2 +- packages/node/src/sdk/client.ts | 2 +- packages/node/test/sdk/client.test.ts | 2 +- packages/node/test/sdk/init.test.ts | 5 +++-- packages/remix/src/cloudflare/index.ts | 5 ++++- packages/remix/src/server/instrumentServer.ts | 5 ++++- packages/server-utils/package.json | 5 +++++ packages/server-utils/rollup.npm.config.mjs | 1 + packages/server-utils/src/exports.ts | 1 - packages/server-utils/src/index.async-context.ts | 8 ++++++++ 24 files changed, 43 insertions(+), 22 deletions(-) create mode 100644 packages/server-utils/src/index.async-context.ts diff --git a/packages/cloudflare/src/durableobject.ts b/packages/cloudflare/src/durableobject.ts index 7626cb1893d5..3564f2cf90a5 100644 --- a/packages/cloudflare/src/durableobject.ts +++ b/packages/cloudflare/src/durableobject.ts @@ -2,7 +2,7 @@ import { RPC } from '@sentry/conventions/op'; import { isObjectLike } from '@sentry/core'; import type { DurableObject } from 'cloudflare:workers'; -import { setAsyncLocalStorageAsyncContextStrategy } from '@sentry/server-utils/no-diagnostic-channels'; +import { setAsyncLocalStorageAsyncContextStrategy } from '@sentry/server-utils/async-context'; import type { CloudflareOptions } from './client'; import { getInstrumented, markAsInstrumented } from './instrument'; import { instrumentDurableObjectHandlers } from './instrumentations/instrumentDurableObjectHandlers'; diff --git a/packages/cloudflare/src/index.ts b/packages/cloudflare/src/index.ts index 7d4809bba808..4c65568b851d 100644 --- a/packages/cloudflare/src/index.ts +++ b/packages/cloudflare/src/index.ts @@ -137,4 +137,4 @@ export { export { instrumentWorkflowWithSentry } from './workflows'; -export { setAsyncLocalStorageAsyncContextStrategy } from '@sentry/server-utils/no-diagnostic-channels'; +export { setAsyncLocalStorageAsyncContextStrategy } from '@sentry/server-utils/async-context'; diff --git a/packages/cloudflare/src/instrumentations/instrumentWorkerEntrypoint.ts b/packages/cloudflare/src/instrumentations/instrumentWorkerEntrypoint.ts index 25d54895ec17..4aa23e173232 100644 --- a/packages/cloudflare/src/instrumentations/instrumentWorkerEntrypoint.ts +++ b/packages/cloudflare/src/instrumentations/instrumentWorkerEntrypoint.ts @@ -1,6 +1,6 @@ import type { RpcStub, WorkerEntrypoint } from 'cloudflare:workers'; import { RPC } from '@sentry/conventions/op'; -import { setAsyncLocalStorageAsyncContextStrategy } from '@sentry/server-utils/no-diagnostic-channels'; +import { setAsyncLocalStorageAsyncContextStrategy } from '@sentry/server-utils/async-context'; import type { CloudflareOptions } from '../client'; import { getFinalOptions } from '../options'; import type { DefaultEnv, ResolveEnv, StrictCloudflareOptions } from '../types'; diff --git a/packages/cloudflare/src/pages-plugin.ts b/packages/cloudflare/src/pages-plugin.ts index c86a3e191217..857e6e83c2f6 100644 --- a/packages/cloudflare/src/pages-plugin.ts +++ b/packages/cloudflare/src/pages-plugin.ts @@ -1,4 +1,4 @@ -import { setAsyncLocalStorageAsyncContextStrategy } from '@sentry/server-utils/no-diagnostic-channels'; +import { setAsyncLocalStorageAsyncContextStrategy } from '@sentry/server-utils/async-context'; import type { CloudflareOptions } from './client'; import type { ExecutionContextCompat } from './executionContext'; import { wrapRequestHandlerWithInit } from './wrapRequestHandlerWithInit'; diff --git a/packages/cloudflare/src/withSentry.ts b/packages/cloudflare/src/withSentry.ts index d26cc30da60c..e34b6bc23df3 100644 --- a/packages/cloudflare/src/withSentry.ts +++ b/packages/cloudflare/src/withSentry.ts @@ -1,4 +1,4 @@ -import { setAsyncLocalStorageAsyncContextStrategy } from '@sentry/server-utils/no-diagnostic-channels'; +import { setAsyncLocalStorageAsyncContextStrategy } from '@sentry/server-utils/async-context'; import { instrumentExportedHandlerEmail } from './instrumentations/worker/instrumentEmail'; import { instrumentExportedHandlerFetch } from './instrumentations/worker/instrumentFetch'; import { instrumentExportedHandlerQueue } from './instrumentations/worker/instrumentQueue'; diff --git a/packages/cloudflare/src/workflows.ts b/packages/cloudflare/src/workflows.ts index 3f580d862af6..c767d2b36b54 100644 --- a/packages/cloudflare/src/workflows.ts +++ b/packages/cloudflare/src/workflows.ts @@ -22,7 +22,7 @@ import type { WorkflowStepRollbackOptions, WorkflowTimeoutDuration, } from 'cloudflare:workers'; -import { setAsyncLocalStorageAsyncContextStrategy } from '@sentry/server-utils/no-diagnostic-channels'; +import { setAsyncLocalStorageAsyncContextStrategy } from '@sentry/server-utils/async-context'; import type { CloudflareOptions } from './client'; import { flushAndDispose, getOriginalWaitUntil } from './flush'; import { instrumentEnv } from './instrumentations/worker/instrumentEnv'; diff --git a/packages/cloudflare/test/client.test.ts b/packages/cloudflare/test/client.test.ts index 09bff574e479..c7fe592bc2a1 100644 --- a/packages/cloudflare/test/client.test.ts +++ b/packages/cloudflare/test/client.test.ts @@ -1,5 +1,5 @@ import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; -import { setAsyncLocalStorageAsyncContextStrategy } from '@sentry/server-utils/no-diagnostic-channels'; +import { setAsyncLocalStorageAsyncContextStrategy } from '@sentry/server-utils/async-context'; import { CloudflareClient, type CloudflareClientOptions } from '../src/client'; import { makeFlushLock } from '../src/flush'; import { getInvocationState } from '../src/utils/invocationContext'; diff --git a/packages/cloudflare/test/instrumentCloudflareAgent.test.ts b/packages/cloudflare/test/instrumentCloudflareAgent.test.ts index 618377156fd1..f5f8f9f566cf 100644 --- a/packages/cloudflare/test/instrumentCloudflareAgent.test.ts +++ b/packages/cloudflare/test/instrumentCloudflareAgent.test.ts @@ -8,7 +8,7 @@ import { startSpan, } from '@sentry/core'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { setAsyncLocalStorageAsyncContextStrategy } from '@sentry/server-utils/no-diagnostic-channels'; +import { setAsyncLocalStorageAsyncContextStrategy } from '@sentry/server-utils/async-context'; import { CloudflareClient, type CloudflareClientOptions } from '../src/client'; import { withStaticSpan } from '../src/index'; import { instrumentCloudflareAgent } from '../src/instrumentations/agents'; diff --git a/packages/cloudflare/test/instrumentations/instrumentWorkerEntrypoint.test.ts b/packages/cloudflare/test/instrumentations/instrumentWorkerEntrypoint.test.ts index e99afb986319..b87c98f2496b 100644 --- a/packages/cloudflare/test/instrumentations/instrumentWorkerEntrypoint.test.ts +++ b/packages/cloudflare/test/instrumentations/instrumentWorkerEntrypoint.test.ts @@ -176,7 +176,7 @@ describe('instrumentWorkerEntrypoint', () => { }); it('Calls setAsyncLocalStorageAsyncContextStrategy outside Proxy (at instrumentation time), not inside construct', async () => { - const asyncModule = await import('@sentry/server-utils/no-diagnostic-channels'); + const asyncModule = await import('@sentry/server-utils/async-context'); const setStrategy = vi.spyOn(asyncModule, 'setAsyncLocalStorageAsyncContextStrategy'); const mockContext = createMockExecutionContext(); const TestClass = class extends WorkerEntrypoint { diff --git a/packages/cloudflare/test/opentelemetry.test.ts b/packages/cloudflare/test/opentelemetry.test.ts index afc4575fa343..86bbe205b36e 100644 --- a/packages/cloudflare/test/opentelemetry.test.ts +++ b/packages/cloudflare/test/opentelemetry.test.ts @@ -1,7 +1,7 @@ import { trace } from '@opentelemetry/api'; import type { TransactionEvent } from '@sentry/core'; import { getActiveSpan, spanToJSON, startSpan } from '@sentry/core'; -import { setAsyncLocalStorageAsyncContextStrategy } from '@sentry/server-utils/no-diagnostic-channels'; +import { setAsyncLocalStorageAsyncContextStrategy } from '@sentry/server-utils/async-context'; import { beforeEach, describe, expect, test, vi } from 'vitest'; import type { CloudflareOptions } from '../src/client'; import { wrapRequestHandler } from '../src/request'; diff --git a/packages/cloudflare/test/request.test.ts b/packages/cloudflare/test/request.test.ts index adfd8c5f848b..0b53443d3aa6 100644 --- a/packages/cloudflare/test/request.test.ts +++ b/packages/cloudflare/test/request.test.ts @@ -5,7 +5,7 @@ import type { ExecutionContext } from '@cloudflare/workers-types'; import type { Event } from '@sentry/core'; import * as SentryCore from '@sentry/core'; import { beforeAll, beforeEach, describe, expect, onTestFinished, test, vi } from 'vitest'; -import { setAsyncLocalStorageAsyncContextStrategy } from '@sentry/server-utils/no-diagnostic-channels'; +import { setAsyncLocalStorageAsyncContextStrategy } from '@sentry/server-utils/async-context'; import type { CloudflareOptions } from '../src/client'; import { CloudflareClient } from '../src/client'; import { httpServerIntegration } from '../src/integrations/httpServer'; diff --git a/packages/cloudflare/test/requestModuleGraph.test.ts b/packages/cloudflare/test/requestModuleGraph.test.ts index 9b6f26322d24..750335c8927f 100644 --- a/packages/cloudflare/test/requestModuleGraph.test.ts +++ b/packages/cloudflare/test/requestModuleGraph.test.ts @@ -75,7 +75,8 @@ describe('module graph of `wrapRequestHandler`', () => { externals.filter( specifier => /^@sentry\/node(\/|$)/.test(specifier) || - (/^@sentry\/server-utils(\/|$)/.test(specifier) && specifier !== '@sentry/server-utils/no-diagnostic-channels'), + (/^@sentry\/server-utils(\/|$)/.test(specifier) && + specifier !== '@sentry/server-utils/no-diagnostic-channels'), ), ).toEqual([]); }); diff --git a/packages/cloudflare/test/utils/invocationContext.test.ts b/packages/cloudflare/test/utils/invocationContext.test.ts index cd05ebf6b8cd..9b71948bec67 100644 --- a/packages/cloudflare/test/utils/invocationContext.test.ts +++ b/packages/cloudflare/test/utils/invocationContext.test.ts @@ -1,6 +1,6 @@ import { debug, getDefaultIsolationScope, getIsolationScope, GLOBAL_OBJ, withIsolationScope } from '@sentry/core'; import { AsyncLocalStorage } from 'async_hooks'; -import { setAsyncLocalStorageAsyncContextStrategy } from '@sentry/server-utils/no-diagnostic-channels'; +import { setAsyncLocalStorageAsyncContextStrategy } from '@sentry/server-utils/async-context'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { getInvocationState, getInvocationWaitUntil, setInvocationState } from '../../src/utils/invocationContext'; import { withInvocationIsolationScope } from '../../src/utils/invocationScope'; diff --git a/packages/cloudflare/test/utils/invocationScope.test.ts b/packages/cloudflare/test/utils/invocationScope.test.ts index a436597f4113..d65ad3484870 100644 --- a/packages/cloudflare/test/utils/invocationScope.test.ts +++ b/packages/cloudflare/test/utils/invocationScope.test.ts @@ -1,6 +1,6 @@ import { getIsolationScope, getMainCarrier, GLOBAL_OBJ, type Scope, setAsyncContextStrategy } from '@sentry/core'; import { AsyncLocalStorage } from 'async_hooks'; -import { setAsyncLocalStorageAsyncContextStrategy } from '@sentry/server-utils/no-diagnostic-channels'; +import { setAsyncLocalStorageAsyncContextStrategy } from '@sentry/server-utils/async-context'; import { beforeEach, describe, expect, it } from 'vitest'; import { withInvocationIsolationScope } from '../../src/utils/invocationScope'; diff --git a/packages/deno/src/client.ts b/packages/deno/src/client.ts index 0eb349fe2710..2b813428973b 100644 --- a/packages/deno/src/client.ts +++ b/packages/deno/src/client.ts @@ -1,7 +1,7 @@ import type { ServerRuntimeClientOptions } from '@sentry/server-utils'; import { _INTERNAL_flushLogsBuffer, SDK_VERSION } from '@sentry/core'; import { ServerRuntimeClient } from '@sentry/server-utils'; -import { setAsyncLocalStorageAsyncContextStrategy } from '@sentry/server-utils'; +import { setAsyncLocalStorageAsyncContextStrategy } from '@sentry/server-utils/async-context'; import type { DenoClientOptions } from './types'; function getHostName(): string | undefined { diff --git a/packages/node/src/sdk/client.ts b/packages/node/src/sdk/client.ts index a81e2fd5cb5b..a9aa76e599f6 100644 --- a/packages/node/src/sdk/client.ts +++ b/packages/node/src/sdk/client.ts @@ -17,7 +17,7 @@ import { type SentryTracerProvider, setOpenTelemetryContextAsyncContextStrategy, } from '@sentry/opentelemetry'; -import { setAsyncLocalStorageAsyncContextStrategy } from '@sentry/server-utils'; +import { setAsyncLocalStorageAsyncContextStrategy } from '@sentry/server-utils/async-context'; import { isMainThread, threadId } from 'worker_threads'; import { DEBUG_BUILD } from '../debug-build'; import type { NodeClientOptions } from '../types'; diff --git a/packages/node/test/sdk/client.test.ts b/packages/node/test/sdk/client.test.ts index 1792dd6da0eb..0353a5f6b18d 100644 --- a/packages/node/test/sdk/client.test.ts +++ b/packages/node/test/sdk/client.test.ts @@ -5,7 +5,7 @@ import { getAsyncContextStrategy, getMainCarrier, Scope, SDK_VERSION } from '@se import type { SentryTracerProvider } from '@sentry/opentelemetry'; import { setOpenTelemetryContextAsyncContextStrategy } from '@sentry/opentelemetry'; import * as SentryOpentelemetry from '@sentry/opentelemetry'; -import * as SentryServerUtils from '@sentry/server-utils'; +import * as SentryServerUtils from '@sentry/server-utils/async-context'; import * as os from 'os'; import { afterEach, beforeEach, describe, expect, it, test, vi } from 'vitest'; import { NodeClient } from '../../src'; diff --git a/packages/node/test/sdk/init.test.ts b/packages/node/test/sdk/init.test.ts index 3a9af657e3d9..0d1af1f2f450 100644 --- a/packages/node/test/sdk/init.test.ts +++ b/packages/node/test/sdk/init.test.ts @@ -2,6 +2,7 @@ import type { Integration } from '@sentry/core'; import { debug, SDK_VERSION } from '@sentry/core'; import * as SentryOpentelemetry from '@sentry/opentelemetry'; import * as SentryServerUtils from '@sentry/server-utils'; +import * as SentryServerUtilsAsyncContext from '@sentry/server-utils/async-context'; import { afterEach, beforeEach, describe, expect, it, type Mock, type MockInstance, vi } from 'vitest'; import { getClient, NodeClient } from '../../src/'; import { init } from '../../src/sdk'; @@ -209,7 +210,7 @@ describe('init()', () => { }); it('uses the AsyncLocalStorage context strategy by default', () => { - const alsStrategySpy = vi.spyOn(SentryServerUtils, 'setAsyncLocalStorageAsyncContextStrategy'); + const alsStrategySpy = vi.spyOn(SentryServerUtilsAsyncContext, 'setAsyncLocalStorageAsyncContextStrategy'); const otelStrategySpy = vi.spyOn(SentryOpentelemetry, 'setOpenTelemetryContextAsyncContextStrategy'); init({ dsn: PUBLIC_DSN }); @@ -227,7 +228,7 @@ describe('init()', () => { }); it('uses the OpenTelemetry context strategy when opting in', () => { - const alsStrategySpy = vi.spyOn(SentryServerUtils, 'setAsyncLocalStorageAsyncContextStrategy'); + const alsStrategySpy = vi.spyOn(SentryServerUtilsAsyncContext, 'setAsyncLocalStorageAsyncContextStrategy'); const otelStrategySpy = vi.spyOn(SentryOpentelemetry, 'setOpenTelemetryContextAsyncContextStrategy'); init({ dsn: PUBLIC_DSN, enableOpenTelemetrySetup: true }); diff --git a/packages/remix/src/cloudflare/index.ts b/packages/remix/src/cloudflare/index.ts index a0f4276f0c95..47b515b80b9a 100644 --- a/packages/remix/src/cloudflare/index.ts +++ b/packages/remix/src/cloudflare/index.ts @@ -120,4 +120,7 @@ export { withStreamedSpan, featureFlagsIntegration, } from '@sentry/core'; -export { trpcMiddleware } from '@sentry/server-utils'; +// Import from the lean `no-diagnostic-channels` barrel rather than the full `@sentry/server-utils` +// barrel: this cloudflare entry is bundled into edge/browser builds (e.g. Hydrogen on Oxygen), where +// the full barrel's Node-only integrations would drag `node:` builtins into the bundle. +export { trpcMiddleware } from '@sentry/server-utils/no-diagnostic-channels'; diff --git a/packages/remix/src/server/instrumentServer.ts b/packages/remix/src/server/instrumentServer.ts index 9010010900c7..1ea9b59e00d1 100644 --- a/packages/remix/src/server/instrumentServer.ts +++ b/packages/remix/src/server/instrumentServer.ts @@ -35,7 +35,10 @@ import { filterCollectedUrl, } from '@sentry/core'; import { isNodeEnv } from '@sentry/core/server'; -import { loadModule } from '@sentry/server-utils'; +// `no-diagnostic-channels` (not the full `@sentry/server-utils` barrel): this module is reachable from +// the `@sentry/remix/cloudflare` entry, which is bundled into edge/browser builds where the full +// barrel's Node-only integrations would drag `node:` builtins into the bundle. +import { loadModule } from '@sentry/server-utils/no-diagnostic-channels'; import { DEBUG_BUILD } from '../utils/debug-build'; import { createRoutes, getTransactionName, isCloudflareEnv } from '../utils/utils'; import { extractData, isResponse, json } from '../utils/vendor/response'; diff --git a/packages/server-utils/package.json b/packages/server-utils/package.json index 639afedbdab5..178374c6e982 100644 --- a/packages/server-utils/package.json +++ b/packages/server-utils/package.json @@ -27,6 +27,11 @@ "import": "./build/esm/index.no-diagnostic-channels.js", "require": "./build/cjs/index.no-diagnostic-channels.js" }, + "./async-context": { + "types": "./build/types/index.async-context.d.ts", + "import": "./build/esm/index.async-context.js", + "require": "./build/cjs/index.async-context.js" + }, "./orchestrion/config": { "types": "./build/types/orchestrion/config/index.d.ts", "import": "./build/esm/orchestrion/config/index.js", diff --git a/packages/server-utils/rollup.npm.config.mjs b/packages/server-utils/rollup.npm.config.mjs index c313a9f1cc4c..b1a78cddcab1 100644 --- a/packages/server-utils/rollup.npm.config.mjs +++ b/packages/server-utils/rollup.npm.config.mjs @@ -98,6 +98,7 @@ export default [ entrypoints: [ 'src/index.ts', 'src/index.no-diagnostic-channels.ts', + 'src/index.async-context.ts', 'src/orchestrion/config/index.ts', 'src/orchestrion/bundler/vite.ts', 'src/orchestrion/bundler/rollup.ts', diff --git a/packages/server-utils/src/exports.ts b/packages/server-utils/src/exports.ts index 856a9da14e51..5f7de9735433 100644 --- a/packages/server-utils/src/exports.ts +++ b/packages/server-utils/src/exports.ts @@ -9,6 +9,5 @@ export { ServerRuntimeClient } from './server-runtime-client'; export type { ServerRuntimeClientOptions } from './server-runtime-client'; export type { ServerRuntimeOptions } from './types/options'; export { trpcMiddleware } from './trpc'; -export { setAsyncLocalStorageAsyncContextStrategy } from './async-context'; export { openTelemetryIntegration, getOtlpTracesEndpoint } from './opentelemetry'; export * from './ai'; diff --git a/packages/server-utils/src/index.async-context.ts b/packages/server-utils/src/index.async-context.ts new file mode 100644 index 000000000000..4ff995b92ebc --- /dev/null +++ b/packages/server-utils/src/index.async-context.ts @@ -0,0 +1,8 @@ +// `setAsyncLocalStorageAsyncContextStrategy` is the only server-utils primitive that statically +// imports a `node:` builtin (`node:async_hooks`). Kept out of the shared `exports.ts` barrel (and +// thus out of the `index` / `no-diagnostic-channels` entries) so that browser/edge bundles which +// import any other helper from those barrels don't drag `node:async_hooks` — which bundlers targeting +// those runtimes externalize into an empty stub, breaking the build. Consumers that actually install +// the strategy (Node/Deno/Cloudflare SDKs, all of which run where `node:async_hooks` resolves) import +// it from this dedicated entry. +export { setAsyncLocalStorageAsyncContextStrategy } from './async-context'; From a6bdaf392b8c62ee15850b123033ce341dda2ec4 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Thu, 3 Sep 2026 13:03:46 +0200 Subject: [PATCH 8/8] fixes --- packages/react-router/src/server/createSentryHandleError.ts | 2 +- .../react-router/src/server/createServerInstrumentation.ts | 2 +- packages/react-router/src/server/wrapSentryHandleRequest.ts | 2 +- .../react-router/test/server/createSentryHandleError.test.ts | 4 ++-- .../test/server/createServerInstrumentation.test.ts | 4 ++-- .../react-router/test/server/wrapSentryHandleRequest.test.ts | 4 ++-- packages/sveltekit/src/server-common/handle.ts | 2 +- packages/sveltekit/src/server-common/handleError.ts | 2 +- packages/sveltekit/src/server-common/load.ts | 2 +- packages/sveltekit/src/server-common/serverRoute.ts | 2 +- .../tanstackstart-react/src/server/wrapFetchWithSentry.ts | 2 +- .../test/server/wrapFetchWithSentry.test.ts | 2 +- 12 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/react-router/src/server/createSentryHandleError.ts b/packages/react-router/src/server/createSentryHandleError.ts index ff3893da60c7..46a21445b6bc 100644 --- a/packages/react-router/src/server/createSentryHandleError.ts +++ b/packages/react-router/src/server/createSentryHandleError.ts @@ -1,5 +1,5 @@ import { captureException } from '@sentry/core'; -import { flushIfServerless } from '@sentry/server-utils'; +import { flushIfServerless } from '@sentry/server-utils/no-diagnostic-channels'; import type { HandleErrorFunction } from 'react-router'; export type SentryHandleErrorOptions = { diff --git a/packages/react-router/src/server/createServerInstrumentation.ts b/packages/react-router/src/server/createServerInstrumentation.ts index 18839cae0f6e..b7acce128165 100644 --- a/packages/react-router/src/server/createServerInstrumentation.ts +++ b/packages/react-router/src/server/createServerInstrumentation.ts @@ -22,7 +22,7 @@ import { updateSpanName, filterCollectedUrl, } from '@sentry/core'; -import { flushIfServerless } from '@sentry/server-utils'; +import { flushIfServerless } from '@sentry/server-utils/no-diagnostic-channels'; import type { ServerInstrumentation } from 'react-router'; import { DEBUG_BUILD } from '../common/debug-build'; import { captureInstrumentationError, getPathFromRequest, getPattern, normalizeRoutePath } from '../common/utils'; diff --git a/packages/react-router/src/server/wrapSentryHandleRequest.ts b/packages/react-router/src/server/wrapSentryHandleRequest.ts index ae4df4083148..b74a166b4b8b 100644 --- a/packages/react-router/src/server/wrapSentryHandleRequest.ts +++ b/packages/react-router/src/server/wrapSentryHandleRequest.ts @@ -6,7 +6,7 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, updateSpanName, } from '@sentry/core'; -import { flushIfServerless } from '@sentry/server-utils'; +import { flushIfServerless } from '@sentry/server-utils/no-diagnostic-channels'; import type { AppLoadContext, EntryContext, RouterContextProvider } from 'react-router'; import { isInstrumentationApiUsed } from './serverGlobals'; diff --git a/packages/react-router/test/server/createSentryHandleError.test.ts b/packages/react-router/test/server/createSentryHandleError.test.ts index 3af557288249..302f08df21de 100644 --- a/packages/react-router/test/server/createSentryHandleError.test.ts +++ b/packages/react-router/test/server/createSentryHandleError.test.ts @@ -1,5 +1,5 @@ import * as core from '@sentry/core'; -import * as serverUtils from '@sentry/server-utils'; +import * as serverUtils from '@sentry/server-utils/no-diagnostic-channels'; import type { ActionFunctionArgs, LoaderFunctionArgs } from 'react-router'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { createSentryHandleError } from '../../src/server/createSentryHandleError'; @@ -8,7 +8,7 @@ vi.mock('@sentry/core', () => ({ captureException: vi.fn(), })); -vi.mock('@sentry/server-utils', () => ({ +vi.mock('@sentry/server-utils/no-diagnostic-channels', () => ({ flushIfServerless: vi.fn().mockResolvedValue(undefined), })); diff --git a/packages/react-router/test/server/createServerInstrumentation.test.ts b/packages/react-router/test/server/createServerInstrumentation.test.ts index d3e4ccf1b3e7..64de9b78c1e2 100644 --- a/packages/react-router/test/server/createServerInstrumentation.test.ts +++ b/packages/react-router/test/server/createServerInstrumentation.test.ts @@ -1,6 +1,6 @@ import { URL_FULL, URL_PATH } from '@sentry/conventions/attributes'; import * as core from '@sentry/core'; -import * as serverUtils from '@sentry/server-utils'; +import * as serverUtils from '@sentry/server-utils/no-diagnostic-channels'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { createSentryServerInstrumentation, @@ -24,7 +24,7 @@ vi.mock('@sentry/core', async () => { }; }); -vi.mock('@sentry/server-utils', () => ({ +vi.mock('@sentry/server-utils/no-diagnostic-channels', () => ({ flushIfServerless: vi.fn(), })); diff --git a/packages/react-router/test/server/wrapSentryHandleRequest.test.ts b/packages/react-router/test/server/wrapSentryHandleRequest.test.ts index 849679b1380a..2831eb90d4a1 100644 --- a/packages/react-router/test/server/wrapSentryHandleRequest.test.ts +++ b/packages/react-router/test/server/wrapSentryHandleRequest.test.ts @@ -1,7 +1,7 @@ import { PassThrough } from 'node:stream'; import { SENTRY_SEGMENT_NAME_SOURCE, HTTP_ROUTE } from '@sentry/conventions/attributes'; import { getActiveSpan, getRootSpan, getTraceMetaTags, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core'; -import { flushIfServerless } from '@sentry/server-utils'; +import { flushIfServerless } from '@sentry/server-utils/no-diagnostic-channels'; import { beforeEach, describe, expect, test, vi } from 'vitest'; import { getMetaTagTransformer } from '../../src/server/getMetaTagTransformer'; import { wrapSentryHandleRequest } from '../../src/server/wrapSentryHandleRequest'; @@ -16,7 +16,7 @@ vi.mock('@sentry/core', () => ({ GLOBAL_OBJ: globalThis, })); -vi.mock('@sentry/server-utils', () => ({ +vi.mock('@sentry/server-utils/no-diagnostic-channels', () => ({ flushIfServerless: vi.fn(), })); diff --git a/packages/sveltekit/src/server-common/handle.ts b/packages/sveltekit/src/server-common/handle.ts index c37f3565ae9c..dcda1f55b2c1 100644 --- a/packages/sveltekit/src/server-common/handle.ts +++ b/packages/sveltekit/src/server-common/handle.ts @@ -20,7 +20,7 @@ import { withIsolationScope, filterCollectedUrl, } from '@sentry/core'; -import { flushIfServerless } from '@sentry/server-utils'; +import { flushIfServerless } from '@sentry/server-utils/no-diagnostic-channels'; import type { Handle, ResolveOptions } from '@sveltejs/kit'; import { DEBUG_BUILD } from '../common/debug-build'; import { getTracePropagationData, sendErrorToSentry } from './utils'; diff --git a/packages/sveltekit/src/server-common/handleError.ts b/packages/sveltekit/src/server-common/handleError.ts index be4ad20f7fa0..5006e2010bc3 100644 --- a/packages/sveltekit/src/server-common/handleError.ts +++ b/packages/sveltekit/src/server-common/handleError.ts @@ -1,5 +1,5 @@ import { captureException, consoleSandbox } from '@sentry/core'; -import { flushIfServerless } from '@sentry/server-utils'; +import { flushIfServerless } from '@sentry/server-utils/no-diagnostic-channels'; import type { AnyErrorHandler, SentryHandleServerErrorInput } from '../common/handleErrorTypes'; import { shouldCaptureError } from '../common/handleErrorTypes'; import { getCloudflareExecutionContext } from './utils'; diff --git a/packages/sveltekit/src/server-common/load.ts b/packages/sveltekit/src/server-common/load.ts index e1e2748b3f47..a58657b39d10 100644 --- a/packages/sveltekit/src/server-common/load.ts +++ b/packages/sveltekit/src/server-common/load.ts @@ -1,5 +1,5 @@ import { addNonEnumerableProperty, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, startSpan } from '@sentry/core'; -import { flushIfServerless } from '@sentry/server-utils'; +import { flushIfServerless } from '@sentry/server-utils/no-diagnostic-channels'; import { SENTRY_SEGMENT_NAME_SOURCE, CODE_FUNCTION_NAME, diff --git a/packages/sveltekit/src/server-common/serverRoute.ts b/packages/sveltekit/src/server-common/serverRoute.ts index 5c410779df63..8b9e808f10ab 100644 --- a/packages/sveltekit/src/server-common/serverRoute.ts +++ b/packages/sveltekit/src/server-common/serverRoute.ts @@ -1,5 +1,5 @@ import { addNonEnumerableProperty, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, startSpan } from '@sentry/core'; -import { flushIfServerless } from '@sentry/server-utils'; +import { flushIfServerless } from '@sentry/server-utils/no-diagnostic-channels'; import { CODE_FUNCTION_NAME, HTTP_REQUEST_METHOD, SENTRY_OP } from '@sentry/conventions/attributes'; import { FUNCTION } from '@sentry/conventions/op'; import type { RequestEvent } from '@sveltejs/kit'; diff --git a/packages/tanstackstart-react/src/server/wrapFetchWithSentry.ts b/packages/tanstackstart-react/src/server/wrapFetchWithSentry.ts index 144a7d8ab21d..140b5bbb7074 100644 --- a/packages/tanstackstart-react/src/server/wrapFetchWithSentry.ts +++ b/packages/tanstackstart-react/src/server/wrapFetchWithSentry.ts @@ -1,5 +1,5 @@ import { getTraceMetaTags } from '@sentry/core'; -import { flushIfServerless } from '@sentry/server-utils'; +import { flushIfServerless } from '@sentry/server-utils/no-diagnostic-channels'; import { captureException, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, startSpan } from '@sentry/node'; import { SENTRY_OP } from '@sentry/conventions/attributes'; import { FUNCTION } from '@sentry/conventions/op'; diff --git a/packages/tanstackstart-react/test/server/wrapFetchWithSentry.test.ts b/packages/tanstackstart-react/test/server/wrapFetchWithSentry.test.ts index e6d17aa94b5b..185c90c26139 100644 --- a/packages/tanstackstart-react/test/server/wrapFetchWithSentry.test.ts +++ b/packages/tanstackstart-react/test/server/wrapFetchWithSentry.test.ts @@ -28,7 +28,7 @@ vi.mock('@sentry/core', async importOriginal => { }; }); -vi.mock('@sentry/server-utils', async importOriginal => { +vi.mock('@sentry/server-utils/no-diagnostic-channels', async importOriginal => { const original = await importOriginal(); return { ...original,