From d89a5fffddc9e5cade136efcd7dc10a0c4765612 Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Mon, 23 Feb 2026 12:39:22 +0100 Subject: [PATCH 1/4] clean up tests --- packages/nuxt/test/vite/sourceMaps.test.ts | 36 +++++++++++----------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/nuxt/test/vite/sourceMaps.test.ts b/packages/nuxt/test/vite/sourceMaps.test.ts index e4ae498639b0..0bbd0cd8343e 100644 --- a/packages/nuxt/test/vite/sourceMaps.test.ts +++ b/packages/nuxt/test/vite/sourceMaps.test.ts @@ -35,6 +35,7 @@ describe('getPluginOptions', () => { authToken: 'default-token', url: 'https://santry.io', telemetry: true, + debug: false, sourcemaps: expect.objectContaining({ rewriteSources: expect.any(Function), }), @@ -43,7 +44,6 @@ describe('getPluginOptions', () => { metaFramework: 'nuxt', }), }), - debug: false, }), ); }); @@ -57,6 +57,7 @@ describe('getPluginOptions', () => { expect(options).toEqual( expect.objectContaining({ telemetry: true, + debug: false, sourcemaps: expect.objectContaining({ rewriteSources: expect.any(Function), }), @@ -65,7 +66,6 @@ describe('getPluginOptions', () => { metaFramework: 'nuxt', }), }), - debug: false, }), ); }); @@ -108,6 +108,14 @@ describe('getPluginOptions', () => { ); }); + it('normalizes source paths via rewriteSources', () => { + const options = getPluginOptions({} as SentryNuxtModuleOptions, undefined); + const rewrite = options.sourcemaps?.rewriteSources as ((s: string) => string) | undefined; + expect(rewrite).toBeTypeOf('function'); + expect(rewrite!('../../../foo/bar')).toBe('./foo/bar'); + expect(rewrite!('./local')).toBe('./local'); + }); + it('prioritizes new BuildTimeOptionsBase options over deprecated ones', () => { const options: SentryNuxtModuleOptions = { // New options @@ -268,27 +276,19 @@ describe('getPluginOptions', () => { name: 'both client and server fallback are true', clientFallback: true, serverFallback: true, - customOptions: {}, - expectedFilesToDelete: [ - '.*/**/public/**/*.map', - '.*/**/server/**/*.map', - '.*/**/output/**/*.map', - '.*/**/function/**/*.map', - ], + expected: ['.*/**/public/**/*.map', '.*/**/server/**/*.map', '.*/**/output/**/*.map', '.*/**/function/**/*.map'], }, { name: 'only client fallback is true', clientFallback: true, serverFallback: false, - customOptions: {}, - expectedFilesToDelete: ['.*/**/public/**/*.map'], + expected: ['.*/**/public/**/*.map'], }, { name: 'only server fallback is true', clientFallback: false, serverFallback: true, - customOptions: {}, - expectedFilesToDelete: ['.*/**/server/**/*.map', '.*/**/output/**/*.map', '.*/**/function/**/*.map'], + expected: ['.*/**/server/**/*.map', '.*/**/output/**/*.map', '.*/**/function/**/*.map'], }, { name: 'no fallback, but custom filesToDeleteAfterUpload is provided (deprecated)', @@ -299,7 +299,7 @@ describe('getPluginOptions', () => { sourcemaps: { filesToDeleteAfterUpload: ['deprecated/path/**/*.map'] }, }, }, - expectedFilesToDelete: ['deprecated/path/**/*.map'], + expected: ['deprecated/path/**/*.map'], }, { name: 'no fallback, but custom filesToDeleteAfterUpload is provided (new)', @@ -308,24 +308,24 @@ describe('getPluginOptions', () => { customOptions: { sourcemaps: { filesToDeleteAfterUpload: ['new-custom/path/**/*.map'] }, }, - expectedFilesToDelete: ['new-custom/path/**/*.map'], + expected: ['new-custom/path/**/*.map'], }, { name: 'no fallback, both source maps explicitly false and no custom filesToDeleteAfterUpload', clientFallback: false, serverFallback: false, customOptions: {}, - expectedFilesToDelete: undefined, + expected: undefined, }, ])( 'sets filesToDeleteAfterUpload correctly when $name', - ({ clientFallback, serverFallback, customOptions, expectedFilesToDelete }) => { + ({ clientFallback, serverFallback, customOptions = {}, expected }) => { const options = getPluginOptions(customOptions as SentryNuxtModuleOptions, { client: clientFallback, server: serverFallback, }); - expect(options?.sourcemaps?.filesToDeleteAfterUpload).toEqual(expectedFilesToDelete); + expect(options?.sourcemaps?.filesToDeleteAfterUpload).toEqual(expected); }, ); }); From b46199176e3754a39a271198414ab182c8ff180d Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Mon, 23 Feb 2026 12:45:05 +0100 Subject: [PATCH 2/4] add test: validateDifferentSourceMapSettings --- packages/nuxt/test/vite/sourceMaps.test.ts | 52 ++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/packages/nuxt/test/vite/sourceMaps.test.ts b/packages/nuxt/test/vite/sourceMaps.test.ts index 0bbd0cd8343e..e3d1347b3193 100644 --- a/packages/nuxt/test/vite/sourceMaps.test.ts +++ b/packages/nuxt/test/vite/sourceMaps.test.ts @@ -4,7 +4,9 @@ import type { SentryNuxtModuleOptions } from '../../src/common/types'; import type { SourceMapSetting } from '../../src/vite/sourceMaps'; import { changeNuxtSourceMapSettings, + extractNuxtSourceMapSetting, getPluginOptions, + validateDifferentSourceMapSettings, validateNitroSourceMapSettings, } from '../../src/vite/sourceMaps'; @@ -330,6 +332,56 @@ describe('getPluginOptions', () => { ); }); +describe('validateDifferentSourceMapSettings', () => { + const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + + afterEach(() => { + consoleWarnSpy.mockClear(); + }); + + it('does not warn when both settings match', () => { + validateDifferentSourceMapSettings({ + nuxtSettingKey: 'sourcemap.server', + nuxtSettingValue: true, + otherSettingKey: 'nitro.sourceMap', + otherSettingValue: true, + }); + expect(consoleWarnSpy).not.toHaveBeenCalled(); + }); + + it('warns when settings conflict', () => { + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + validateDifferentSourceMapSettings({ + nuxtSettingKey: 'sourcemap.server', + nuxtSettingValue: true, + otherSettingKey: 'nitro.sourceMap', + otherSettingValue: false, + }); + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('sourcemap.server')); + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('nitro.sourceMap')); + warnSpy.mockRestore(); + }); +}); + +describe('extractNuxtSourceMapSetting', () => { + it.each<{ + runtime: 'client' | 'server' | undefined; + sourcemap: SourceMapSetting | { client?: SourceMapSetting; server?: SourceMapSetting }; + expected: SourceMapSetting | undefined; + }>([ + { runtime: undefined, sourcemap: true, expected: undefined }, + { runtime: 'client', sourcemap: true, expected: true }, + { runtime: 'server', sourcemap: 'hidden', expected: 'hidden' }, + { runtime: 'client', sourcemap: { client: true, server: false }, expected: true }, + { runtime: 'server', sourcemap: { client: true, server: 'hidden' }, expected: 'hidden' }, + ])('returns correct value for runtime=$runtime and sourcemap type', ({ runtime, sourcemap, expected }) => { + const nuxt = { options: { sourcemap } }; + expect(extractNuxtSourceMapSetting(nuxt as Parameters[0], runtime)).toBe( + expected, + ); + }); +}); + describe('validate sourcemap settings', () => { const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); From a6bc448e7450283600adc48c77299b2c41f72c83 Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Mon, 23 Feb 2026 13:00:03 +0100 Subject: [PATCH 3/4] ref(nuxt): Use `addVitePlugin` instead of deprecated `vite:extendConfig` --- packages/nuxt/src/module.ts | 3 +- packages/nuxt/src/vite/sentryVitePlugin.ts | 57 ++++ packages/nuxt/src/vite/sourceMaps.ts | 61 ++-- .../test/vite/sourceMaps-nuxtHooks.test.ts | 267 +++++++++++++----- 4 files changed, 286 insertions(+), 102 deletions(-) create mode 100644 packages/nuxt/src/vite/sentryVitePlugin.ts diff --git a/packages/nuxt/src/module.ts b/packages/nuxt/src/module.ts index f2968d70482d..55656e103738 100644 --- a/packages/nuxt/src/module.ts +++ b/packages/nuxt/src/module.ts @@ -3,6 +3,7 @@ import { addPluginTemplate, addServerPlugin, addTemplate, + addVitePlugin, createResolver, defineNuxtModule, } from '@nuxt/kit'; @@ -88,7 +89,7 @@ export default defineNuxtModule({ } if (clientConfigFile || serverConfigFile) { - setupSourceMaps(moduleOptions, nuxt); + setupSourceMaps(moduleOptions, nuxt, addVitePlugin); } addOTelCommonJSImportAlias(nuxt); diff --git a/packages/nuxt/src/vite/sentryVitePlugin.ts b/packages/nuxt/src/vite/sentryVitePlugin.ts new file mode 100644 index 000000000000..78c11110bf72 --- /dev/null +++ b/packages/nuxt/src/vite/sentryVitePlugin.ts @@ -0,0 +1,57 @@ +import type { Nuxt } from '@nuxt/schema'; +import { sentryVitePlugin } from '@sentry/vite-plugin'; +import type { ConfigEnv, Plugin, UserConfig } from 'vite'; +import type { SentryNuxtModuleOptions } from '../common/types'; +import { extractNuxtSourceMapSetting, getPluginOptions, validateDifferentSourceMapSettings } from './sourceMaps'; + +/** + * Creates a Vite plugin that adds the Sentry Vite plugin and validates source map settings. + */ +export function createSentryViteConfigPlugin(options: { + nuxt: Nuxt; + moduleOptions: SentryNuxtModuleOptions; + sourceMapsEnabled: boolean; + shouldDeleteFilesFallback: { client: boolean; server: boolean }; +}): Plugin { + const { nuxt, moduleOptions, sourceMapsEnabled, shouldDeleteFilesFallback } = options; + const isDebug = moduleOptions.debug; + + return { + name: 'sentry-nuxt-vite-config', + config(viteConfig: UserConfig, env: ConfigEnv) { + // Only run in production builds + if (!sourceMapsEnabled || env.mode === 'development' || nuxt.options?._prepare) { + return; + } + + // Detect runtime from Vite config + // In Nuxt, SSR builds have build.ssr: true, client builds don't + const runtime = viteConfig.build?.ssr ? 'server' : 'client'; + + const nuxtSourceMapSetting = extractNuxtSourceMapSetting(nuxt, runtime); + + // Initialize build config if needed + viteConfig.build = viteConfig.build || {}; + const viteSourceMap = viteConfig.build.sourcemap; + + // Vite source map options are the same as the Nuxt source map config options (unless overwritten) + validateDifferentSourceMapSettings({ + nuxtSettingKey: `sourcemap.${runtime}`, + nuxtSettingValue: nuxtSourceMapSetting, + otherSettingKey: 'viteConfig.build.sourcemap', + otherSettingValue: viteSourceMap, + }); + + if (isDebug) { + // eslint-disable-next-line no-console + console.log(`[Sentry] Adding Sentry Vite plugin to the ${runtime} runtime.`); + } + + // Add Sentry plugin by mutating the config + // Vite plugin is added on the client and server side (plugin runs for both builds) + // Nuxt client source map is 'false' by default. Warning about this will be shown already in an earlier step, and it's also documented that `nuxt.sourcemap.client` needs to be enabled. + viteConfig.plugins = viteConfig.plugins || []; + viteConfig.plugins.push(sentryVitePlugin(getPluginOptions(moduleOptions, shouldDeleteFilesFallback))); + }, + }; +} diff --git a/packages/nuxt/src/vite/sourceMaps.ts b/packages/nuxt/src/vite/sourceMaps.ts index 771be8d3d532..b3478a16f8c6 100644 --- a/packages/nuxt/src/vite/sourceMaps.ts +++ b/packages/nuxt/src/vite/sourceMaps.ts @@ -1,8 +1,10 @@ import type { Nuxt } from '@nuxt/schema'; import { sentryRollupPlugin, type SentryRollupPluginOptions } from '@sentry/rollup-plugin'; -import { sentryVitePlugin, type SentryVitePluginOptions } from '@sentry/vite-plugin'; +import type { SentryVitePluginOptions } from '@sentry/vite-plugin'; import type { NitroConfig } from 'nitropack'; +import type { Plugin } from 'vite'; import type { SentryNuxtModuleOptions } from '../common/types'; +import { createSentryViteConfigPlugin } from './sentryVitePlugin'; /** * Whether the user enabled (true, 'hidden', 'inline') or disabled (false) source maps @@ -15,7 +17,11 @@ export type SourceMapSetting = boolean | 'hidden' | 'inline'; /** * Setup source maps for Sentry inside the Nuxt module during build time (in Vite for Nuxt and Rollup for Nitro). */ -export function setupSourceMaps(moduleOptions: SentryNuxtModuleOptions, nuxt: Nuxt): void { +export function setupSourceMaps( + moduleOptions: SentryNuxtModuleOptions, + nuxt: Nuxt, + addVitePlugin: (plugin: Plugin | (() => Plugin), options?: { dev?: boolean; build?: boolean }) => void, +): void { // TODO(v11): remove deprecated options (also from SentryNuxtModuleOptions type) const isDebug = moduleOptions.debug; @@ -76,39 +82,16 @@ export function setupSourceMaps(moduleOptions: SentryNuxtModuleOptions, nuxt: Nu } }); - nuxt.hook('vite:extendConfig', async (viteConfig, env) => { - if (sourceMapsEnabled && viteConfig.mode !== 'development' && !nuxt.options?._prepare) { - const runtime = env.isServer ? 'server' : env.isClient ? 'client' : undefined; - const nuxtSourceMapSetting = extractNuxtSourceMapSetting(nuxt, runtime); - - viteConfig.build = viteConfig.build || {}; - const viteSourceMap = viteConfig.build.sourcemap; - - // Vite source map options are the same as the Nuxt source map config options (unless overwritten) - validateDifferentSourceMapSettings({ - nuxtSettingKey: `sourcemap.${runtime}`, - nuxtSettingValue: nuxtSourceMapSetting, - otherSettingKey: 'viteConfig.build.sourcemap', - otherSettingValue: viteSourceMap, - }); - - if (isDebug) { - if (!runtime) { - // eslint-disable-next-line no-console - console.log("[Sentry] Cannot detect runtime (client/server) inside hook 'vite:extendConfig'."); - } else { - // eslint-disable-next-line no-console - console.log(`[Sentry] Adding Sentry Vite plugin to the ${runtime} runtime.`); - } - } - - // Add Sentry plugin - // Vite plugin is added on the client and server side (hook runs twice) - // Nuxt client source map is 'false' by default. Warning about this will be shown already in an earlier step, and it's also documented that `nuxt.sourcemap.client` needs to be enabled. - viteConfig.plugins = viteConfig.plugins || []; - viteConfig.plugins.push(sentryVitePlugin(getPluginOptions(moduleOptions, shouldDeleteFilesFallback))); - } - }); + addVitePlugin( + createSentryViteConfigPlugin({ + nuxt, + moduleOptions, + sourceMapsEnabled, + shouldDeleteFilesFallback, + }), + // Only add source map plugin during build + { dev: false, build: true }, + ); nuxt.hook('nitro:config', (nitroConfig: NitroConfig) => { if (sourceMapsEnabled && !nitroConfig.dev && !nuxt.options?._prepare) { @@ -379,7 +362,13 @@ export function validateNitroSourceMapSettings( } } -function validateDifferentSourceMapSettings({ +/** + * Validates that source map settings are consistent between Nuxt and Vite/Nitro configurations. + * Logs a warning if conflicting settings are detected. + * + * @internal Only exported for testing. + */ +export function validateDifferentSourceMapSettings({ nuxtSettingKey, nuxtSettingValue, otherSettingKey, diff --git a/packages/nuxt/test/vite/sourceMaps-nuxtHooks.test.ts b/packages/nuxt/test/vite/sourceMaps-nuxtHooks.test.ts index 230c92b812a7..b638b6155c7a 100644 --- a/packages/nuxt/test/vite/sourceMaps-nuxtHooks.test.ts +++ b/packages/nuxt/test/vite/sourceMaps-nuxtHooks.test.ts @@ -1,7 +1,50 @@ import type { Nuxt } from '@nuxt/schema'; +import type { Plugin, UserConfig } from 'vite'; import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; import type { SourceMapSetting } from '../../src/vite/sourceMaps'; +function createMockAddVitePlugin() { + let capturedPlugin: Plugin | null = null; + + const mockAddVitePlugin = vi.fn((plugin: Plugin | (() => Plugin)) => { + capturedPlugin = typeof plugin === 'function' ? plugin() : plugin; + }); + + return { + mockAddVitePlugin, + getCapturedPlugin: () => capturedPlugin, + }; +} + +type HookCallback = (...args: unknown[]) => void | Promise; + +function createMockNuxt(options: { + _prepare?: boolean; + dev?: boolean; + sourcemap?: SourceMapSetting | { server?: SourceMapSetting; client?: SourceMapSetting }; +}) { + const hooks: Record = {}; + + return { + options: { + _prepare: options._prepare ?? false, + dev: options.dev ?? false, + sourcemap: options.sourcemap ?? { server: undefined, client: undefined }, + }, + hook: (name: string, callback: HookCallback) => { + hooks[name] = hooks[name] || []; + hooks[name].push(callback); + }, + // Helper to trigger hooks in tests + triggerHook: async (name: string, ...args: unknown[]) => { + const callbacks = hooks[name] || []; + for (const callback of callbacks) { + await callback(...args); + } + }, + }; +} + describe('setupSourceMaps hooks', () => { const mockSentryVitePlugin = vi.fn(() => ({ name: 'sentry-vite-plugin' })); const mockSentryRollupPlugin = vi.fn(() => ({ name: 'sentry-rollup-plugin' })); @@ -32,93 +75,187 @@ describe('setupSourceMaps hooks', () => { mockSentryRollupPlugin.mockClear(); }); - type HookCallback = (...args: unknown[]) => void | Promise; + describe('vite plugin registration', () => { + it('registers a vite plugin after modules:done hook', async () => { + const { setupSourceMaps } = await import('../../src/vite/sourceMaps'); + const mockNuxt = createMockNuxt({ _prepare: false, dev: false }); + const { mockAddVitePlugin, getCapturedPlugin } = createMockAddVitePlugin(); - function createMockNuxt(options: { - _prepare?: boolean; - dev?: boolean; - sourcemap?: SourceMapSetting | { server?: SourceMapSetting; client?: SourceMapSetting }; - }) { - const hooks: Record = {}; + setupSourceMaps({ debug: true }, mockNuxt as unknown as Nuxt, mockAddVitePlugin); + await mockNuxt.triggerHook('modules:done'); - return { - options: { - _prepare: options._prepare ?? false, - dev: options.dev ?? false, - sourcemap: options.sourcemap ?? { server: undefined, client: undefined }, - }, - hook: (name: string, callback: HookCallback) => { - hooks[name] = hooks[name] || []; - hooks[name].push(callback); + const plugin = getCapturedPlugin(); + expect(plugin).not.toBeNull(); + expect(plugin?.name).toBe('sentry-nuxt-vite-config'); + }); + + it.each([ + { + label: 'prepare mode', + nuxtOptions: { _prepare: true }, + viteOptions: { mode: 'production', command: 'build' as const }, + buildConfig: { build: {}, plugins: [] }, }, - // Helper to trigger hooks in tests - triggerHook: async (name: string, ...args: unknown[]) => { - const callbacks = hooks[name] || []; - for (const callback of callbacks) { - await callback(...args); - } + { + label: 'dev mode', + nuxtOptions: { dev: true }, + viteOptions: { mode: 'development', command: 'build' as const }, + buildConfig: { build: {}, plugins: [] }, }, - }; - } + ])('does not add plugins to vite config in $label', async ({ nuxtOptions, viteOptions, buildConfig }) => { + const { setupSourceMaps } = await import('../../src/vite/sourceMaps'); + const mockNuxt = createMockNuxt(nuxtOptions); + const { mockAddVitePlugin, getCapturedPlugin } = createMockAddVitePlugin(); + + setupSourceMaps({ debug: true }, mockNuxt as unknown as Nuxt, mockAddVitePlugin); + await mockNuxt.triggerHook('modules:done'); - it('should not call any source map related functions in nuxt prepare mode', async () => { - const { setupSourceMaps } = await import('../../src/vite/sourceMaps'); - const mockNuxt = createMockNuxt({ _prepare: true }); + const plugin = getCapturedPlugin(); + expect(plugin).not.toBeNull(); - setupSourceMaps({ debug: true }, mockNuxt as unknown as Nuxt); + if (plugin && typeof plugin.config === 'function') { + const viteConfig: UserConfig = buildConfig; + plugin.config(viteConfig, viteOptions); + expect(viteConfig.plugins?.length).toBe(0); + } + }); - await mockNuxt.triggerHook('modules:done'); - await mockNuxt.triggerHook( - 'vite:extendConfig', - { build: {}, plugins: [], mode: 'production' }, - { isServer: true, isClient: false }, - ); - await mockNuxt.triggerHook('nitro:config', { rollupConfig: { plugins: [] }, dev: false }); + it.each([ + { label: 'server (SSR) build', buildConfig: { build: { ssr: true }, plugins: [] } }, + { label: 'client build', buildConfig: { build: { ssr: false }, plugins: [] } }, + ])('adds sentry vite plugin to vite config for $label in production', async ({ buildConfig }) => { + const { setupSourceMaps } = await import('../../src/vite/sourceMaps'); + const mockNuxt = createMockNuxt({ _prepare: false, dev: false }); + const { mockAddVitePlugin, getCapturedPlugin } = createMockAddVitePlugin(); - expect(mockSentryVitePlugin).not.toHaveBeenCalled(); - expect(mockSentryRollupPlugin).not.toHaveBeenCalled(); + setupSourceMaps({ debug: true }, mockNuxt as unknown as Nuxt, mockAddVitePlugin); + await mockNuxt.triggerHook('modules:done'); - expect(consoleLogSpy).not.toHaveBeenCalledWith(expect.stringContaining('[Sentry]')); + const plugin = getCapturedPlugin(); + expect(plugin).not.toBeNull(); + + if (plugin && typeof plugin.config === 'function') { + const viteConfig: UserConfig = buildConfig; + plugin.config(viteConfig, { mode: 'production', command: 'build' }); + expect(viteConfig.plugins?.length).toBeGreaterThan(0); + } + }); }); - it('should call source map related functions when not in prepare mode', async () => { - const { setupSourceMaps } = await import('../../src/vite/sourceMaps'); - const mockNuxt = createMockNuxt({ _prepare: false, dev: false }); + describe('sentry vite plugin calls', () => { + it('calls sentryVitePlugin in production mode', async () => { + const { setupSourceMaps } = await import('../../src/vite/sourceMaps'); + const mockNuxt = createMockNuxt({ _prepare: false, dev: false }); + const { mockAddVitePlugin, getCapturedPlugin } = createMockAddVitePlugin(); + + setupSourceMaps({ debug: true }, mockNuxt as unknown as Nuxt, mockAddVitePlugin); + await mockNuxt.triggerHook('modules:done'); + + const plugin = getCapturedPlugin(); + if (plugin && typeof plugin.config === 'function') { + plugin.config({ build: { ssr: false }, plugins: [] }, { mode: 'production', command: 'build' }); + } - setupSourceMaps({ debug: true }, mockNuxt as unknown as Nuxt); + expect(mockSentryVitePlugin).toHaveBeenCalled(); + }); - await mockNuxt.triggerHook('modules:done'); + it.each([ + { label: 'prepare mode', nuxtOptions: { _prepare: true }, viteMode: 'production' as const }, + { label: 'dev mode', nuxtOptions: { dev: true }, viteMode: 'development' as const }, + ])('does not call sentryVitePlugin in $label', async ({ nuxtOptions, viteMode }) => { + const { setupSourceMaps } = await import('../../src/vite/sourceMaps'); + const mockNuxt = createMockNuxt(nuxtOptions); + const { mockAddVitePlugin, getCapturedPlugin } = createMockAddVitePlugin(); - const viteConfig = { build: {}, plugins: [] as unknown[], mode: 'production' }; - await mockNuxt.triggerHook('vite:extendConfig', viteConfig, { isServer: true, isClient: false }); + setupSourceMaps({ debug: true }, mockNuxt as unknown as Nuxt, mockAddVitePlugin); + await mockNuxt.triggerHook('modules:done'); - const nitroConfig = { rollupConfig: { plugins: [] as unknown[], output: {} }, dev: false }; - await mockNuxt.triggerHook('nitro:config', nitroConfig); + const plugin = getCapturedPlugin(); + if (plugin && typeof plugin.config === 'function') { + plugin.config({ build: {}, plugins: [] }, { mode: viteMode, command: 'build' }); + } - expect(mockSentryVitePlugin).toHaveBeenCalled(); - expect(mockSentryRollupPlugin).toHaveBeenCalled(); + expect(mockSentryVitePlugin).not.toHaveBeenCalled(); + }); + }); + + describe('nitro:config hook', () => { + it('adds sentryRollupPlugin to nitro rollup config in production mode', async () => { + const { setupSourceMaps } = await import('../../src/vite/sourceMaps'); + const mockNuxt = createMockNuxt({ _prepare: false, dev: false }); + const { mockAddVitePlugin } = createMockAddVitePlugin(); + + setupSourceMaps({ debug: true }, mockNuxt as unknown as Nuxt, mockAddVitePlugin); + await mockNuxt.triggerHook('modules:done'); + + const nitroConfig = { rollupConfig: { plugins: [] as unknown[], output: {} }, dev: false }; + await mockNuxt.triggerHook('nitro:config', nitroConfig); + + expect(mockSentryRollupPlugin).toHaveBeenCalled(); + expect(nitroConfig.rollupConfig.plugins.length).toBeGreaterThan(0); + }); + + it.each([ + { + label: 'prepare mode', + nuxtOptions: { _prepare: true }, + nitroConfig: { rollupConfig: { plugins: [] }, dev: false }, + }, + { label: 'dev mode', nuxtOptions: { dev: true }, nitroConfig: { rollupConfig: { plugins: [] }, dev: true } }, + ])('does not add sentryRollupPlugin to nitro rollup config in $label', async ({ nuxtOptions, nitroConfig }) => { + const { setupSourceMaps } = await import('../../src/vite/sourceMaps'); + const mockNuxt = createMockNuxt(nuxtOptions); + const { mockAddVitePlugin } = createMockAddVitePlugin(); - expect(viteConfig.plugins.length).toBeGreaterThan(0); - expect(nitroConfig.rollupConfig.plugins.length).toBeGreaterThan(0); + setupSourceMaps({ debug: true }, mockNuxt as unknown as Nuxt, mockAddVitePlugin); + await mockNuxt.triggerHook('modules:done'); + await mockNuxt.triggerHook('nitro:config', nitroConfig); - expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('[Sentry]')); + expect(mockSentryRollupPlugin).not.toHaveBeenCalled(); + }); }); - it('should not call source map related functions in dev mode', async () => { - const { setupSourceMaps } = await import('../../src/vite/sourceMaps'); - const mockNuxt = createMockNuxt({ _prepare: false, dev: true }); + describe('debug logging', () => { + it('logs a [Sentry] message in production mode', async () => { + const { setupSourceMaps } = await import('../../src/vite/sourceMaps'); + const mockNuxt = createMockNuxt({ _prepare: false, dev: false }); + const { mockAddVitePlugin, getCapturedPlugin } = createMockAddVitePlugin(); + + setupSourceMaps({ debug: true }, mockNuxt as unknown as Nuxt, mockAddVitePlugin); + await mockNuxt.triggerHook('modules:done'); + + const plugin = getCapturedPlugin(); + if (plugin && typeof plugin.config === 'function') { + plugin.config({ build: { ssr: false }, plugins: [] }, { mode: 'production', command: 'build' }); + } + + const nitroConfig = { rollupConfig: { plugins: [] as unknown[], output: {} }, dev: false }; + await mockNuxt.triggerHook('nitro:config', nitroConfig); + + expect(consoleLogSpy).toHaveBeenCalledWith( + expect.stringContaining('[Sentry] Adding Sentry Vite plugin to the client runtime.'), + ); + expect(consoleLogSpy).toHaveBeenCalledWith( + expect.stringContaining('[Sentry] Adding Sentry Rollup plugin to the server runtime.'), + ); + }); + + it('does not log a [Sentry] messages in prepare mode', async () => { + const { setupSourceMaps } = await import('../../src/vite/sourceMaps'); + const mockNuxt = createMockNuxt({ _prepare: true }); + const { mockAddVitePlugin, getCapturedPlugin } = createMockAddVitePlugin(); + + setupSourceMaps({ debug: true }, mockNuxt as unknown as Nuxt, mockAddVitePlugin); + await mockNuxt.triggerHook('modules:done'); - setupSourceMaps({ debug: true }, mockNuxt as unknown as Nuxt); + const plugin = getCapturedPlugin(); + if (plugin && typeof plugin.config === 'function') { + plugin.config({ build: {}, plugins: [] }, { mode: 'production', command: 'build' }); + } - await mockNuxt.triggerHook('modules:done'); - await mockNuxt.triggerHook( - 'vite:extendConfig', - { build: {}, plugins: [], mode: 'development' }, - { isServer: true, isClient: false }, - ); - await mockNuxt.triggerHook('nitro:config', { rollupConfig: { plugins: [] }, dev: true }); + await mockNuxt.triggerHook('nitro:config', { rollupConfig: { plugins: [] }, dev: false }); - expect(mockSentryVitePlugin).not.toHaveBeenCalled(); - expect(mockSentryRollupPlugin).not.toHaveBeenCalled(); + expect(consoleLogSpy).not.toHaveBeenCalledWith(expect.stringContaining('[Sentry]')); + }); }); }); From cc11b083bdb6bcd756831025fcd505336a33e4f3 Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Mon, 23 Feb 2026 13:29:35 +0100 Subject: [PATCH 4/4] fix console spies --- packages/nuxt/test/vite/sourceMaps.test.ts | 38 ++++++++++++---------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/packages/nuxt/test/vite/sourceMaps.test.ts b/packages/nuxt/test/vite/sourceMaps.test.ts index e3d1347b3193..87e87d14b635 100644 --- a/packages/nuxt/test/vite/sourceMaps.test.ts +++ b/packages/nuxt/test/vite/sourceMaps.test.ts @@ -333,10 +333,14 @@ describe('getPluginOptions', () => { }); describe('validateDifferentSourceMapSettings', () => { - const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + let consoleWarnSpy: ReturnType; + + beforeEach(() => { + consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + }); afterEach(() => { - consoleWarnSpy.mockClear(); + consoleWarnSpy.mockRestore(); }); it('does not warn when both settings match', () => { @@ -350,16 +354,14 @@ describe('validateDifferentSourceMapSettings', () => { }); it('warns when settings conflict', () => { - const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); validateDifferentSourceMapSettings({ nuxtSettingKey: 'sourcemap.server', nuxtSettingValue: true, otherSettingKey: 'nitro.sourceMap', otherSettingValue: false, }); - expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('sourcemap.server')); - expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('nitro.sourceMap')); - warnSpy.mockRestore(); + expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining('sourcemap.server')); + expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining('nitro.sourceMap')); }); }); @@ -383,23 +385,20 @@ describe('extractNuxtSourceMapSetting', () => { }); describe('validate sourcemap settings', () => { - const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); - const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); + let consoleWarnSpy: ReturnType; + let consoleLogSpy: ReturnType; beforeEach(() => { - consoleLogSpy.mockClear(); - consoleWarnSpy.mockClear(); + consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); }); afterEach(() => { - vi.clearAllMocks(); + consoleWarnSpy.mockRestore(); + consoleLogSpy.mockRestore(); }); describe('should handle nitroConfig.rollupConfig.output.sourcemap settings', () => { - afterEach(() => { - vi.clearAllMocks(); - }); - type MinimalNitroConfig = { sourceMap?: SourceMapSetting; rollupConfig?: { @@ -453,17 +452,20 @@ describe('validate sourcemap settings', () => { describe('change Nuxt source map settings', () => { let nuxt: { options: { sourcemap: { client: boolean | 'hidden'; server: boolean | 'hidden' } } }; let sentryModuleOptions: SentryNuxtModuleOptions; - - const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); + let consoleLogSpy: ReturnType; beforeEach(() => { - consoleLogSpy.mockClear(); + consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); // @ts-expect-error - Nuxt types don't accept `undefined` but we want to test this case nuxt = { options: { sourcemap: { client: undefined } } }; sentryModuleOptions = {}; }); + afterEach(() => { + consoleLogSpy.mockRestore(); + }); + it.each([ { clientSourcemap: false, expectedSourcemap: false, expectedReturn: 'disabled' }, { clientSourcemap: 'hidden', expectedSourcemap: 'hidden', expectedReturn: 'enabled' },