diff --git a/CHANGELOG.md b/CHANGELOG.md index 419b81051685..0334e291efae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ Work in this release was contributed by @zhongrenfei1-hub. Thank you for your co `@sentry/angular` now officially supports Angular 22. +- **fix(node-core): Read `__SENTRY_SERVER_MODULES__` lazily so Turbopack injection is honored [#21339](https://github.com/getsentry/sentry-javascript/pull/21339)** + + On Next.js 16 / Turbopack production builds, `modulesIntegration` captured `__SENTRY_SERVER_MODULES__` at module-evaluation time, before Turbopack's runtime `globalThis` assignment ran. This silently disabled module-detection-based auto integrations (Vercel AI, OpenAI, Anthropic, Google GenAI, LangChain, LangGraph) and left `event.modules` empty. The value is now read lazily, supporting both the webpack (`DefinePlugin`) and Turbopack (runtime global) injection styles. + - **ref(core): Deprecate `sendDefaultPii` in favor of `dataCollection` [#21277](https://github.com/getsentry/sentry-javascript/pull/21277)** `sendDefaultPii` is deprecated and will be removed in v11. The new `dataCollection` option lets you control each category of collected data. diff --git a/packages/node-core/src/integrations/modules.ts b/packages/node-core/src/integrations/modules.ts index 7079f4a2fab8..701e413d55fd 100644 --- a/packages/node-core/src/integrations/modules.ts +++ b/packages/node-core/src/integrations/modules.ts @@ -1,6 +1,6 @@ import { existsSync, readFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; -import type { IntegrationFn } from '@sentry/core'; +import { GLOBAL_OBJ, type IntegrationFn } from '@sentry/core'; import { isCjs } from '../utils/detection'; type ModuleInfo = Record; @@ -12,10 +12,24 @@ const INTEGRATION_NAME = 'Modules'; declare const __SENTRY_SERVER_MODULES__: Record; /** - * `__SENTRY_SERVER_MODULES__` can be replaced at build time with the modules loaded by the server. - * Right now, we leverage this in Next.js to circumvent the problem that we do not get access to these things at runtime. + * Reads the modules injected at build time into `__SENTRY_SERVER_MODULES__` (e.g. by the Next.js SDK). + * + * Must be read lazily (per call), not captured at module-eval time: webpack replaces the token via + * `DefinePlugin` (available immediately), but Turbopack assigns `globalThis.__SENTRY_SERVER_MODULES__` + * at runtime, *after* this module's imports are hoisted and evaluated. A `const` capture would be + * empty under Turbopack. See getsentry/sentry-javascript#19147. */ -const SERVER_MODULES = typeof __SENTRY_SERVER_MODULES__ === 'undefined' ? {} : __SENTRY_SERVER_MODULES__; +function getServerModules(): Record { + // webpack: the token is replaced with a literal at build time. + if (typeof __SENTRY_SERVER_MODULES__ !== 'undefined') { + return __SENTRY_SERVER_MODULES__; + } + // Turbopack: the value is assigned onto the global object at runtime. + return ( + (GLOBAL_OBJ as typeof GLOBAL_OBJ & { __SENTRY_SERVER_MODULES__?: Record }) + .__SENTRY_SERVER_MODULES__ ?? {} + ); +} const _modulesIntegration = (() => { return { @@ -52,7 +66,7 @@ function getRequireCachePaths(): string[] { /** Extract information about package.json modules */ function collectModules(): ModuleInfo { return { - ...SERVER_MODULES, + ...getServerModules(), ...getModulesFromPackageJson(), ...(isCjs() ? collectRequireModules() : {}), }; diff --git a/packages/node-core/test/integrations/modules.test.ts b/packages/node-core/test/integrations/modules.test.ts new file mode 100644 index 000000000000..31ee5671e40a --- /dev/null +++ b/packages/node-core/test/integrations/modules.test.ts @@ -0,0 +1,36 @@ +import { GLOBAL_OBJ } from '@sentry/core'; +import { afterEach, describe, expect, it, vi } from 'vitest'; + +type GlobalWithModules = typeof GLOBAL_OBJ & { __SENTRY_SERVER_MODULES__?: Record }; + +describe('modulesIntegration', () => { + afterEach(() => { + delete (GLOBAL_OBJ as GlobalWithModules).__SENTRY_SERVER_MODULES__; + vi.resetModules(); + }); + + it('includes modules injected onto the global AFTER this module was evaluated (Turbopack ordering)', async () => { + // Re-evaluate the integration module with no injected global present. This mirrors Turbopack: + // the instrumentation file's hoisted ESM imports evaluate this module *before* its + // `globalThis.__SENTRY_SERVER_MODULES__ = {...}` assignment runs. A module-level capture would + // freeze an empty value here and never see the injection (getsentry/sentry-javascript#19147). + vi.resetModules(); + const { modulesIntegration } = await import('../../src/integrations/modules'); + + // The runtime injection happens only now — after the module is already evaluated. + (GLOBAL_OBJ as GlobalWithModules).__SENTRY_SERVER_MODULES__ = { '@sentry/turbopack-injected': '1.2.3' }; + + const modules = modulesIntegration().getModules?.() ?? {}; + expect(modules['@sentry/turbopack-injected']).toBe('1.2.3'); + }); + + it('reads modules already present before evaluation (webpack DefinePlugin / pre-set global)', async () => { + (GLOBAL_OBJ as GlobalWithModules).__SENTRY_SERVER_MODULES__ = { '@sentry/prebuilt-injected': '4.5.6' }; + + vi.resetModules(); + const { modulesIntegration } = await import('../../src/integrations/modules'); + + const modules = modulesIntegration().getModules?.() ?? {}; + expect(modules['@sentry/prebuilt-injected']).toBe('4.5.6'); + }); +});