-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
feat(nuxt): allow custom configuration files paths in Nuxt module #20650
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
37cb8d6
b947a37
490c666
ef02bd4
48ded16
fead18f
c1ed6c9
207be84
4e4b30e
b2e0bf3
d0dc685
6fc8f2a
92ef57d
134c385
3cadbd5
b49a967
f67046a
da048cf
0c5a94a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -222,6 +222,38 @@ export type SentryNuxtModuleOptions = BuildTimeOptionsBase & { | |
| */ | ||
| autoInjectServerSentry?: 'top-level-import' | 'experimental_dynamic-import'; | ||
|
|
||
| /** | ||
| * Provide the resolved path to a custom Sentry client config file. | ||
| * | ||
| * If not provided, the default location (`<projectRoot>/sentry.client.config.(js|ts)`) will be used to look up the config file. | ||
| * If there is no file at the default location either, the SDK will initialize with the options specified in the `sentry` module options or with default options. | ||
| * | ||
| * @example | ||
| * | ||
| * ```ts | ||
| * sentry: { | ||
| * clientConfigFile: '~/client-sentry-config.ts', | ||
| * } | ||
| * ``` | ||
| */ | ||
| clientConfigFile?: string; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you think about adding
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep that seems better i'll change it 👍
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. True, that would be even better. You could call it
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @s1gr1d I implemented the single
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry didn't see your comment, renamed it 👌 |
||
|
|
||
| /** | ||
| * Provide the resolved path to a custom Sentry server config file. | ||
| * | ||
| * If not provided, the default location (`<projectRoot>/sentry.server.config.(js|ts)`) will be used to look up the config file. | ||
| * If there is no file at the default location either, the SDK will initialize with the options specified in the `sentry` module options or with default options. | ||
| * | ||
| * @example | ||
| * | ||
| * ```ts | ||
| * sentry: { | ||
| * serverConfigFile: '~/server-sentry-config.ts', | ||
| * } | ||
| * ``` | ||
| */ | ||
| serverConfigFile?: string; | ||
|
|
||
| /** | ||
| * When `autoInjectServerSentry` is set to `"experimental_dynamic-import"`, the SDK will wrap your Nitro server entrypoint | ||
| * with a dynamic `import()` to ensure all dependencies can be properly instrumented. Any previous exports from the entrypoint are still exported. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,8 @@ import type { Nuxt } from '@nuxt/schema'; | |
| import { consoleSandbox } from '@sentry/core'; | ||
| import * as fs from 'fs'; | ||
| import * as path from 'path'; | ||
| import type { SentryNuxtModuleOptions } from '../common/types'; | ||
| import { resolvePath } from '@nuxt/kit'; | ||
|
|
||
| /** | ||
| * Gets the major version of the installed nitro package. | ||
|
|
@@ -25,16 +27,34 @@ export async function getNitroMajorVersion(): Promise<number> { | |
| * 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', nuxt?: Nuxt): string | undefined { | ||
| export async function findDefaultSdkInitFile( | ||
| type: 'server' | 'client', | ||
| nuxt?: Nuxt, | ||
| options?: SentryNuxtModuleOptions, | ||
| ): Promise<string | undefined> { | ||
| const possibleFileExtensions = ['ts', 'js', 'mjs', 'cjs', 'mts', 'cts']; | ||
| const relativePaths: string[] = []; | ||
|
|
||
| if (type === 'server') { | ||
| if (options?.serverConfigFile) { | ||
| const resolvedPath = await resolvePath(options.serverConfigFile); | ||
| if (fs.existsSync(resolvedPath)) { | ||
| return resolvedPath; | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Custom server config paths skip critical server-side setupHigh Severity When a custom Additional Locations (1)Reviewed by Cursor Bugbot for commit 490c666. Configure here.
cursor[bot] marked this conversation as resolved.
Outdated
|
||
| throw new Error(`Server configuration file not found: ${resolvedPath}`); | ||
| } | ||
| for (const ext of possibleFileExtensions) { | ||
| relativePaths.push(`sentry.${type}.config.${ext}`); | ||
| relativePaths.push(path.join('public', `instrument.${type}.${ext}`)); | ||
| } | ||
| } else { | ||
| if (options?.clientConfigFile) { | ||
| const resolvedPath = await resolvePath(options.clientConfigFile); | ||
| if (fs.existsSync(resolvedPath)) { | ||
| return resolvedPath; | ||
| } | ||
|
cursor[bot] marked this conversation as resolved.
Outdated
|
||
| throw new Error(`Client configuration file not found: ${resolvedPath}`); | ||
| } | ||
| for (const ext of possibleFileExtensions) { | ||
| relativePaths.push(`sentry.${type}.config.${ext}`); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,13 @@ import { | |
| SENTRY_WRAPPED_FUNCTIONS, | ||
| } from '../../src/vite/utils'; | ||
|
|
||
|
|
||
| const resolvePathMock = vi.hoisted(() => vi.fn()); | ||
|
|
||
| vi.mock('@nuxt/kit', () => ({ | ||
| resolvePath: resolvePathMock, | ||
| })); | ||
|
|
||
| vi.mock('fs'); | ||
|
|
||
| describe('findDefaultSdkInitFile', () => { | ||
|
|
@@ -24,43 +31,76 @@ describe('findDefaultSdkInitFile', () => { | |
|
|
||
| it.each(['ts', 'js', 'mjs', 'cjs', 'mts', 'cts'])( | ||
| 'should return the server file path with .%s extension if it exists', | ||
| ext => { | ||
| async ext => { | ||
| vi.spyOn(fs, 'existsSync').mockImplementation(filePath => { | ||
| return !(filePath instanceof URL) && filePath.toString().includes(`sentry.server.config.${ext}`); | ||
| }); | ||
|
|
||
| const result = findDefaultSdkInitFile('server'); | ||
| const result = await findDefaultSdkInitFile('server'); | ||
| expect(result).toMatch(`packages/nuxt/sentry.server.config.${ext}`); | ||
| }, | ||
| ); | ||
|
|
||
| it.each(['ts', 'js', 'mjs', 'cjs', 'mts', 'cts'])( | ||
| 'should return the client file path with .%s extension if it exists', | ||
| ext => { | ||
| async ext => { | ||
| vi.spyOn(fs, 'existsSync').mockImplementation(filePath => { | ||
| return !(filePath instanceof URL) && filePath.toString().includes(`sentry.client.config.${ext}`); | ||
| }); | ||
|
|
||
| const result = findDefaultSdkInitFile('client'); | ||
| const result = await findDefaultSdkInitFile('client'); | ||
| expect(result).toMatch(`packages/nuxt/sentry.client.config.${ext}`); | ||
| }, | ||
| ); | ||
|
|
||
| it('should return undefined if no file with specified extensions exists', () => { | ||
| it('should return a custom client config file if it exists', async () => { | ||
| const expectedPath = '/some/path/mock-app/app/client-sentry-config.ts'; | ||
|
|
||
| vi.spyOn(fs, 'existsSync').mockImplementation(filePath => { | ||
| return !(filePath instanceof URL) && filePath.toString() === expectedPath; | ||
| }); | ||
|
|
||
| resolvePathMock.mockResolvedValue(expectedPath); | ||
|
|
||
| const result = await findDefaultSdkInitFile('client', undefined, { | ||
| clientConfigFile: '~/client-sentry-config.ts', | ||
| }); | ||
|
|
||
| expect(result).toBe(expectedPath); | ||
| expect(resolvePathMock).toHaveBeenCalledWith('~/client-sentry-config.ts'); | ||
| }); | ||
|
|
||
| it('should return a custom server config file if it exists', async () => { | ||
| const expectedPath = '/users/some-user/front-example/app/server-sentry-config.ts'; | ||
| vi.spyOn(fs, 'existsSync').mockImplementation(filePath => { | ||
| return !(filePath instanceof URL) && filePath.toString() === expectedPath; | ||
| }); | ||
|
|
||
| resolvePathMock.mockResolvedValue(expectedPath); | ||
|
|
||
| const result = await findDefaultSdkInitFile('server', undefined, { | ||
| serverConfigFile: '~/server-sentry-config.ts', | ||
| }); | ||
|
|
||
| expect(result).toBe(expectedPath); | ||
| expect(resolvePathMock).toHaveBeenCalledWith('~/server-sentry-config.ts'); | ||
| }); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Feat PR lacks integration or E2E test coverageLow Severity This Triggered by project rule: PR Review Guidelines for Cursor Bot Reviewed by Cursor Bugbot for commit b947a37. Configure here.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a Nuxt module related change, I couln't find the need for an integration test |
||
|
|
||
| it('should return undefined if no file with specified extensions exists', async () => { | ||
| vi.spyOn(fs, 'existsSync').mockReturnValue(false); | ||
|
|
||
| const result = findDefaultSdkInitFile('server'); | ||
| const result = await findDefaultSdkInitFile('server'); | ||
| expect(result).toBeUndefined(); | ||
| }); | ||
|
|
||
| it('should return undefined if no file exists', () => { | ||
| it('should return undefined if no file exists', async () => { | ||
| vi.spyOn(fs, 'existsSync').mockReturnValue(false); | ||
|
|
||
| const result = findDefaultSdkInitFile('server'); | ||
| const result = await findDefaultSdkInitFile('server'); | ||
| expect(result).toBeUndefined(); | ||
| }); | ||
|
|
||
| it('should return the server config file path if server.config and instrument exist', () => { | ||
| it('should return the server config file path if server.config and instrument exist', async () => { | ||
| vi.spyOn(fs, 'existsSync').mockImplementation(filePath => { | ||
| return ( | ||
| !(filePath instanceof URL) && | ||
|
|
@@ -69,11 +109,11 @@ describe('findDefaultSdkInitFile', () => { | |
| ); | ||
| }); | ||
|
|
||
| const result = findDefaultSdkInitFile('server'); | ||
| const result = await findDefaultSdkInitFile('server'); | ||
| expect(result).toMatch('packages/nuxt/sentry.server.config.js'); | ||
| }); | ||
|
|
||
| it('should return the latest layer config file path if client config exists', () => { | ||
| it('should return the latest layer config file path if client config exists', async () => { | ||
| vi.spyOn(fs, 'existsSync').mockImplementation(filePath => { | ||
| return !(filePath instanceof URL) && filePath.toString().includes('sentry.client.config.ts'); | ||
| }); | ||
|
|
@@ -91,11 +131,11 @@ describe('findDefaultSdkInitFile', () => { | |
| }, | ||
| } as unknown as Nuxt; | ||
|
|
||
| const result = findDefaultSdkInitFile('client', nuxtMock); | ||
| const result = await findDefaultSdkInitFile('client', nuxtMock); | ||
| expect(result).toMatch('packages/nuxt/sentry.client.config.ts'); | ||
| }); | ||
|
|
||
| it('should return the latest layer config file path if server config exists', () => { | ||
| it('should return the latest layer config file path if server config exists', async () => { | ||
| vi.spyOn(fs, 'existsSync').mockImplementation(filePath => { | ||
| return ( | ||
| !(filePath instanceof URL) && | ||
|
|
@@ -117,11 +157,11 @@ describe('findDefaultSdkInitFile', () => { | |
| }, | ||
| } as unknown as Nuxt; | ||
|
|
||
| const result = findDefaultSdkInitFile('server', nuxtMock); | ||
| const result = await findDefaultSdkInitFile('server', nuxtMock); | ||
| expect(result).toMatch('packages/nuxt/sentry.server.config.ts'); | ||
| }); | ||
|
|
||
| it('should return the latest layer config file path if client config exists in former layer', () => { | ||
| it('should return the latest layer config file path if client config exists in former layer', async () => { | ||
| vi.spyOn(fs, 'existsSync').mockImplementation(filePath => { | ||
| return !(filePath instanceof URL) && filePath.toString().includes('nuxt/sentry.client.config.ts'); | ||
| }); | ||
|
|
@@ -139,7 +179,7 @@ describe('findDefaultSdkInitFile', () => { | |
| }, | ||
| } as unknown as Nuxt; | ||
|
|
||
| const result = findDefaultSdkInitFile('client', nuxtMock); | ||
| const result = await findDefaultSdkInitFile('client', nuxtMock); | ||
| expect(result).toMatch('packages/nuxt/sentry.client.config.ts'); | ||
| }); | ||
| }); | ||
|
|
||


Uh oh!
There was an error while loading. Please reload this page.