Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
8 changes: 5 additions & 3 deletions packages/sveltekit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@
"@sentry/node": "7.42.0",
"@sentry/svelte": "7.42.0",
"@sentry/types": "7.42.0",
"@sentry/utils": "7.42.0"
"@sentry/utils": "7.42.0",
"magic-string": "^0.30.0"
},
"devDependencies": {
"@sveltejs/kit": "^1.10.0",
"vite": "^4.0.0"
"@sveltejs/kit": "^1.5.0",
Comment thread
Lms24 marked this conversation as resolved.
"vite": "4.0.0",
"typescript": "^4.9.3"
},
"scripts": {
"build": "run-p build:transpile build:types",
Expand Down
1 change: 1 addition & 0 deletions packages/sveltekit/src/config/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { withSentryViteConfig } from './withSentryViteConfig';
73 changes: 73 additions & 0 deletions packages/sveltekit/src/config/vitePlugins.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { logger } from '@sentry/utils';
import * as fs from 'fs';
import MagicString from 'magic-string';
import * as path from 'path';
import type { Plugin, TransformResult } from 'vite';

/**
* This plugin injects the `Sentry.init` calls from `sentry.(client|server).config.(ts|js)`
* into SvelteKit runtime files.
*/
export const injectSentryInitPlugin: Plugin = {
name: 'sentry-init-injection-plugin',

// In this hook, we inject the `Sentry.init` calls from `sentry.(client|server).config.(ts|js)`
// into SvelteKit runtime files: For the server, we inject it into the server's `index.js`
// file. For the client, we use the `_app.js` file.
transform(code, id) {
const serverIndexFilePath = path.join('@sveltejs', 'kit', 'src', 'runtime', 'server', 'index.js');
const devClientAppFilePath = path.join('.svelte-kit', 'generated', 'client', 'app.js');
const prodClientAppFilePath = path.join('.svelte-kit', 'generated', 'client-optimized', 'app.js');
Comment thread
AbhiPrasad marked this conversation as resolved.
Outdated

if (id.endsWith(serverIndexFilePath)) {
logger.debug('Injecting Server Sentry.init into', id);
return addSentryConfigFileImport('server', code, id) || code;
}

if (id.endsWith(devClientAppFilePath) || id.endsWith(prodClientAppFilePath)) {
logger.debug('Injecting Client Sentry.init into', id);
return addSentryConfigFileImport('client', code, id) || code;
}

return code;
},

// This plugin should run as early as possible,
// setting `enforce: 'pre'` ensures that it runs before the built-in vite plugins.
// see: https://vitejs.dev/guide/api-plugin.html#plugin-ordering
enforce: 'pre',
};

function addSentryConfigFileImport(
platform: 'server' | 'client',
originalCode: string,
entryFileId: string,
): TransformResult | undefined {
const projectRoot = process.cwd();
const sentryConfigFilename = getUserConfigFile(projectRoot, platform);

if (!sentryConfigFilename) {
logger.error(`Could not find sentry.${platform}.config.(ts|js) file.`);
return undefined;
}

const filePath = path.join(path.relative(path.dirname(entryFileId), projectRoot), sentryConfigFilename);
const importStmt = `\nimport "${filePath}";`;

const ms = new MagicString(originalCode);
ms.append(importStmt);

return { code: ms.toString(), map: ms.generateMap() };
}

function getUserConfigFile(projectDir: string, platform: 'server' | 'client'): string | undefined {
const possibilities = [`sentry.${platform}.config.ts`, `sentry.${platform}.config.js`];

for (const filename of possibilities) {
if (fs.existsSync(path.resolve(projectDir, filename))) {
return filename;
}
}

throw new Error(`Cannot find '${possibilities[0]}' or '${possibilities[1]}' in '${projectDir}'.`);
}
56 changes: 56 additions & 0 deletions packages/sveltekit/src/config/withSentryViteConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import type { UserConfig, UserConfigExport } from 'vite';

import { injectSentryInitPlugin } from './vitePlugins';

/**
* This function adds Sentry-specific configuration to your Vite config.
* Pass your config to this function and make sure the return value is exported
* from your `vite.config.js` file.
*
* Note: If you're already wrapping your config with another wrapper,
* for instance with `defineConfig` from vitest, make sure
* that the Sentry wrapper is the outermost one.
Comment thread
Lms24 marked this conversation as resolved.
*
* @param originalConfig your original vite config
*
* @returns a vite config with Sentry-specific configuration added to it.
*/
export function withSentryViteConfig(originalConfig: UserConfigExport): UserConfigExport {
if (typeof originalConfig === 'function') {
return function (this: unknown, ...viteConfigFunctionArgs: unknown[]): UserConfig | Promise<UserConfig> {
const userViteConfigObject = originalConfig.apply(this, viteConfigFunctionArgs);
if (userViteConfigObject instanceof Promise) {
return userViteConfigObject.then(userConfig => addSentryConfig(userConfig));
}
return addSentryConfig(userViteConfigObject);
};
} else if (originalConfig instanceof Promise) {
return originalConfig.then(userConfig => addSentryConfig(userConfig));
}
return addSentryConfig(originalConfig);
}

function addSentryConfig(originalConfig: UserConfig): UserConfig {
const config = { ...originalConfig };

const { plugins } = config;
if (!plugins) {
config.plugins = [injectSentryInitPlugin];
} else {
config.plugins = [injectSentryInitPlugin, ...plugins];
}
Comment thread
Lms24 marked this conversation as resolved.
Outdated

const mergedDevServerFileSystemConfig: UserConfig['server'] = {
fs: {
...(config.server && config.server.fs),
allow: [...((config.server && config.server.fs && config.server.fs.allow) || []), '.'],
Comment thread
AbhiPrasad marked this conversation as resolved.
},
};

config.server = {
...config.server,
...mergedDevServerFileSystemConfig,
};

return config;
}
1 change: 1 addition & 0 deletions packages/sveltekit/src/index.server.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './server';
export * from './config';

// This file is the main entrypoint on the server and/or when the package is `require`d

Expand Down
1 change: 1 addition & 0 deletions packages/sveltekit/src/index.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// Some of the exports collide, which is not allowed, unless we redifine the colliding
// exports in this file - which we do below.
export * from './client';
export * from './config';
export * from './server';

import type { Integration, Options, StackParser } from '@sentry/types';
Expand Down
58 changes: 58 additions & 0 deletions packages/sveltekit/test/config/vitePlugins.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import * as fs from 'fs';

import { injectSentryInitPlugin } from '../../src/config/vitePlugins';

describe('injectSentryInitPlugin', () => {
it('has its basic properties set', () => {
expect(injectSentryInitPlugin.name).toBe('sentry-init-injection-plugin');
expect(injectSentryInitPlugin.enforce).toBe('pre');
expect(typeof injectSentryInitPlugin.transform).toBe('function');
});

describe('tansform', () => {
jest.spyOn(fs, 'existsSync').mockReturnValue(true);

it('transforms the server index file', () => {
const code = 'foo();';
const id = '/node_modules/@sveltejs/kit/src/runtime/server/index.js';

// @ts-ignore -- transform is definitely defined and callable. Seems like TS doesn't know that.
const result = injectSentryInitPlugin.transform(code, id);

expect(result.code).toMatch(/foo\(\);\n.*import ".*sentry\.server\.config\.ts";/gm);
expect(result.map).toBeDefined();
});

it('transforms the client index file (dev server)', () => {
const code = 'foo();';
const id = '.svelte-kit/generated/client/app.js';

// @ts-ignore -- transform is definitely defined and callable. Seems like TS doesn't know that.
const result = injectSentryInitPlugin.transform(code, id);

expect(result.code).toMatch(/foo\(\);\n.*import ".*sentry\.client\.config\.ts";/gm);
expect(result.map).toBeDefined();
});

it('transforms the client index file (prod build)', () => {
const code = 'foo();';
const id = '.svelte-kit/generated/client-optimized/app.js';

// @ts-ignore -- transform is definitely defined and callable. Seems like TS doesn't know that.
const result = injectSentryInitPlugin.transform(code, id);

expect(result.code).toMatch(/foo\(\);\n.*import ".*sentry\.client\.config\.ts";/gm);
expect(result.map).toBeDefined();
});

it("doesn't transform other files", () => {
const code = 'foo();';
const id = './src/routes/+page.ts';

// @ts-ignore -- transform is definitely defined and callable. Seems like TS doesn't know that.
const result = injectSentryInitPlugin.transform(code, id);

expect(result).toBe(code);
});
});
});
90 changes: 90 additions & 0 deletions packages/sveltekit/test/config/withSentryViteConfig.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import type { Plugin, UserConfig } from 'vite';

import { withSentryViteConfig } from '../../src/config/withSentryViteConfig';

describe('withSentryViteConfig', () => {
const originalConfig = {
plugins: [{ name: 'foo' }],
Comment thread
Lms24 marked this conversation as resolved.
server: {
fs: {
allow: ['./bar'],
},
},
test: {
include: ['src/**/*.{test,spec}.{js,ts}'],
},
};

it('takes a POJO Vite config and returns the sentrified version', () => {
const sentrifiedConfig = withSentryViteConfig(originalConfig);

expect(typeof sentrifiedConfig).toBe('object');

const plugins = (sentrifiedConfig as UserConfig).plugins as Plugin[];

expect(plugins).toHaveLength(2);
expect(plugins[0].name).toBe('sentry-init-injection-plugin');
expect(plugins[1].name).toBe('foo');

expect((sentrifiedConfig as UserConfig).server?.fs?.allow).toStrictEqual(['./bar', '.']);

expect((sentrifiedConfig as any).test).toEqual(originalConfig.test);
});

it('takes a Vite config Promise and returns the sentrified version', async () => {
const sentrifiedConfig = await withSentryViteConfig(Promise.resolve(originalConfig));

expect(typeof sentrifiedConfig).toBe('object');

const plugins = (sentrifiedConfig as UserConfig).plugins as Plugin[];

expect(plugins).toHaveLength(2);
expect(plugins[0].name).toBe('sentry-init-injection-plugin');
expect(plugins[1].name).toBe('foo');

expect((sentrifiedConfig as UserConfig).server?.fs?.allow).toStrictEqual(['./bar', '.']);

expect((sentrifiedConfig as any).test).toEqual(originalConfig.test);
});

it('takes a function returning a Vite config and returns the sentrified version', () => {
const sentrifiedConfigFunction = withSentryViteConfig(_env => {
return originalConfig;
});
const sentrifiedConfig =
typeof sentrifiedConfigFunction === 'function' && sentrifiedConfigFunction({ command: 'build', mode: 'test' });

expect(typeof sentrifiedConfig).toBe('object');

const plugins = (sentrifiedConfig as UserConfig).plugins as Plugin[];

expect(plugins).toHaveLength(2);
expect(plugins[0].name).toBe('sentry-init-injection-plugin');
expect(plugins[1].name).toBe('foo');

expect((sentrifiedConfig as UserConfig).server?.fs?.allow).toStrictEqual(['./bar', '.']);

expect((sentrifiedConfig as any).test).toEqual(originalConfig.test);
});

it('takes a function returning a Vite config promise and returns the sentrified version', async () => {
const sentrifiedConfigFunction = withSentryViteConfig(_env => {
return originalConfig;
});
const sentrifiedConfig =
typeof sentrifiedConfigFunction === 'function' &&
(await sentrifiedConfigFunction({ command: 'build', mode: 'test' }));

expect(typeof sentrifiedConfig).toBe('object');

const plugins = (sentrifiedConfig as UserConfig).plugins as Plugin[];

expect(plugins).toHaveLength(2);
expect(plugins[0].name).toBe('sentry-init-injection-plugin');
expect(plugins[1].name).toBe('foo');

expect((sentrifiedConfig as UserConfig).server?.fs?.allow).toStrictEqual(['./bar', '.']);

expect((sentrifiedConfig as any).test).toEqual(originalConfig.test);
});
});
30 changes: 15 additions & 15 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4205,10 +4205,10 @@
dependencies:
highlight.js "^9.15.6"

"@sveltejs/kit@^1.10.0":
version "1.10.0"
resolved "https://registry.yarnpkg.com/@sveltejs/kit/-/kit-1.10.0.tgz#17d3565e5903f6d2c0730197fd875c2cf921ad01"
integrity sha512-0P35zHrByfbF3Ym3RdQL+RvzgsCDSyO3imSwuZ67XAD5HoCQFF3a8Mhh0V3sObz3rc5aJd4Qn82UpAihJqZ6gQ==
"@sveltejs/kit@^1.5.0":
version "1.11.0"
resolved "https://registry.yarnpkg.com/@sveltejs/kit/-/kit-1.11.0.tgz#23f233c351e5956356ba6f3206e40637c5f5dbda"
integrity sha512-PwViZcMoLgEU/jhLoSyjf5hSrHS67wvSm0ifBo4prP9irpGa5HuPOZeVDTL5tPDSBoKxtdYi1zlGdoiJfO86jA==
dependencies:
"@sveltejs/vite-plugin-svelte" "^2.0.0"
"@types/cookie" "^0.5.1"
Expand Down Expand Up @@ -12057,7 +12057,7 @@ esbuild@0.13.8:
esbuild-windows-64 "0.13.8"
esbuild-windows-arm64 "0.13.8"

esbuild@^0.16.14:
esbuild@^0.16.3:
version "0.16.17"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.16.17.tgz#fc2c3914c57ee750635fee71b89f615f25065259"
integrity sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==
Expand Down Expand Up @@ -21687,7 +21687,7 @@ postcss@^8.1.10, postcss@^8.1.7, postcss@^8.2.15:
picocolors "^1.0.0"
source-map-js "^1.0.2"

postcss@^8.2.4, postcss@^8.3.5, postcss@^8.3.7, postcss@^8.4.21:
postcss@^8.2.4, postcss@^8.3.5, postcss@^8.3.7, postcss@^8.4.19:
version "8.4.21"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.21.tgz#c639b719a57efc3187b13a1d765675485f4134f4"
integrity sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==
Expand Down Expand Up @@ -23284,7 +23284,7 @@ rollup@^2.45.1:
optionalDependencies:
fsevents "~2.3.2"

rollup@^3.10.0:
rollup@^3.7.0:
version "3.18.0"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.18.0.tgz#2354ba63ba66d6a09c652c3ea0dbcd9dad72bbde"
integrity sha512-J8C6VfEBjkvYPESMQYxKHxNOh4A5a3FlP+0BETGo34HEcE4eTlgCrO2+eWzlu2a/sHs2QUkZco+wscH7jhhgWg==
Expand Down Expand Up @@ -25983,7 +25983,7 @@ typescript@^3.9.5, typescript@^3.9.7:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.9.tgz#e69905c54bc0681d0518bd4d587cc6f2d0b1a674"
integrity sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w==

typescript@^4.9.4:
typescript@^4.9.3, typescript@^4.9.4:
version "4.9.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a"
integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==
Expand Down Expand Up @@ -26518,15 +26518,15 @@ verror@1.10.0:
core-util-is "1.0.2"
extsprintf "^1.2.0"

vite@^4.0.0:
version "4.1.4"
resolved "https://registry.yarnpkg.com/vite/-/vite-4.1.4.tgz#170d93bcff97e0ebc09764c053eebe130bfe6ca0"
integrity sha512-3knk/HsbSTKEin43zHu7jTwYWv81f8kgAL99G5NWBcA1LKvtvcVAC4JjBH1arBunO9kQka+1oGbrMKOjk4ZrBg==
vite@4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/vite/-/vite-4.0.0.tgz#b81b88349a06b2faaa53ae14cf96c942548e3454"
integrity sha512-ynad+4kYs8Jcnn8J7SacS9vAbk7eMy0xWg6E7bAhS1s79TK+D7tVFGXVZ55S7RNLRROU1rxoKlvZ/qjaB41DGA==
dependencies:
esbuild "^0.16.14"
postcss "^8.4.21"
esbuild "^0.16.3"
postcss "^8.4.19"
resolve "^1.22.1"
rollup "^3.10.0"
rollup "^3.7.0"
optionalDependencies:
fsevents "~2.3.2"

Expand Down