From e2207ff92c47e310c3593d772d944f592a4105b0 Mon Sep 17 00:00:00 2001 From: Aditya Reddy <82123567+adityareddy-dev@users.noreply.github.com> Date: Thu, 15 Oct 2026 01:04:10 -0500 Subject: [PATCH] fix(nextjs): Expose common server APIs to Node ESM imports --- packages/nextjs/src/server/index.ts | 15 ++++++- packages/nextjs/test/serverExports.test.ts | 46 ++++++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/packages/nextjs/src/server/index.ts b/packages/nextjs/src/server/index.ts index 6dea488a7d9d..78a85614c841 100644 --- a/packages/nextjs/src/server/index.ts +++ b/packages/nextjs/src/server/index.ts @@ -29,8 +29,19 @@ import { nextjsUseCacheIntegration } from './useCacheInstrumentation'; export * from '@sentry/node'; -// Explicitly re-export so these are statically detectable by turbopack -export { pinoIntegration, vercelAIIntegration } from '@sentry/node'; +// Explicit exports are also detectable by Node's CJS lexer when imported from ESM. +export { + addBreadcrumb, + captureException, + captureMessage, + consoleLoggingIntegration, + isEnabled, + isInitialized, + pinoIntegration, + setContext, + setTag, + vercelAIIntegration, +} from '@sentry/node'; export { captureUnderscoreErrorException } from '../common/pages-router-instrumentation/_error'; diff --git a/packages/nextjs/test/serverExports.test.ts b/packages/nextjs/test/serverExports.test.ts index 558c890e93b8..7ce3c0273585 100644 --- a/packages/nextjs/test/serverExports.test.ts +++ b/packages/nextjs/test/serverExports.test.ts @@ -1,3 +1,4 @@ +import { spawnSync } from 'node:child_process'; import { readFileSync } from 'node:fs'; import { resolve } from 'node:path'; import { init, parse } from 'cjs-module-lexer'; @@ -35,3 +36,48 @@ describe('Node-only integrations are statically detectable exports from every ru }, ); }); + +// Regression for https://github.com/getsentry/sentry-javascript/issues/22791. +describe('common server APIs imported by plain Node ESM', () => { + const commonExports = [ + 'addBreadcrumb', + 'captureException', + 'captureMessage', + 'consoleLoggingIntegration', + 'isEnabled', + 'isInitialized', + 'setContext', + 'setTag', + ]; + let staticExports: string[]; + + beforeAll(async () => { + await init(); + staticExports = parse(readFileSync(resolve(__dirname, '../build/cjs/index.server.js'), 'utf8')).exports; + }); + + it.each(commonExports)('statically exports `%s` from the CJS server build', name => { + expect(staticExports).toContain(name); + }); + + it('exposes named and namespace imports with the same values as require', () => { + const script = ` + import assert from 'node:assert/strict'; + import { createRequire } from 'node:module'; + import * as namespace from '@sentry/nextjs'; + import { ${commonExports.join(', ')} } from '@sentry/nextjs'; + const required = createRequire(import.meta.url)('@sentry/nextjs'); + const named = { ${commonExports.join(', ')} }; + for (const [name, value] of Object.entries(named)) { + assert.equal(typeof value, 'function', name); + assert.equal(value, required[name], name); + assert.equal(namespace[name], required[name], name); + } + `; + const result = spawnSync(process.execPath, ['--input-type=module', '-e', script], { + cwd: resolve(__dirname, '..'), + encoding: 'utf8', + }); + expect(result.status, result.stderr).toBe(0); + }); +});