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: 2 additions & 2 deletions dev-packages/e2e-tests/test-applications/nuxt-3/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -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"
}
Expand Down
31 changes: 12 additions & 19 deletions packages/nuxt/src/module.ts
Original file line numberDiff line numberDiff line change
@@ -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;

Expand DownExpand Up@@ -62,22 +62,15 @@ export default defineNuxtModule<ModuleOptions>({
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.`,
Comment thread
andreiborza marked this conversation as resolved.
);
}

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;
}
57 changes: 57 additions & 0 deletions packages/nuxt/src/vite/addServerConfig.ts
Original file line numberDiff line numberDiff line change
@@ -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,
);
}
}
});
});
}
28 changes: 28 additions & 0 deletions packages/nuxt/src/vite/utils.ts
Original file line numberDiff line numberDiff line change
@@ -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;
}
61 changes: 61 additions & 0 deletions packages/nuxt/test/vite/utils.test.ts
Original file line numberDiff line numberDiff line change
@@ -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');
});
});
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
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: 2 additions & 2 deletions dev-packages/e2e-tests/test-applications/nuxt-3/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -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"
}
Expand Down
31 changes: 12 additions & 19 deletions packages/nuxt/src/module.ts
Original file line numberDiff line numberDiff line change
@@ -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;

Expand DownExpand Up@@ -62,22 +62,15 @@ export default defineNuxtModule<ModuleOptions>({
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.`,
Comment thread
andreiborza marked this conversation as resolved.
);
}

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;
}
57 changes: 57 additions & 0 deletions packages/nuxt/src/vite/addServerConfig.ts
Original file line numberDiff line numberDiff line change
@@ -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,
);
}
}
});
});
}
28 changes: 28 additions & 0 deletions packages/nuxt/src/vite/utils.ts
Original file line numberDiff line numberDiff line change
@@ -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;
}
61 changes: 61 additions & 0 deletions packages/nuxt/test/vite/utils.test.ts
Original file line numberDiff line numberDiff line change
@@ -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');
});
});
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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: 2 additions & 2 deletions dev-packages/e2e-tests/test-applications/nuxt-3/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -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"
}
Expand Down
31 changes: 12 additions & 19 deletions packages/nuxt/src/module.ts
Original file line numberDiff line numberDiff line change
@@ -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;

Expand DownExpand Up@@ -62,22 +62,15 @@ export default defineNuxtModule<ModuleOptions>({
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.`,
Comment thread
andreiborza marked this conversation as resolved.
);
}

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;
}
57 changes: 57 additions & 0 deletions packages/nuxt/src/vite/addServerConfig.ts
Original file line numberDiff line numberDiff line change
@@ -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,
);
}
}
});
});
}
28 changes: 28 additions & 0 deletions packages/nuxt/src/vite/utils.ts
Original file line numberDiff line numberDiff line change
@@ -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;
}
61 changes: 61 additions & 0 deletions packages/nuxt/test/vite/utils.test.ts
Original file line numberDiff line numberDiff line change
@@ -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');
});
});
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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: 2 additions & 2 deletions dev-packages/e2e-tests/test-applications/nuxt-3/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -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"
}
Expand Down
31 changes: 12 additions & 19 deletions packages/nuxt/src/module.ts
Original file line numberDiff line numberDiff line change
@@ -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;

Expand DownExpand Up@@ -62,22 +62,15 @@ export default defineNuxtModule<ModuleOptions>({
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.`,
Comment thread
andreiborza marked this conversation as resolved.
);
}

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;
}
57 changes: 57 additions & 0 deletions packages/nuxt/src/vite/addServerConfig.ts
Original file line numberDiff line numberDiff line change
@@ -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,
);
}
}
});
});
}
28 changes: 28 additions & 0 deletions packages/nuxt/src/vite/utils.ts
Original file line numberDiff line numberDiff line change
@@ -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;
}
61 changes: 61 additions & 0 deletions packages/nuxt/test/vite/utils.test.ts
Original file line numberDiff line numberDiff line change
@@ -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');
});
});
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
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: 2 additions & 2 deletions dev-packages/e2e-tests/test-applications/nuxt-3/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -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"
}
Expand Down
31 changes: 12 additions & 19 deletions packages/nuxt/src/module.ts
Original file line numberDiff line numberDiff line change
@@ -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;

Expand DownExpand Up@@ -62,22 +62,15 @@ export default defineNuxtModule<ModuleOptions>({
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.`,
Comment thread
andreiborza marked this conversation as resolved.
);
}

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;
}
57 changes: 57 additions & 0 deletions packages/nuxt/src/vite/addServerConfig.ts
Original file line numberDiff line numberDiff line change
@@ -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,
);
}
}
});
});
}
28 changes: 28 additions & 0 deletions packages/nuxt/src/vite/utils.ts
Original file line numberDiff line numberDiff line change
@@ -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;
}
61 changes: 61 additions & 0 deletions packages/nuxt/test/vite/utils.test.ts
Original file line numberDiff line numberDiff line change
@@ -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');
});
});
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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: 2 additions & 2 deletions dev-packages/e2e-tests/test-applications/nuxt-3/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -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"
}
Expand Down
31 changes: 12 additions & 19 deletions packages/nuxt/src/module.ts
Original file line numberDiff line numberDiff line change
@@ -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;

Expand DownExpand Up@@ -62,22 +62,15 @@ export default defineNuxtModule<ModuleOptions>({
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.`,
Comment thread
andreiborza marked this conversation as resolved.
);
}

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;
}
57 changes: 57 additions & 0 deletions packages/nuxt/src/vite/addServerConfig.ts
Original file line numberDiff line numberDiff line change
@@ -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,
);
}
}
});
});
}
28 changes: 28 additions & 0 deletions packages/nuxt/src/vite/utils.ts
Original file line numberDiff line numberDiff line change
@@ -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;
}
61 changes: 61 additions & 0 deletions packages/nuxt/test/vite/utils.test.ts
Original file line numberDiff line numberDiff line change
@@ -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');
});
});
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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: 2 additions & 2 deletions dev-packages/e2e-tests/test-applications/nuxt-3/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -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"
}
Expand Down
31 changes: 12 additions & 19 deletions packages/nuxt/src/module.ts
Original file line numberDiff line numberDiff line change
@@ -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;

Expand DownExpand Up@@ -62,22 +62,15 @@ export default defineNuxtModule<ModuleOptions>({
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.`,
Comment thread
andreiborza marked this conversation as resolved.
);
}

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;
}
57 changes: 57 additions & 0 deletions packages/nuxt/src/vite/addServerConfig.ts
Original file line numberDiff line numberDiff line change
@@ -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,
);
}
}
});
});
}
28 changes: 28 additions & 0 deletions packages/nuxt/src/vite/utils.ts
Original file line numberDiff line numberDiff line change
@@ -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;
}
61 changes: 61 additions & 0 deletions packages/nuxt/test/vite/utils.test.ts
Original file line numberDiff line numberDiff line change
@@ -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');
});
});
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
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: 2 additions & 2 deletions dev-packages/e2e-tests/test-applications/nuxt-3/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -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"
}
Expand Down
31 changes: 12 additions & 19 deletions packages/nuxt/src/module.ts
Original file line numberDiff line numberDiff line change
@@ -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;

Expand DownExpand Up@@ -62,22 +62,15 @@ export default defineNuxtModule<ModuleOptions>({
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.`,
Comment thread
andreiborza marked this conversation as resolved.
);
}

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;
}
57 changes: 57 additions & 0 deletions packages/nuxt/src/vite/addServerConfig.ts
Original file line numberDiff line numberDiff line change
@@ -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,
);
}
}
});
});
}
28 changes: 28 additions & 0 deletions packages/nuxt/src/vite/utils.ts
Original file line numberDiff line numberDiff line change
@@ -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;
}
61 changes: 61 additions & 0 deletions packages/nuxt/test/vite/utils.test.ts
Original file line numberDiff line numberDiff line change
@@ -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');
});
});