Skip to content

Commit 45156d2

Browse files
authored
feat(nuxt): Add server config to root folder (#13583)
> This is a draft PR as this approach leads to an error as `hook.mjs` is not included in the `node_modules`. This has been fixed upstream but was not yet released for nuxt. Makes it possible to include a `sentry.server.config.ts` file in the root folder alongside `sentry.client.config.ts`. Currently, it has to be added in the `public` folder which is not 100% ideal.
1 parent 44f3ffa commit 45156d2

File tree

5 files changed

+160
-21
lines changed

5 files changed

+160
-21
lines changed

dev-packages/e2e-tests/test-applications/nuxt-3/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@
1414
},
1515
"dependencies": {
1616
"@sentry/nuxt": "latest || *",
17-
"nuxt": "3.12.4"
17+
"nuxt": "3.13.1"
1818
},
1919
"devDependencies": {
20-
"@nuxt/test-utils": "^3.13.1",
20+
"@nuxt/test-utils": "^3.14.1",
2121
"@playwright/test": "^1.44.1",
2222
"@sentry-internal/test-utils": "link:../../../test-utils"
2323
}

packages/nuxt/src/module.ts

+12-19
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import * as fs from 'fs';
2-
import * as path from 'path';
31
import { addPlugin, addPluginTemplate, addServerPlugin, createResolver, defineNuxtModule } from '@nuxt/kit';
42
import type { SentryNuxtModuleOptions } from './common/types';
3+
import { addServerConfigToBuild } from './vite/addServerConfig';
54
import { setupSourceMaps } from './vite/sourceMaps';
5+
import { findDefaultSdkInitFile } from './vite/utils';
66

77
export type ModuleOptions = SentryNuxtModuleOptions;
88

@@ -62,22 +62,15 @@ export default defineNuxtModule<ModuleOptions>({
6262
if (clientConfigFile || serverConfigFile) {
6363
setupSourceMaps(moduleOptions, nuxt);
6464
}
65+
if (serverConfigFile && serverConfigFile.includes('.server.config')) {
66+
if (moduleOptions.debug) {
67+
// eslint-disable-next-line no-console
68+
console.log(
69+
`[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.`,
70+
);
71+
}
72+
73+
addServerConfigToBuild(moduleOptions, nuxt, serverConfigFile);
74+
}
6575
},
6676
});
67-
68-
function findDefaultSdkInitFile(type: 'server' | 'client'): string | undefined {
69-
const possibleFileExtensions = ['ts', 'js', 'mjs', 'cjs', 'mts', 'cts'];
70-
71-
const cwd = process.cwd();
72-
const filePath = possibleFileExtensions
73-
.map(e =>
74-
path.resolve(
75-
type === 'server'
76-
? path.join(cwd, 'public', `instrument.${type}.${e}`)
77-
: path.join(cwd, `sentry.${type}.config.${e}`),
78-
),
79-
)
80-
.find(filename => fs.existsSync(filename));
81-
82-
return filePath ? path.basename(filePath) : undefined;
83-
}
+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import * as fs from 'fs';
2+
import * as path from 'path';
3+
import { createResolver } from '@nuxt/kit';
4+
import type { Nuxt } from '@nuxt/schema';
5+
import type { SentryNuxtModuleOptions } from '../common/types';
6+
7+
/**
8+
* 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.
9+
*
10+
* 1. Adding the file as a rollup import, so it is included in the build (automatically transpiles the file).
11+
* 2. Copying the file to the `.output` directory after the build process is finished.
12+
*/
13+
export function addServerConfigToBuild(
14+
moduleOptions: SentryNuxtModuleOptions,
15+
nuxt: Nuxt,
16+
serverConfigFile: string,
17+
): void {
18+
nuxt.hook('vite:extendConfig', async (viteInlineConfig, _env) => {
19+
if (
20+
typeof viteInlineConfig?.build?.rollupOptions?.input === 'object' &&
21+
'server' in viteInlineConfig.build.rollupOptions.input
22+
) {
23+
// Create a rollup entry for the server config to add it as `sentry.server.config.mjs` to the build
24+
(viteInlineConfig.build.rollupOptions.input as { [entryName: string]: string })['sentry.server.config'] =
25+
createResolver(nuxt.options.srcDir).resolve(`/${serverConfigFile}`);
26+
}
27+
28+
/**
29+
* When the build process is finished, copy the `sentry.server.config` file to the `.output` directory.
30+
* This is necessary because we need to reference this file path in the node --import option.
31+
*/
32+
nuxt.hook('close', async () => {
33+
const source = path.resolve('.nuxt/dist/server/sentry.server.config.mjs');
34+
const destination = path.resolve('.output/server/sentry.server.config.mjs');
35+
36+
try {
37+
await fs.promises.access(source, fs.constants.F_OK);
38+
await fs.promises.copyFile(source, destination);
39+
40+
if (moduleOptions.debug) {
41+
// eslint-disable-next-line no-console
42+
console.log(
43+
`[Sentry] Successfully added the content of the \`${serverConfigFile}\` file to \`${destination}\``,
44+
);
45+
}
46+
} catch (error) {
47+
if (moduleOptions.debug) {
48+
// eslint-disable-next-line no-console
49+
console.warn(
50+
`[Sentry] An error occurred when trying to add the \`${serverConfigFile}\` file to the \`.output\` directory`,
51+
error,
52+
);
53+
}
54+
}
55+
});
56+
});
57+
}

packages/nuxt/src/vite/utils.ts

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import * as fs from 'fs';
2+
import * as path from 'path';
3+
4+
/**
5+
* Find the default SDK init file for the given type (client or server).
6+
* The sentry.server.config file is prioritized over the instrument.server file.
7+
*/
8+
export function findDefaultSdkInitFile(type: 'server' | 'client'): string | undefined {
9+
const possibleFileExtensions = ['ts', 'js', 'mjs', 'cjs', 'mts', 'cts'];
10+
const cwd = process.cwd();
11+
12+
const filePaths: string[] = [];
13+
if (type === 'server') {
14+
for (const ext of possibleFileExtensions) {
15+
// order is important here - we want to prioritize the server.config file
16+
filePaths.push(path.join(cwd, `sentry.${type}.config.${ext}`));
17+
filePaths.push(path.join(cwd, 'public', `instrument.${type}.${ext}`));
18+
}
19+
} else {
20+
for (const ext of possibleFileExtensions) {
21+
filePaths.push(path.join(cwd, `sentry.${type}.config.${ext}`));
22+
}
23+
}
24+
25+
const filePath = filePaths.find(filename => fs.existsSync(filename));
26+
27+
return filePath ? path.basename(filePath) : undefined;
28+
}

packages/nuxt/test/vite/utils.test.ts

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import * as fs from 'fs';
2+
import { afterEach, describe, expect, it, vi } from 'vitest';
3+
import { findDefaultSdkInitFile } from '../../src/vite/utils';
4+
5+
vi.mock('fs');
6+
7+
describe('findDefaultSdkInitFile', () => {
8+
afterEach(() => {
9+
vi.clearAllMocks();
10+
});
11+
12+
it.each(['ts', 'js', 'mjs', 'cjs', 'mts', 'cts'])(
13+
'should return the server file with .%s extension if it exists',
14+
ext => {
15+
vi.spyOn(fs, 'existsSync').mockImplementation(filePath => {
16+
return !(filePath instanceof URL) && filePath.includes(`sentry.server.config.${ext}`);
17+
});
18+
19+
const result = findDefaultSdkInitFile('server');
20+
expect(result).toBe(`sentry.server.config.${ext}`);
21+
},
22+
);
23+
24+
it.each(['ts', 'js', 'mjs', 'cjs', 'mts', 'cts'])(
25+
'should return the client file with .%s extension if it exists',
26+
ext => {
27+
vi.spyOn(fs, 'existsSync').mockImplementation(filePath => {
28+
return !(filePath instanceof URL) && filePath.includes(`sentry.client.config.${ext}`);
29+
});
30+
31+
const result = findDefaultSdkInitFile('client');
32+
expect(result).toBe(`sentry.client.config.${ext}`);
33+
},
34+
);
35+
36+
it('should return undefined if no file with specified extensions exists', () => {
37+
vi.spyOn(fs, 'existsSync').mockReturnValue(false);
38+
39+
const result = findDefaultSdkInitFile('server');
40+
expect(result).toBeUndefined();
41+
});
42+
43+
it('should return undefined if no file exists', () => {
44+
vi.spyOn(fs, 'existsSync').mockReturnValue(false);
45+
46+
const result = findDefaultSdkInitFile('server');
47+
expect(result).toBeUndefined();
48+
});
49+
50+
it('should return the server config file if server.config and instrument exist', () => {
51+
vi.spyOn(fs, 'existsSync').mockImplementation(filePath => {
52+
return (
53+
!(filePath instanceof URL) &&
54+
(filePath.includes('sentry.server.config.js') || filePath.includes('instrument.server.js'))
55+
);
56+
});
57+
58+
const result = findDefaultSdkInitFile('server');
59+
expect(result).toBe('sentry.server.config.js');
60+
});
61+
});

0 commit comments

Comments
 (0)