Skip to content

Commit

Permalink
feat(nuxt): Add server config to root folder (#13583)
Browse files Browse the repository at this point in the history
> 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.
  • Loading branch information
s1gr1d committed Sep 9, 2024
1 parent 44f3ffa commit 45156d2
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 21 deletions.
4 changes: 2 additions & 2 deletions dev-packages/e2e-tests/test-applications/nuxt-3/package.json
Original file line number Diff line number Diff 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 number Diff line number Diff 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 Down Expand 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.`,
);
}

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 number Diff line number Diff 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 number Diff line number Diff 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 number Diff line number Diff 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');
});
});

0 comments on commit 45156d2

Please sign in to comment.