Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/nuxt/src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ export default defineNuxtModule<ModuleOptions>({
}

nuxt.hooks.hook('nitro:init', nitro => {
if (nuxt.options?._prepare) {
return;
}

if (serverConfigFile) {
addMiddlewareInstrumentation(nitro);
}
Expand Down
40 changes: 26 additions & 14 deletions packages/nuxt/src/vite/sourceMaps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export function setupSourceMaps(moduleOptions: SentryNuxtModuleOptions, nuxt: Nu
let shouldDeleteFilesFallback = { client: true, server: true };

nuxt.hook('modules:done', () => {
if (sourceMapsEnabled && !nuxt.options.dev) {
if (sourceMapsEnabled && !nuxt.options.dev && !nuxt.options?._prepare) {
// Changing this setting will propagate:
// - for client to viteConfig.build.sourceMap
// - for server to viteConfig.build.sourceMap and nitro.sourceMap
Expand All @@ -49,23 +49,35 @@ export function setupSourceMaps(moduleOptions: SentryNuxtModuleOptions, nuxt: Nu
server: previousSourceMapSettings.server === 'unset',
};

if (
isDebug &&
!moduleOptions.sourcemaps?.filesToDeleteAfterUpload &&
// eslint-disable-next-line deprecation/deprecation
!sourceMapsUploadOptions.sourcemaps?.filesToDeleteAfterUpload &&
(shouldDeleteFilesFallback.client || shouldDeleteFilesFallback.server)
) {
// eslint-disable-next-line no-console
console.log(
"[Sentry] As Sentry enabled `'hidden'` source maps, source maps will be automatically deleted after uploading them to Sentry.",
);
if (isDebug && (shouldDeleteFilesFallback.client || shouldDeleteFilesFallback.server)) {
const enabledDeleteFallbacks =
shouldDeleteFilesFallback.client && shouldDeleteFilesFallback.server
? 'client-side and server-side'
: shouldDeleteFilesFallback.server
? 'server-side'
: 'client-side';

if (
!moduleOptions.sourcemaps?.filesToDeleteAfterUpload &&
// eslint-disable-next-line deprecation/deprecation
!sourceMapsUploadOptions.sourcemaps?.filesToDeleteAfterUpload
) {
// eslint-disable-next-line no-console
console.log(
`[Sentry] We enabled \`'hidden'\` source maps for your ${enabledDeleteFallbacks} build. Source map files will be automatically deleted after uploading them to Sentry.`,
);
} else {
// eslint-disable-next-line no-console
console.log(
`[Sentry] We enabled \`'hidden'\` source maps for your ${enabledDeleteFallbacks} build. Source map files will be deleted according to your \`sourcemaps.filesToDeleteAfterUpload\` configuration. To use automatic deletion instead, leave \`filesToDeleteAfterUpload\` empty.`,
);
}
}
}
});

nuxt.hook('vite:extendConfig', async (viteConfig, env) => {
if (sourceMapsEnabled && viteConfig.mode !== 'development') {
if (sourceMapsEnabled && viteConfig.mode !== 'development' && !nuxt.options?._prepare) {
const runtime = env.isServer ? 'server' : env.isClient ? 'client' : undefined;
const nuxtSourceMapSetting = extractNuxtSourceMapSetting(nuxt, runtime);

Expand Down Expand Up @@ -99,7 +111,7 @@ export function setupSourceMaps(moduleOptions: SentryNuxtModuleOptions, nuxt: Nu
});

nuxt.hook('nitro:config', (nitroConfig: NitroConfig) => {
if (sourceMapsEnabled && !nitroConfig.dev) {
if (sourceMapsEnabled && !nitroConfig.dev && !nuxt.options?._prepare) {
if (!nitroConfig.rollupConfig) {
nitroConfig.rollupConfig = {};
}
Expand Down
124 changes: 124 additions & 0 deletions packages/nuxt/test/vite/sourceMaps-nuxtHooks.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import type { Nuxt } from '@nuxt/schema';
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
import type { SourceMapSetting } from '../../src/vite/sourceMaps';

describe('setupSourceMaps hooks', () => {
const mockSentryVitePlugin = vi.fn(() => ({ name: 'sentry-vite-plugin' }));
const mockSentryRollupPlugin = vi.fn(() => ({ name: 'sentry-rollup-plugin' }));

const consoleLogSpy = vi.spyOn(console, 'log');
const consoleWarnSpy = vi.spyOn(console, 'warn');

beforeAll(() => {
vi.doMock('@sentry/vite-plugin', () => ({
sentryVitePlugin: mockSentryVitePlugin,
}));
vi.doMock('@sentry/rollup-plugin', () => ({
sentryRollupPlugin: mockSentryRollupPlugin,
}));
});

afterAll(() => {
consoleLogSpy.mockRestore();
consoleWarnSpy.mockRestore();
vi.doUnmock('@sentry/vite-plugin');
vi.doUnmock('@sentry/rollup-plugin');
});

beforeEach(() => {
consoleLogSpy.mockClear();
consoleWarnSpy.mockClear();
mockSentryVitePlugin.mockClear();
mockSentryRollupPlugin.mockClear();
});

type HookCallback = (...args: unknown[]) => void | Promise<void>;

function createMockNuxt(options: {
_prepare?: boolean;
dev?: boolean;
sourcemap?: SourceMapSetting | { server?: SourceMapSetting; client?: SourceMapSetting };
}) {
const hooks: Record<string, HookCallback[]> = {};

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);
}
},
};
}

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 });

setupSourceMaps({ debug: true }, mockNuxt as unknown as Nuxt);

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 });

expect(mockSentryVitePlugin).not.toHaveBeenCalled();
expect(mockSentryRollupPlugin).not.toHaveBeenCalled();

expect(consoleLogSpy).not.toHaveBeenCalledWith(expect.stringContaining('[Sentry]'));
});

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 });

setupSourceMaps({ debug: true }, mockNuxt as unknown as Nuxt);

await mockNuxt.triggerHook('modules:done');

const viteConfig = { build: {}, plugins: [] as unknown[], mode: 'production' };
await mockNuxt.triggerHook('vite:extendConfig', viteConfig, { isServer: true, isClient: false });

const nitroConfig = { rollupConfig: { plugins: [] as unknown[], output: {} }, dev: false };
await mockNuxt.triggerHook('nitro:config', nitroConfig);

expect(mockSentryVitePlugin).toHaveBeenCalled();
expect(mockSentryRollupPlugin).toHaveBeenCalled();

expect(viteConfig.plugins.length).toBeGreaterThan(0);
expect(nitroConfig.rollupConfig.plugins.length).toBeGreaterThan(0);

expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('[Sentry]'));
});

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 });

setupSourceMaps({ debug: true }, mockNuxt as unknown as Nuxt);

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 });

expect(mockSentryVitePlugin).not.toHaveBeenCalled();
expect(mockSentryRollupPlugin).not.toHaveBeenCalled();
});
});
Loading