Skip to content

Commit 2a22f24

Browse files
authored
feat(sveltekit): Add SvelteKit client and server init functions (#7408)
AddsSDK `init` functions for the SvelteKit server- and client-side SDK. Currently these functions * set SDK metadata * call the respective base SDK's init * set a `runtime` tag ('browser' / 'node' resp.) Also fix a bug in the Svelte SDK where passed in `options._metadata.sdk` was previously overwritten with the Svelte SDKs data.
1 parent 8b0d536 commit 2a22f24

File tree

9 files changed

+212
-19
lines changed

9 files changed

+212
-19
lines changed

packages/svelte/src/sdk.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { getDomElement } from '@sentry/utils';
77
*/
88
export function init(options: BrowserOptions): void {
99
options._metadata = options._metadata || {};
10-
options._metadata.sdk = {
10+
options._metadata.sdk = options._metadata.sdk || {
1111
name: 'sentry.javascript.svelte',
1212
packages: [
1313
{

packages/svelte/test/sdk.test.ts

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,21 @@
1-
import { addGlobalEventProcessor, init as browserInit, SDK_VERSION } from '@sentry/browser';
1+
import { SDK_VERSION } from '@sentry/browser';
2+
import * as SentryBrowser from '@sentry/browser';
23
import type { EventProcessor } from '@sentry/types';
34

45
import { detectAndReportSvelteKit, init as svelteInit, isSvelteKitApp } from '../src/sdk';
56

67
let passedEventProcessor: EventProcessor | undefined;
78

8-
jest.mock('@sentry/browser', () => {
9-
const actual = jest.requireActual('@sentry/browser');
10-
return {
11-
...actual,
12-
init: jest.fn().mockImplementation(actual.init),
13-
addGlobalEventProcessor: jest.fn().mockImplementation(proc => {
14-
passedEventProcessor = proc;
15-
}),
16-
};
17-
});
9+
const browserInit = jest.spyOn(SentryBrowser, 'init');
10+
const addGlobalEventProcessor = jest
11+
.spyOn(SentryBrowser, 'addGlobalEventProcessor')
12+
.mockImplementation((eventProcessor: EventProcessor) => {
13+
passedEventProcessor = eventProcessor;
14+
return () => {};
15+
});
1816

1917
describe('Initialize Svelte SDk', () => {
20-
afterAll(() => {
18+
beforeEach(() => {
2119
jest.clearAllMocks();
2220
});
2321

@@ -37,13 +35,45 @@ describe('Initialize Svelte SDk', () => {
3735
};
3836

3937
expect(browserInit).toHaveBeenCalledTimes(1);
40-
expect(browserInit).toHaveBeenCalledWith(expect.objectContaining(expectedMetadata));
38+
expect(browserInit).toHaveBeenLastCalledWith(expect.objectContaining(expectedMetadata));
39+
});
40+
41+
it("doesn't add the default svelte metadata, if metadata is already passed", () => {
42+
svelteInit({
43+
dsn: 'https://[email protected]/1337',
44+
_metadata: {
45+
sdk: {
46+
name: 'sentry.javascript.sveltekit',
47+
version: SDK_VERSION,
48+
packages: [
49+
{ name: 'npm:@sentry/sveltekit', version: SDK_VERSION },
50+
{ name: 'npm:@sentry/svelte', version: SDK_VERSION },
51+
],
52+
},
53+
},
54+
});
55+
56+
expect(browserInit).toHaveBeenCalledTimes(1);
57+
expect(browserInit).toHaveBeenLastCalledWith(
58+
expect.objectContaining({
59+
_metadata: {
60+
sdk: {
61+
name: 'sentry.javascript.sveltekit',
62+
version: SDK_VERSION,
63+
packages: [
64+
{ name: 'npm:@sentry/sveltekit', version: SDK_VERSION },
65+
{ name: 'npm:@sentry/svelte', version: SDK_VERSION },
66+
],
67+
},
68+
},
69+
}),
70+
);
4171
});
4272
});
4373

4474
describe('detectAndReportSvelteKit()', () => {
4575
const originalHtmlBody = document.body.innerHTML;
46-
afterEach(() => {
76+
beforeEach(() => {
4777
jest.clearAllMocks();
4878
document.body.innerHTML = originalHtmlBody;
4979
passedEventProcessor = undefined;
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
export * from '@sentry/svelte';
22

3-
// Just here so that eslint is happy until we export more stuff here
4-
export const PLACEHOLDER_CLIENT = 'PLACEHOLDER';
3+
export { init } from './sdk';

packages/sveltekit/src/client/sdk.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import type { BrowserOptions } from '@sentry/svelte';
2+
import { configureScope, init as initSvelteSdk } from '@sentry/svelte';
3+
4+
import { applySdkMetadata } from '../common/metadata';
5+
6+
/**
7+
*
8+
* @param options
9+
*/
10+
export function init(options: BrowserOptions): void {
11+
applySdkMetadata(options, ['sveltekit', 'svelte']);
12+
13+
initSvelteSdk(options);
14+
15+
configureScope(scope => {
16+
scope.setTag('runtime', 'browser');
17+
});
18+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { SDK_VERSION } from '@sentry/core';
2+
import type { Options, SdkInfo } from '@sentry/types';
3+
4+
const PACKAGE_NAME_PREFIX = 'npm:@sentry/';
5+
6+
/**
7+
* A builder for the SDK metadata in the options for the SDK initialization.
8+
*
9+
* Note: This function is identical to `buildMetadata` in Remix and NextJS.
10+
* We don't extract it for bundle size reasons.
11+
* If you make changes to this function consider updating the othera as well.
12+
*
13+
* @param options SDK options object that gets mutated
14+
* @param names list of package names
15+
*/
16+
export function applySdkMetadata(options: Options, names: string[]): void {
17+
options._metadata = options._metadata || {};
18+
options._metadata.sdk =
19+
options._metadata.sdk ||
20+
({
21+
name: 'sentry.javascript.sveltekit',
22+
packages: names.map(name => ({
23+
name: `${PACKAGE_NAME_PREFIX}${name}`,
24+
version: SDK_VERSION,
25+
})),
26+
version: SDK_VERSION,
27+
} as SdkInfo);
28+
}
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
export * from '@sentry/node';
22

3-
// Just here so that eslint is happy until we export more stuff here
4-
export const PLACEHOLDER_SERVER = 'PLACEHOLDER';
3+
export { init } from './sdk';

packages/sveltekit/src/server/sdk.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { configureScope } from '@sentry/core';
2+
import type { NodeOptions } from '@sentry/node';
3+
import { init as initNodeSdk } from '@sentry/node';
4+
5+
import { applySdkMetadata } from '../common/metadata';
6+
7+
/**
8+
*
9+
* @param options
10+
*/
11+
export function init(options: NodeOptions): void {
12+
applySdkMetadata(options, ['sveltekit', 'node']);
13+
14+
initNodeSdk(options);
15+
16+
configureScope(scope => {
17+
scope.setTag('runtime', 'node');
18+
});
19+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { getCurrentHub } from '@sentry/core';
2+
import * as SentrySvelte from '@sentry/svelte';
3+
import { SDK_VERSION, WINDOW } from '@sentry/svelte';
4+
5+
import { init } from '../../src/client/sdk';
6+
const svelteInit = jest.spyOn(SentrySvelte, 'init');
7+
8+
describe('Sentry client SDK', () => {
9+
describe('init', () => {
10+
afterEach(() => {
11+
jest.clearAllMocks();
12+
WINDOW.__SENTRY__.hub = undefined;
13+
});
14+
15+
it('adds SvelteKit metadata to the SDK options', () => {
16+
expect(svelteInit).not.toHaveBeenCalled();
17+
18+
init({});
19+
20+
expect(svelteInit).toHaveBeenCalledTimes(1);
21+
expect(svelteInit).toHaveBeenCalledWith(
22+
expect.objectContaining({
23+
_metadata: {
24+
sdk: {
25+
name: 'sentry.javascript.sveltekit',
26+
version: SDK_VERSION,
27+
packages: [
28+
{ name: 'npm:@sentry/sveltekit', version: SDK_VERSION },
29+
{ name: 'npm:@sentry/svelte', version: SDK_VERSION },
30+
],
31+
},
32+
},
33+
}),
34+
);
35+
});
36+
37+
it('sets the runtime tag on the scope', () => {
38+
const currentScope = getCurrentHub().getScope();
39+
40+
// @ts-ignore need access to protected _tags attribute
41+
expect(currentScope._tags).toEqual({});
42+
43+
init({ dsn: 'https://[email protected]/1337' });
44+
45+
// @ts-ignore need access to protected _tags attribute
46+
expect(currentScope._tags).toEqual({ runtime: 'browser' });
47+
});
48+
});
49+
});
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { getCurrentHub } from '@sentry/core';
2+
import * as SentryNode from '@sentry/node';
3+
import { SDK_VERSION } from '@sentry/node';
4+
import { GLOBAL_OBJ } from '@sentry/utils';
5+
6+
import { init } from '../../src/server/sdk';
7+
8+
const nodeInit = jest.spyOn(SentryNode, 'init');
9+
10+
describe('Sentry server SDK', () => {
11+
describe('init', () => {
12+
afterEach(() => {
13+
jest.clearAllMocks();
14+
GLOBAL_OBJ.__SENTRY__.hub = undefined;
15+
});
16+
17+
it('adds SvelteKit metadata to the SDK options', () => {
18+
expect(nodeInit).not.toHaveBeenCalled();
19+
20+
init({});
21+
22+
expect(nodeInit).toHaveBeenCalledTimes(1);
23+
expect(nodeInit).toHaveBeenCalledWith(
24+
expect.objectContaining({
25+
_metadata: {
26+
sdk: {
27+
name: 'sentry.javascript.sveltekit',
28+
version: SDK_VERSION,
29+
packages: [
30+
{ name: 'npm:@sentry/sveltekit', version: SDK_VERSION },
31+
{ name: 'npm:@sentry/node', version: SDK_VERSION },
32+
],
33+
},
34+
},
35+
}),
36+
);
37+
});
38+
39+
it('sets the runtime tag on the scope', () => {
40+
const currentScope = getCurrentHub().getScope();
41+
42+
// @ts-ignore need access to protected _tags attribute
43+
expect(currentScope._tags).toEqual({});
44+
45+
init({ dsn: 'https://[email protected]/1337' });
46+
47+
// @ts-ignore need access to protected _tags attribute
48+
expect(currentScope._tags).toEqual({ runtime: 'node' });
49+
});
50+
});
51+
});

0 commit comments

Comments
 (0)