diff --git a/packages/nextjs/src/config/util.ts b/packages/nextjs/src/config/util.ts index a5def59a66fe..de8ad68cac41 100644 --- a/packages/nextjs/src/config/util.ts +++ b/packages/nextjs/src/config/util.ts @@ -33,15 +33,16 @@ function resolveNextjsPackageJson(): string | undefined { * Checks if the current Next.js version supports the runAfterProductionCompile hook. * This hook was introduced in Next.js 15.4.1. (https://github.com/vercel/next.js/pull/77345) * + * @param version - version string to check. * @returns true if Next.js version is 15.4.1 or higher */ -export function supportsProductionCompileHook(): boolean { - const version = getNextjsVersion(); - if (!version) { +export function supportsProductionCompileHook(version: string): boolean { + const versionToCheck = version; + if (!versionToCheck) { return false; } - const { major, minor, patch } = parseSemver(version); + const { major, minor, patch } = parseSemver(versionToCheck); if (major === undefined || minor === undefined || patch === undefined) { return false; diff --git a/packages/nextjs/src/config/withSentryConfig.ts b/packages/nextjs/src/config/withSentryConfig.ts index b5c2be2f25bb..201c27dc5d0a 100644 --- a/packages/nextjs/src/config/withSentryConfig.ts +++ b/packages/nextjs/src/config/withSentryConfig.ts @@ -253,44 +253,25 @@ function getFinalConfigObject( } let nextMajor: number | undefined; - const isTurbopack = process.env.TURBOPACK; - let isTurbopackSupported = false; if (nextJsVersion) { - const { major, minor, patch, prerelease } = parseSemver(nextJsVersion); + const { major } = parseSemver(nextJsVersion); nextMajor = major; - const isSupportedVersion = - major !== undefined && - minor !== undefined && - patch !== undefined && - (major > 15 || - (major === 15 && minor > 3) || - (major === 15 && minor === 3 && patch === 0 && prerelease === undefined) || - (major === 15 && minor === 3 && patch > 0)); - isTurbopackSupported = isSupportedVersion; - const isSupportedCanary = - major !== undefined && - minor !== undefined && - patch !== undefined && - prerelease !== undefined && - major === 15 && - minor === 3 && - patch === 0 && - prerelease.startsWith('canary.') && - parseInt(prerelease.split('.')[1] || '', 10) >= 28; - const supportsClientInstrumentation = isSupportedCanary || isSupportedVersion; + } - if (!supportsClientInstrumentation && isTurbopack) { - if (process.env.NODE_ENV === 'development') { - // eslint-disable-next-line no-console - console.warn( - `[@sentry/nextjs] WARNING: You are using the Sentry SDK with Turbopack (\`next dev --turbo\`). The Sentry SDK is compatible with Turbopack on Next.js version 15.3.0 or later. You are currently on ${nextJsVersion}. Please upgrade to a newer Next.js version to use the Sentry SDK with Turbopack. Note that the SDK will continue to work for non-Turbopack production builds. This warning is only about dev-mode.`, - ); - } else if (process.env.NODE_ENV === 'production') { - // eslint-disable-next-line no-console - console.warn( - `[@sentry/nextjs] WARNING: You are using the Sentry SDK with Turbopack (\`next build --turbo\`). The Sentry SDK is compatible with Turbopack on Next.js version 15.3.0 or later. You are currently on ${nextJsVersion}. Please upgrade to a newer Next.js version to use the Sentry SDK with Turbopack. Note that as Turbopack is still experimental for production builds, some of the Sentry SDK features like source maps will not work. Follow this issue for progress on Sentry + Turbopack: https://github.com/getsentry/sentry-javascript/issues/8105.`, - ); - } + const isTurbopack = process.env.TURBOPACK; + const isTurbopackSupported = supportsProductionCompileHook(nextJsVersion ?? ''); + + if (!isTurbopackSupported && isTurbopack) { + if (process.env.NODE_ENV === 'development') { + // eslint-disable-next-line no-console + console.warn( + `[@sentry/nextjs] WARNING: You are using the Sentry SDK with Turbopack (\`next dev --turbopack\`). The Sentry SDK is compatible with Turbopack on Next.js version 15.4.1 or later. You are currently on ${nextJsVersion}. Please upgrade to a newer Next.js version to use the Sentry SDK with Turbopack.`, + ); + } else if (process.env.NODE_ENV === 'production') { + // eslint-disable-next-line no-console + console.warn( + `[@sentry/nextjs] WARNING: You are using the Sentry SDK with Turbopack (\`next build --turbopack\`). The Sentry SDK is compatible with Turbopack on Next.js version 15.4.1 or later. You are currently on ${nextJsVersion}. Please upgrade to a newer Next.js version to use the Sentry SDK with Turbopack.`, + ); } } @@ -298,7 +279,7 @@ function getFinalConfigObject( const shouldUseRunAfterProductionCompileHook = userSentryOptions?.useRunAfterProductionCompileHook ?? (isTurbopack ? true : false); - if (shouldUseRunAfterProductionCompileHook && supportsProductionCompileHook()) { + if (shouldUseRunAfterProductionCompileHook && supportsProductionCompileHook(nextJsVersion ?? '')) { if (incomingUserNextConfigObject?.compiler?.runAfterProductionCompile === undefined) { incomingUserNextConfigObject.compiler ??= {}; incomingUserNextConfigObject.compiler.runAfterProductionCompile = async ({ distDir }) => { diff --git a/packages/nextjs/test/config/util.test.ts b/packages/nextjs/test/config/util.test.ts index 01b94480ea5f..b31f71705029 100644 --- a/packages/nextjs/test/config/util.test.ts +++ b/packages/nextjs/test/config/util.test.ts @@ -1,169 +1,98 @@ -import * as fs from 'fs'; -import { afterEach, describe, expect, it, vi } from 'vitest'; +import { describe, expect, it } from 'vitest'; import * as util from '../../src/config/util'; -// Mock fs to control what getNextjsVersion reads -vi.mock('fs'); - describe('util', () => { describe('supportsProductionCompileHook', () => { - afterEach(() => { - vi.restoreAllMocks(); - }); - describe('supported versions', () => { it('returns true for Next.js 15.4.1', () => { - const mockReadFileSync = fs.readFileSync as any; - mockReadFileSync.mockReturnValue(JSON.stringify({ version: '15.4.1' })); - - const result = util.supportsProductionCompileHook(); + const result = util.supportsProductionCompileHook('15.4.1'); expect(result).toBe(true); }); it('returns true for Next.js 15.4.2', () => { - const mockReadFileSync = fs.readFileSync as any; - mockReadFileSync.mockReturnValue(JSON.stringify({ version: '15.4.2' })); - - expect(util.supportsProductionCompileHook()).toBe(true); + expect(util.supportsProductionCompileHook('15.4.2')).toBe(true); }); it('returns true for Next.js 15.5.0', () => { - const mockReadFileSync = fs.readFileSync as any; - mockReadFileSync.mockReturnValue(JSON.stringify({ version: '15.5.0' })); - - expect(util.supportsProductionCompileHook()).toBe(true); + expect(util.supportsProductionCompileHook('15.5.0')).toBe(true); }); it('returns true for Next.js 16.0.0', () => { - const mockReadFileSync = fs.readFileSync as any; - mockReadFileSync.mockReturnValue(JSON.stringify({ version: '16.0.0' })); - - expect(util.supportsProductionCompileHook()).toBe(true); + expect(util.supportsProductionCompileHook('16.0.0')).toBe(true); }); it('returns true for Next.js 17.0.0', () => { - const mockReadFileSync = fs.readFileSync as any; - mockReadFileSync.mockReturnValue(JSON.stringify({ version: '17.0.0' })); - - expect(util.supportsProductionCompileHook()).toBe(true); + expect(util.supportsProductionCompileHook('17.0.0')).toBe(true); }); it('returns true for supported canary versions', () => { - const mockReadFileSync = fs.readFileSync as any; - mockReadFileSync.mockReturnValue(JSON.stringify({ version: '15.4.1-canary.42' })); - - expect(util.supportsProductionCompileHook()).toBe(true); + expect(util.supportsProductionCompileHook('15.4.1-canary.42')).toBe(true); }); it('returns true for supported rc versions', () => { - const mockReadFileSync = fs.readFileSync as any; - mockReadFileSync.mockReturnValue(JSON.stringify({ version: '15.4.1-rc.1' })); - - expect(util.supportsProductionCompileHook()).toBe(true); + expect(util.supportsProductionCompileHook('15.4.1-rc.1')).toBe(true); }); }); describe('unsupported versions', () => { it('returns false for Next.js 15.4.0', () => { - const mockReadFileSync = fs.readFileSync as any; - mockReadFileSync.mockReturnValue(JSON.stringify({ version: '15.4.0' })); - - expect(util.supportsProductionCompileHook()).toBe(false); + expect(util.supportsProductionCompileHook('15.4.0')).toBe(false); }); it('returns false for Next.js 15.3.9', () => { - const mockReadFileSync = fs.readFileSync as any; - mockReadFileSync.mockReturnValue(JSON.stringify({ version: '15.3.9' })); - - expect(util.supportsProductionCompileHook()).toBe(false); + expect(util.supportsProductionCompileHook('15.3.9')).toBe(false); }); it('returns false for Next.js 15.0.0', () => { - const mockReadFileSync = fs.readFileSync as any; - mockReadFileSync.mockReturnValue(JSON.stringify({ version: '15.0.0' })); - - expect(util.supportsProductionCompileHook()).toBe(false); + expect(util.supportsProductionCompileHook('15.0.0')).toBe(false); }); it('returns false for Next.js 14.2.0', () => { - const mockReadFileSync = fs.readFileSync as any; - mockReadFileSync.mockReturnValue(JSON.stringify({ version: '14.2.0' })); - - expect(util.supportsProductionCompileHook()).toBe(false); + expect(util.supportsProductionCompileHook('14.2.0')).toBe(false); }); it('returns false for unsupported canary versions', () => { - const mockReadFileSync = fs.readFileSync as any; - mockReadFileSync.mockReturnValue(JSON.stringify({ version: '15.4.0-canary.42' })); - - expect(util.supportsProductionCompileHook()).toBe(false); + expect(util.supportsProductionCompileHook('15.4.0-canary.42')).toBe(false); }); }); describe('edge cases', () => { it('returns false for invalid version strings', () => { - const mockReadFileSync = fs.readFileSync as any; - mockReadFileSync.mockReturnValue(JSON.stringify({ version: 'invalid.version' })); - - expect(util.supportsProductionCompileHook()).toBe(false); + expect(util.supportsProductionCompileHook('invalid.version')).toBe(false); }); it('handles versions with build metadata', () => { - const mockReadFileSync = fs.readFileSync as any; - mockReadFileSync.mockReturnValue(JSON.stringify({ version: '15.4.1+build.123' })); - - expect(util.supportsProductionCompileHook()).toBe(true); + expect(util.supportsProductionCompileHook('15.4.1+build.123')).toBe(true); }); it('handles versions with pre-release identifiers', () => { - const mockReadFileSync = fs.readFileSync as any; - mockReadFileSync.mockReturnValue(JSON.stringify({ version: '15.4.1-alpha.1' })); - - expect(util.supportsProductionCompileHook()).toBe(true); + expect(util.supportsProductionCompileHook('15.4.1-alpha.1')).toBe(true); }); it('returns false for versions missing patch number', () => { - const mockReadFileSync = fs.readFileSync as any; - mockReadFileSync.mockReturnValue(JSON.stringify({ version: '15.4' })); - - expect(util.supportsProductionCompileHook()).toBe(false); + expect(util.supportsProductionCompileHook('15.4')).toBe(false); }); it('returns false for versions missing minor number', () => { - const mockReadFileSync = fs.readFileSync as any; - mockReadFileSync.mockReturnValue(JSON.stringify({ version: '15' })); - - expect(util.supportsProductionCompileHook()).toBe(false); + expect(util.supportsProductionCompileHook('15')).toBe(false); }); }); describe('version boundary tests', () => { it('returns false for 15.4.0 (just below threshold)', () => { - const mockReadFileSync = fs.readFileSync as any; - mockReadFileSync.mockReturnValue(JSON.stringify({ version: '15.4.0' })); - - expect(util.supportsProductionCompileHook()).toBe(false); + expect(util.supportsProductionCompileHook('15.4.0')).toBe(false); }); it('returns true for 15.4.1 (exact threshold)', () => { - const mockReadFileSync = fs.readFileSync as any; - mockReadFileSync.mockReturnValue(JSON.stringify({ version: '15.4.1' })); - - expect(util.supportsProductionCompileHook()).toBe(true); + expect(util.supportsProductionCompileHook('15.4.1')).toBe(true); }); it('returns true for 15.4.2 (just above threshold)', () => { - const mockReadFileSync = fs.readFileSync as any; - mockReadFileSync.mockReturnValue(JSON.stringify({ version: '15.4.2' })); - - expect(util.supportsProductionCompileHook()).toBe(true); + expect(util.supportsProductionCompileHook('15.4.2')).toBe(true); }); it('returns false for 15.3.999 (high patch but wrong minor)', () => { - const mockReadFileSync = fs.readFileSync as any; - mockReadFileSync.mockReturnValue(JSON.stringify({ version: '15.3.999' })); - - expect(util.supportsProductionCompileHook()).toBe(false); + expect(util.supportsProductionCompileHook('15.3.999')).toBe(false); }); }); }); diff --git a/packages/nextjs/test/config/withSentryConfig.test.ts b/packages/nextjs/test/config/withSentryConfig.test.ts index 0697fc56b9e4..8fc0a81e3dd4 100644 --- a/packages/nextjs/test/config/withSentryConfig.test.ts +++ b/packages/nextjs/test/config/withSentryConfig.test.ts @@ -183,14 +183,14 @@ describe('withSentryConfig', () => { expect(finalConfigWithoutTurbopack.webpack).toBe(originalWebpackFunction); process.env.TURBOPACK = '1'; - vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.3.0'); + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.1'); const finalConfigWithTurbopack = materializeFinalNextConfig(configWithWebpack, undefined, sentryOptions); expect(finalConfigWithTurbopack.webpack).toBe(originalWebpackFunction); }); it('preserves original webpack config when Turbopack is enabled (ignores disableSentryWebpackConfig flag)', () => { process.env.TURBOPACK = '1'; - vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.3.0'); + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.1'); const originalWebpackFunction = vi.fn(); const configWithWebpack = { @@ -216,7 +216,7 @@ describe('withSentryConfig', () => { it('preserves original webpack config when Turbopack is enabled and disableSentryWebpackConfig is true', () => { process.env.TURBOPACK = '1'; - vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.3.0'); + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.1'); const sentryOptions = { disableSentryWebpackConfig: true, @@ -235,7 +235,7 @@ describe('withSentryConfig', () => { it('preserves undefined webpack when Turbopack is enabled, disableSentryWebpackConfig is true, and no original webpack config exists', () => { process.env.TURBOPACK = '1'; - vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.3.0'); + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.1'); const sentryOptions = { disableSentryWebpackConfig: true, @@ -253,7 +253,7 @@ describe('withSentryConfig', () => { it('includes turbopack config when Turbopack is supported and enabled', () => { process.env.TURBOPACK = '1'; - vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.3.0'); + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.1'); const finalConfig = materializeFinalNextConfig(exportedNextConfig); @@ -279,7 +279,7 @@ describe('withSentryConfig', () => { it('enables productionBrowserSourceMaps for supported turbopack builds when sourcemaps are not disabled', () => { process.env.TURBOPACK = '1'; - vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.3.0'); + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.1'); const finalConfig = materializeFinalNextConfig(exportedNextConfig); @@ -288,7 +288,7 @@ describe('withSentryConfig', () => { it('does not enable productionBrowserSourceMaps when sourcemaps are disabled', () => { process.env.TURBOPACK = '1'; - vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.3.0'); + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.1'); const cleanConfig = { ...exportedNextConfig }; delete cleanConfig.productionBrowserSourceMaps; @@ -329,7 +329,7 @@ describe('withSentryConfig', () => { it('preserves user-configured productionBrowserSourceMaps setting', () => { process.env.TURBOPACK = '1'; - vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.3.0'); + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.1'); const configWithSourceMaps = { ...exportedNextConfig, @@ -343,7 +343,7 @@ describe('withSentryConfig', () => { it('preserves user-configured productionBrowserSourceMaps: true setting', () => { process.env.TURBOPACK = '1'; - vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.3.0'); + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.1'); const configWithSourceMaps = { ...exportedNextConfig, @@ -363,7 +363,7 @@ describe('withSentryConfig', () => { it('automatically enables deleteSourcemapsAfterUpload for turbopack builds when not explicitly set', () => { process.env.TURBOPACK = '1'; - vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.3.0'); + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.1'); // Use a clean config without productionBrowserSourceMaps to ensure it gets auto-enabled const cleanConfig = { ...exportedNextConfig }; @@ -382,7 +382,7 @@ describe('withSentryConfig', () => { it('preserves explicitly configured deleteSourcemapsAfterUpload setting', () => { process.env.TURBOPACK = '1'; - vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.3.0'); + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.1'); const sentryOptions = { sourcemaps: { @@ -397,7 +397,7 @@ describe('withSentryConfig', () => { it('does not modify deleteSourcemapsAfterUpload when sourcemaps are disabled', () => { process.env.TURBOPACK = '1'; - vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.3.0'); + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.1'); const sentryOptions = { sourcemaps: { @@ -412,7 +412,7 @@ describe('withSentryConfig', () => { it('does not enable deleteSourcemapsAfterUpload when user pre-configured productionBrowserSourceMaps: true', () => { process.env.TURBOPACK = '1'; - vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.3.0'); + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.1'); const configWithSourceMapsPreEnabled = { ...exportedNextConfig, @@ -431,7 +431,7 @@ describe('withSentryConfig', () => { it('does not enable sourcemaps or deletion when user explicitly sets productionBrowserSourceMaps: false', () => { process.env.TURBOPACK = '1'; - vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.3.0'); + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.1'); const configWithSourceMapsDisabled = { ...exportedNextConfig, @@ -451,7 +451,7 @@ describe('withSentryConfig', () => { it('logs correct message when enabling sourcemaps for turbopack', () => { process.env.TURBOPACK = '1'; - vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.3.0'); + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.1'); const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); const cleanConfig = { ...exportedNextConfig }; @@ -472,7 +472,7 @@ describe('withSentryConfig', () => { it('warns about automatic sourcemap deletion for turbopack builds', () => { process.env.TURBOPACK = '1'; - vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.3.0'); + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.1'); const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); // Use a clean config without productionBrowserSourceMaps to trigger automatic enablement @@ -494,22 +494,25 @@ describe('withSentryConfig', () => { }); describe('version compatibility', () => { - it('enables sourcemaps for Next.js 15.3.0', () => { + it('enables sourcemaps for Next.js 15.4.1', () => { process.env.TURBOPACK = '1'; - vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.3.0'); + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.1'); const finalConfig = materializeFinalNextConfig(exportedNextConfig); expect(finalConfig.productionBrowserSourceMaps).toBe(true); }); - it('enables sourcemaps for Next.js 15.4.0', () => { + it('does not enable sourcemaps for Next.js 15.4.0', () => { process.env.TURBOPACK = '1'; vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.0'); - const finalConfig = materializeFinalNextConfig(exportedNextConfig); + const cleanConfig = { ...exportedNextConfig }; + delete cleanConfig.productionBrowserSourceMaps; - expect(finalConfig.productionBrowserSourceMaps).toBe(true); + const finalConfig = materializeFinalNextConfig(cleanConfig); + + expect(finalConfig.productionBrowserSourceMaps).toBeUndefined(); }); it('enables sourcemaps for Next.js 16.0.0', () => { @@ -535,7 +538,7 @@ describe('withSentryConfig', () => { it('enables sourcemaps for supported canary versions', () => { process.env.TURBOPACK = '1'; - vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.3.0-canary.28'); + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.1-canary.1'); const finalConfig = materializeFinalNextConfig(exportedNextConfig); @@ -544,7 +547,7 @@ describe('withSentryConfig', () => { it('does not enable sourcemaps for unsupported canary versions', () => { process.env.TURBOPACK = '1'; - vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.3.0-canary.27'); + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.0-canary.999'); const cleanConfig = { ...exportedNextConfig }; delete cleanConfig.productionBrowserSourceMaps; @@ -558,7 +561,7 @@ describe('withSentryConfig', () => { describe('edge cases', () => { it('handles undefined sourcemaps option', () => { process.env.TURBOPACK = '1'; - vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.3.0'); + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.1'); const sentryOptions = {}; // no sourcemaps property @@ -569,7 +572,7 @@ describe('withSentryConfig', () => { it('handles empty sourcemaps object', () => { process.env.TURBOPACK = '1'; - vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.3.0'); + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.1'); // Use a clean config without productionBrowserSourceMaps to trigger automatic enablement const cleanConfig = { ...exportedNextConfig }; @@ -586,7 +589,7 @@ describe('withSentryConfig', () => { it('works when TURBOPACK env var is truthy string', () => { process.env.TURBOPACK = 'true'; - vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.3.0'); + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.1'); const finalConfig = materializeFinalNextConfig(exportedNextConfig); @@ -595,7 +598,7 @@ describe('withSentryConfig', () => { it('does not enable sourcemaps when TURBOPACK env var is falsy', () => { process.env.TURBOPACK = ''; - vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.3.0'); + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.1'); const cleanConfig = { ...exportedNextConfig }; delete cleanConfig.productionBrowserSourceMaps; @@ -607,7 +610,7 @@ describe('withSentryConfig', () => { it('works correctly with tunnel route configuration', () => { process.env.TURBOPACK = '1'; - vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.3.0'); + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.1'); // Use a clean config without productionBrowserSourceMaps to trigger automatic enablement const cleanConfig = { ...exportedNextConfig }; @@ -627,7 +630,7 @@ describe('withSentryConfig', () => { it('works correctly with custom release configuration', () => { process.env.TURBOPACK = '1'; - vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.3.0'); + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.1'); // Clear environment variable to test custom release name const originalSentryRelease = process.env.SENTRY_RELEASE; @@ -658,7 +661,7 @@ describe('withSentryConfig', () => { it('does not interfere with other Next.js configuration options', () => { process.env.TURBOPACK = '1'; - vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.3.0'); + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.1'); const configWithOtherOptions = { ...exportedNextConfig, @@ -677,7 +680,7 @@ describe('withSentryConfig', () => { it('works correctly when turbopack config already exists', () => { process.env.TURBOPACK = '1'; - vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.3.0'); + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.1'); const configWithTurbopack = { ...exportedNextConfig, @@ -968,4 +971,206 @@ describe('withSentryConfig', () => { expect(finalConfig.compiler?.runAfterProductionCompile).toBeInstanceOf(Function); }); }); + + describe('turbopack version compatibility warnings', () => { + const originalTurbopack = process.env.TURBOPACK; + const originalNodeEnv = process.env.NODE_ENV; + + afterEach(() => { + vi.restoreAllMocks(); + process.env.TURBOPACK = originalTurbopack; + // @ts-expect-error - NODE_ENV is read-only in types but we need to restore it in tests + process.env.NODE_ENV = originalNodeEnv; + }); + + it('warns in development mode when Turbopack is enabled with unsupported Next.js version', () => { + process.env.TURBOPACK = '1'; + // @ts-expect-error - NODE_ENV is read-only in types but we need to set it for testing + process.env.NODE_ENV = 'development'; + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.0'); + vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(false); + const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + materializeFinalNextConfig(exportedNextConfig); + + expect(consoleWarnSpy).toHaveBeenCalledWith( + '[@sentry/nextjs] WARNING: You are using the Sentry SDK with Turbopack (`next dev --turbopack`). The Sentry SDK is compatible with Turbopack on Next.js version 15.4.1 or later. You are currently on 15.4.0. Please upgrade to a newer Next.js version to use the Sentry SDK with Turbopack.', + ); + + consoleWarnSpy.mockRestore(); + }); + + it('warns in production mode when Turbopack is enabled with unsupported Next.js version', () => { + process.env.TURBOPACK = '1'; + // @ts-expect-error - NODE_ENV is read-only in types but we need to set it for testing + process.env.NODE_ENV = 'production'; + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.3.9'); + vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(false); + const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + materializeFinalNextConfig(exportedNextConfig); + + expect(consoleWarnSpy).toHaveBeenCalledWith( + '[@sentry/nextjs] WARNING: You are using the Sentry SDK with Turbopack (`next build --turbopack`). The Sentry SDK is compatible with Turbopack on Next.js version 15.4.1 or later. You are currently on 15.3.9. Please upgrade to a newer Next.js version to use the Sentry SDK with Turbopack.', + ); + + consoleWarnSpy.mockRestore(); + }); + + it('does not warn when Turbopack is enabled with supported Next.js version', () => { + process.env.TURBOPACK = '1'; + // @ts-expect-error - NODE_ENV is read-only in types but we need to set it for testing + process.env.NODE_ENV = 'development'; + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.1'); + vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(true); + const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + materializeFinalNextConfig(exportedNextConfig); + + expect(consoleWarnSpy).not.toHaveBeenCalledWith( + expect.stringContaining('WARNING: You are using the Sentry SDK with Turbopack'), + ); + + consoleWarnSpy.mockRestore(); + }); + + it('does not warn when Turbopack is enabled with higher supported Next.js version', () => { + process.env.TURBOPACK = '1'; + // @ts-expect-error - NODE_ENV is read-only in types but we need to set it for testing + process.env.NODE_ENV = 'production'; + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.5.0'); + vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(true); + const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + materializeFinalNextConfig(exportedNextConfig); + + expect(consoleWarnSpy).not.toHaveBeenCalledWith( + expect.stringContaining('WARNING: You are using the Sentry SDK with Turbopack'), + ); + + consoleWarnSpy.mockRestore(); + }); + + it('does not warn when Turbopack is enabled with Next.js 16+', () => { + process.env.TURBOPACK = '1'; + // @ts-expect-error - NODE_ENV is read-only in types but we need to set it for testing + process.env.NODE_ENV = 'development'; + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('16.0.0'); + vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(true); + const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + materializeFinalNextConfig(exportedNextConfig); + + expect(consoleWarnSpy).not.toHaveBeenCalledWith( + expect.stringContaining('WARNING: You are using the Sentry SDK with Turbopack'), + ); + + consoleWarnSpy.mockRestore(); + }); + + it('does not warn when Turbopack is not enabled', () => { + delete process.env.TURBOPACK; + // @ts-expect-error - NODE_ENV is read-only in types but we need to set it for testing + process.env.NODE_ENV = 'development'; + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.1'); + vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(false); + const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + materializeFinalNextConfig(exportedNextConfig); + + expect(consoleWarnSpy).not.toHaveBeenCalledWith( + expect.stringContaining('WARNING: You are using the Sentry SDK with Turbopack'), + ); + + consoleWarnSpy.mockRestore(); + }); + + it('warns even when Next.js version cannot be determined if Turbopack is unsupported', () => { + process.env.TURBOPACK = '1'; + // @ts-expect-error - NODE_ENV is read-only in types but we need to set it for testing + process.env.NODE_ENV = 'development'; + vi.spyOn(util, 'getNextjsVersion').mockReturnValue(undefined); + vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(false); + const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + materializeFinalNextConfig(exportedNextConfig); + + // Warning will still show because supportsProductionCompileHook returns false + expect(consoleWarnSpy).toHaveBeenCalledWith( + expect.stringContaining('WARNING: You are using the Sentry SDK with Turbopack'), + ); + expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining('You are currently on undefined')); + + consoleWarnSpy.mockRestore(); + }); + + it('warns with correct version in message for edge case versions', () => { + process.env.TURBOPACK = '1'; + // @ts-expect-error - NODE_ENV is read-only in types but we need to set it for testing + process.env.NODE_ENV = 'development'; + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.0-canary.15'); + vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(false); + const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + materializeFinalNextConfig(exportedNextConfig); + + expect(consoleWarnSpy).toHaveBeenCalledWith( + '[@sentry/nextjs] WARNING: You are using the Sentry SDK with Turbopack (`next dev --turbopack`). The Sentry SDK is compatible with Turbopack on Next.js version 15.4.1 or later. You are currently on 15.4.0-canary.15. Please upgrade to a newer Next.js version to use the Sentry SDK with Turbopack.', + ); + + consoleWarnSpy.mockRestore(); + }); + + it('does not warn in other environments besides development and production', () => { + process.env.TURBOPACK = '1'; + // @ts-expect-error - NODE_ENV is read-only in types but we need to set it for testing + process.env.NODE_ENV = 'test'; + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.1'); + vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(false); + const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + materializeFinalNextConfig(exportedNextConfig); + + expect(consoleWarnSpy).not.toHaveBeenCalledWith( + expect.stringContaining('WARNING: You are using the Sentry SDK with Turbopack'), + ); + + consoleWarnSpy.mockRestore(); + }); + + it('handles falsy TURBOPACK environment variable', () => { + process.env.TURBOPACK = ''; + // @ts-expect-error - NODE_ENV is read-only in types but we need to set it for testing + process.env.NODE_ENV = 'development'; + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.1'); + vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(false); + const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + materializeFinalNextConfig(exportedNextConfig); + + expect(consoleWarnSpy).not.toHaveBeenCalledWith( + expect.stringContaining('WARNING: You are using the Sentry SDK with Turbopack'), + ); + + consoleWarnSpy.mockRestore(); + }); + + it('warns when TURBOPACK=0 (truthy string) with unsupported version', () => { + process.env.TURBOPACK = '0'; + // @ts-expect-error - NODE_ENV is read-only in types but we need to set it for testing + process.env.NODE_ENV = 'development'; + vi.spyOn(util, 'getNextjsVersion').mockReturnValue('15.4.1'); + vi.spyOn(util, 'supportsProductionCompileHook').mockReturnValue(false); + const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + materializeFinalNextConfig(exportedNextConfig); + + // Note: '0' is truthy in JavaScript, so this will trigger the warning + expect(consoleWarnSpy).toHaveBeenCalledWith( + expect.stringContaining('WARNING: You are using the Sentry SDK with Turbopack'), + ); + + consoleWarnSpy.mockRestore(); + }); + }); });