diff --git a/dev-packages/e2e-tests/test-applications/nuxt-3/package.json b/dev-packages/e2e-tests/test-applications/nuxt-3/package.json index 93471fff7aab..7173aeaa4ce5 100644 --- a/dev-packages/e2e-tests/test-applications/nuxt-3/package.json +++ b/dev-packages/e2e-tests/test-applications/nuxt-3/package.json @@ -14,10 +14,10 @@ }, "dependencies": { "@sentry/nuxt": "latest || *", - "nuxt": "3.12.4" + "nuxt": "3.13.1" }, "devDependencies": { - "@nuxt/test-utils": "^3.13.1", + "@nuxt/test-utils": "^3.14.1", "@playwright/test": "^1.44.1", "@sentry-internal/test-utils": "link:../../../test-utils" } diff --git a/packages/nuxt/src/module.ts b/packages/nuxt/src/module.ts index 5d529c99330c..faa48e5c3c26 100644 --- a/packages/nuxt/src/module.ts +++ b/packages/nuxt/src/module.ts @@ -1,8 +1,8 @@ -import * as fs from 'fs'; -import * as path from 'path'; import { addPlugin, addPluginTemplate, addServerPlugin, createResolver, defineNuxtModule } from '@nuxt/kit'; import type { SentryNuxtModuleOptions } from './common/types'; +import { addServerConfigToBuild } from './vite/addServerConfig'; import { setupSourceMaps } from './vite/sourceMaps'; +import { findDefaultSdkInitFile } from './vite/utils'; export type ModuleOptions = SentryNuxtModuleOptions; @@ -62,22 +62,15 @@ export default defineNuxtModule({ if (clientConfigFile || serverConfigFile) { setupSourceMaps(moduleOptions, nuxt); } + if (serverConfigFile && serverConfigFile.includes('.server.config')) { + if (moduleOptions.debug) { + // eslint-disable-next-line no-console + console.log( + `[Sentry] Using your \`${serverConfigFile}\` file for the server-side Sentry configuration. In case you have a \`public/instrument.server\` file, the \`public/instrument.server\` file will be ignored. Make sure the file path in your node \`--import\` option matches the Sentry server config file in your \`.output\` folder and has a \`.mjs\` extension.`, + ); + } + + addServerConfigToBuild(moduleOptions, nuxt, serverConfigFile); + } }, }); - -function findDefaultSdkInitFile(type: 'server' | 'client'): string | undefined { - const possibleFileExtensions = ['ts', 'js', 'mjs', 'cjs', 'mts', 'cts']; - - const cwd = process.cwd(); - const filePath = possibleFileExtensions - .map(e => - path.resolve( - type === 'server' - ? path.join(cwd, 'public', `instrument.${type}.${e}`) - : path.join(cwd, `sentry.${type}.config.${e}`), - ), - ) - .find(filename => fs.existsSync(filename)); - - return filePath ? path.basename(filePath) : undefined; -} diff --git a/packages/nuxt/src/vite/addServerConfig.ts b/packages/nuxt/src/vite/addServerConfig.ts new file mode 100644 index 000000000000..dc1fc21dd6bd --- /dev/null +++ b/packages/nuxt/src/vite/addServerConfig.ts @@ -0,0 +1,57 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { createResolver } from '@nuxt/kit'; +import type { Nuxt } from '@nuxt/schema'; +import type { SentryNuxtModuleOptions } from '../common/types'; + +/** + * Adds the `sentry.server.config.ts` file as `sentry.server.config.mjs` to the `.output` directory to be able to reference this file in the node --import option. + * + * 1. Adding the file as a rollup import, so it is included in the build (automatically transpiles the file). + * 2. Copying the file to the `.output` directory after the build process is finished. + */ +export function addServerConfigToBuild( + moduleOptions: SentryNuxtModuleOptions, + nuxt: Nuxt, + serverConfigFile: string, +): void { + nuxt.hook('vite:extendConfig', async (viteInlineConfig, _env) => { + if ( + typeof viteInlineConfig?.build?.rollupOptions?.input === 'object' && + 'server' in viteInlineConfig.build.rollupOptions.input + ) { + // Create a rollup entry for the server config to add it as `sentry.server.config.mjs` to the build + (viteInlineConfig.build.rollupOptions.input as { [entryName: string]: string })['sentry.server.config'] = + createResolver(nuxt.options.srcDir).resolve(`/${serverConfigFile}`); + } + + /** + * When the build process is finished, copy the `sentry.server.config` file to the `.output` directory. + * This is necessary because we need to reference this file path in the node --import option. + */ + nuxt.hook('close', async () => { + const source = path.resolve('.nuxt/dist/server/sentry.server.config.mjs'); + const destination = path.resolve('.output/server/sentry.server.config.mjs'); + + try { + await fs.promises.access(source, fs.constants.F_OK); + await fs.promises.copyFile(source, destination); + + if (moduleOptions.debug) { + // eslint-disable-next-line no-console + console.log( + `[Sentry] Successfully added the content of the \`${serverConfigFile}\` file to \`${destination}\``, + ); + } + } catch (error) { + if (moduleOptions.debug) { + // eslint-disable-next-line no-console + console.warn( + `[Sentry] An error occurred when trying to add the \`${serverConfigFile}\` file to the \`.output\` directory`, + error, + ); + } + } + }); + }); +} diff --git a/packages/nuxt/src/vite/utils.ts b/packages/nuxt/src/vite/utils.ts new file mode 100644 index 000000000000..7d794e807fd7 --- /dev/null +++ b/packages/nuxt/src/vite/utils.ts @@ -0,0 +1,28 @@ +import * as fs from 'fs'; +import * as path from 'path'; + +/** + * Find the default SDK init file for the given type (client or server). + * The sentry.server.config file is prioritized over the instrument.server file. + */ +export function findDefaultSdkInitFile(type: 'server' | 'client'): string | undefined { + const possibleFileExtensions = ['ts', 'js', 'mjs', 'cjs', 'mts', 'cts']; + const cwd = process.cwd(); + + const filePaths: string[] = []; + if (type === 'server') { + for (const ext of possibleFileExtensions) { + // order is important here - we want to prioritize the server.config file + filePaths.push(path.join(cwd, `sentry.${type}.config.${ext}`)); + filePaths.push(path.join(cwd, 'public', `instrument.${type}.${ext}`)); + } + } else { + for (const ext of possibleFileExtensions) { + filePaths.push(path.join(cwd, `sentry.${type}.config.${ext}`)); + } + } + + const filePath = filePaths.find(filename => fs.existsSync(filename)); + + return filePath ? path.basename(filePath) : undefined; +} diff --git a/packages/nuxt/test/vite/utils.test.ts b/packages/nuxt/test/vite/utils.test.ts new file mode 100644 index 000000000000..0ca81b3e2986 --- /dev/null +++ b/packages/nuxt/test/vite/utils.test.ts @@ -0,0 +1,61 @@ +import * as fs from 'fs'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { findDefaultSdkInitFile } from '../../src/vite/utils'; + +vi.mock('fs'); + +describe('findDefaultSdkInitFile', () => { + afterEach(() => { + vi.clearAllMocks(); + }); + + it.each(['ts', 'js', 'mjs', 'cjs', 'mts', 'cts'])( + 'should return the server file with .%s extension if it exists', + ext => { + vi.spyOn(fs, 'existsSync').mockImplementation(filePath => { + return !(filePath instanceof URL) && filePath.includes(`sentry.server.config.${ext}`); + }); + + const result = findDefaultSdkInitFile('server'); + expect(result).toBe(`sentry.server.config.${ext}`); + }, + ); + + it.each(['ts', 'js', 'mjs', 'cjs', 'mts', 'cts'])( + 'should return the client file with .%s extension if it exists', + ext => { + vi.spyOn(fs, 'existsSync').mockImplementation(filePath => { + return !(filePath instanceof URL) && filePath.includes(`sentry.client.config.${ext}`); + }); + + const result = findDefaultSdkInitFile('client'); + expect(result).toBe(`sentry.client.config.${ext}`); + }, + ); + + it('should return undefined if no file with specified extensions exists', () => { + vi.spyOn(fs, 'existsSync').mockReturnValue(false); + + const result = findDefaultSdkInitFile('server'); + expect(result).toBeUndefined(); + }); + + it('should return undefined if no file exists', () => { + vi.spyOn(fs, 'existsSync').mockReturnValue(false); + + const result = findDefaultSdkInitFile('server'); + expect(result).toBeUndefined(); + }); + + it('should return the server config file if server.config and instrument exist', () => { + vi.spyOn(fs, 'existsSync').mockImplementation(filePath => { + return ( + !(filePath instanceof URL) && + (filePath.includes('sentry.server.config.js') || filePath.includes('instrument.server.js')) + ); + }); + + const result = findDefaultSdkInitFile('server'); + expect(result).toBe('sentry.server.config.js'); + }); +});