Skip to content

Commit ff5d199

Browse files
authored
Merge branch 'develop' into dependabot/npm_and_yarn/ts-node-10.9.2
2 parents a900e35 + 471a683 commit ff5d199

12 files changed

Lines changed: 258 additions & 128 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@
129129
"madge": "8.0.0",
130130
"nodemon": "^3.1.10",
131131
"npm-run-all2": "^6.2.0",
132-
"prettier": "^3.6.2",
132+
"prettier": "^3.8.1",
133133
"prettier-plugin-astro": "^0.14.1",
134134
"rimraf": "^5.0.10",
135135
"rollup": "^4.35.0",

packages/browser/src/integrations/spotlight.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,9 @@ export const spotlightBrowserIntegration = defineIntegration(_spotlightIntegrati
8080
export function isSpotlightInteraction(event: Event): boolean {
8181
return Boolean(
8282
event.type === 'transaction' &&
83-
event.spans &&
84-
event.contexts?.trace &&
85-
event.contexts.trace.op === 'ui.action.click' &&
86-
event.spans.some(({ description }) => description?.includes('#sentry-spotlight')),
83+
event.spans &&
84+
event.contexts?.trace &&
85+
event.contexts.trace.op === 'ui.action.click' &&
86+
event.spans.some(({ description }) => description?.includes('#sentry-spotlight')),
8787
);
8888
}

packages/core/src/types-hoist/feedback/index.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,7 @@ export type { FeedbackEvent, UserFeedback, SendFeedback, SendFeedbackParams };
1515
* The integration's internal `options` member where every value should be set
1616
*/
1717
export interface FeedbackInternalOptions
18-
extends FeedbackGeneralConfiguration,
19-
FeedbackThemeConfiguration,
20-
FeedbackTextConfiguration,
21-
FeedbackCallbacks {}
18+
extends FeedbackGeneralConfiguration, FeedbackThemeConfiguration, FeedbackTextConfiguration, FeedbackCallbacks {}
2219

2320
type Hooks = unknown;
2421
type HTMLElement = unknown;

packages/core/src/types-hoist/options.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -616,8 +616,10 @@ export interface ClientOptions<TO extends BaseTransportOptions = BaseTransportOp
616616
}
617617

618618
/** Base configuration options for every SDK. */
619-
export interface CoreOptions<TO extends BaseTransportOptions = BaseTransportOptions>
620-
extends Omit<Partial<ClientOptions<TO>>, 'integrations' | 'transport' | 'stackParser'> {
619+
export interface CoreOptions<TO extends BaseTransportOptions = BaseTransportOptions> extends Omit<
620+
Partial<ClientOptions<TO>>,
621+
'integrations' | 'transport' | 'stackParser'
622+
> {
621623
/**
622624
* If this is set to false, default integrations will not be added, otherwise this will internally be set to the
623625
* recommended default integrations.

packages/core/src/utils/flushIfServerless.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ async function flushWithTimeout(timeout: number): Promise<void> {
3636
*/
3737
export async function flushIfServerless(
3838
params: // eslint-disable-next-line @typescript-eslint/no-explicit-any
39-
| { timeout?: number; cloudflareWaitUntil?: (task: Promise<any>) => void }
39+
| { timeout?: number; cloudflareWaitUntil?: (task: Promise<any>) => void }
4040
| { timeout?: number; cloudflareCtx?: MinimalCloudflareContext } = {},
4141
): Promise<void> {
4242
const { timeout = 2000 } = params;

packages/feedback/src/core/types.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ import type { FeedbackInternalOptions } from '@sentry/core';
55
*
66
* This is the config that gets passed into the integration constructor
77
*/
8-
export interface OptionalFeedbackConfiguration
9-
extends Omit<Partial<FeedbackInternalOptions>, 'themeLight' | 'themeDark'> {
8+
export interface OptionalFeedbackConfiguration extends Omit<
9+
Partial<FeedbackInternalOptions>,
10+
'themeLight' | 'themeDark'
11+
> {
1012
themeLight?: Partial<FeedbackInternalOptions['themeLight']>;
1113
themeDark?: Partial<FeedbackInternalOptions['themeLight']>;
1214
}

packages/nextjs/src/common/pages-router-instrumentation/_error.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,18 @@ type ContextOrProps = {
1313
/**
1414
* Capture the exception passed by nextjs to the `_error` page, adding context data as appropriate.
1515
*
16+
* This will not capture the exception if the status code is < 500 or if the pathname is not provided and will thus not return an event ID.
17+
*
1618
* @param contextOrProps The data passed to either `getInitialProps` or `render` by nextjs
19+
* @returns The Sentry event ID, or `undefined` if no event was captured
1720
*/
18-
export async function captureUnderscoreErrorException(contextOrProps: ContextOrProps): Promise<void> {
21+
export async function captureUnderscoreErrorException(contextOrProps: ContextOrProps): Promise<string | undefined> {
1922
const { req, res, err } = contextOrProps;
2023

2124
// 404s (and other 400-y friends) can trigger `_error`, but we don't want to send them to Sentry
2225
const statusCode = res?.statusCode || contextOrProps.statusCode;
2326
if (statusCode && statusCode < 500) {
24-
return Promise.resolve();
27+
return;
2528
}
2629

2730
// In previous versions of the suggested `_error.js` page in which this function is meant to be used, there was a
@@ -32,18 +35,18 @@ export async function captureUnderscoreErrorException(contextOrProps: ContextOrP
3235
// twice, we just bail if we sense we're in that now-extraneous second call. (We can tell which function we're in
3336
// because Nextjs passes `pathname` to `getInitialProps` but not to `render`.)
3437
if (!contextOrProps.pathname) {
35-
return Promise.resolve();
38+
return;
3639
}
3740

38-
withScope(scope => {
41+
const eventId = withScope(scope => {
3942
if (req) {
4043
const normalizedRequest = httpRequestToRequestData(req);
4144
scope.setSDKProcessingMetadata({ normalizedRequest });
4245
}
4346

4447
// If third-party libraries (or users themselves) throw something falsy, we want to capture it as a message (which
4548
// is what passing a string to `captureException` will wind up doing)
46-
captureException(err || `_error.js called with falsy error (${err})`, {
49+
return captureException(err || `_error.js called with falsy error (${err})`, {
4750
mechanism: {
4851
type: 'auto.function.nextjs.underscore_error',
4952
handled: false,
@@ -55,4 +58,6 @@ export async function captureUnderscoreErrorException(contextOrProps: ContextOrP
5558
});
5659

5760
waitUntil(flushSafelyWithTimeout());
61+
62+
return eventId;
5863
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2+
import { captureUnderscoreErrorException } from '../../../src/common/pages-router-instrumentation/_error';
3+
4+
const mockCaptureException = vi.fn(() => 'test-event-id');
5+
const mockWithScope = vi.fn((callback: (scope: any) => any) => {
6+
const mockScope = {
7+
setSDKProcessingMetadata: vi.fn(),
8+
};
9+
return callback(mockScope);
10+
});
11+
12+
vi.mock('@sentry/core', async () => {
13+
const actual = await vi.importActual('@sentry/core');
14+
return {
15+
...actual,
16+
captureException: (...args: unknown[]) => mockCaptureException(...args),
17+
withScope: (callback: (scope: any) => any) => mockWithScope(callback),
18+
httpRequestToRequestData: vi.fn(() => ({ url: 'http://test.com' })),
19+
};
20+
});
21+
22+
vi.mock('../../../src/common/utils/responseEnd', () => ({
23+
flushSafelyWithTimeout: vi.fn(() => Promise.resolve()),
24+
waitUntil: vi.fn(),
25+
}));
26+
27+
describe('captureUnderscoreErrorException', () => {
28+
beforeEach(() => {
29+
vi.clearAllMocks();
30+
});
31+
32+
afterEach(() => {
33+
vi.clearAllMocks();
34+
});
35+
36+
it('should return the event ID when capturing an exception', async () => {
37+
const error = new Error('Test error');
38+
const result = await captureUnderscoreErrorException({
39+
err: error,
40+
pathname: '/test',
41+
res: { statusCode: 500 } as any,
42+
});
43+
44+
expect(result).toBe('test-event-id');
45+
expect(mockCaptureException).toHaveBeenCalledWith(error, {
46+
mechanism: {
47+
type: 'auto.function.nextjs.underscore_error',
48+
handled: false,
49+
data: {
50+
function: '_error.getInitialProps',
51+
},
52+
},
53+
});
54+
});
55+
56+
it('should return undefined for 4xx status codes', async () => {
57+
const result = await captureUnderscoreErrorException({
58+
err: new Error('Not found'),
59+
pathname: '/test',
60+
res: { statusCode: 404 } as any,
61+
});
62+
63+
expect(result).toBeUndefined();
64+
expect(mockCaptureException).not.toHaveBeenCalled();
65+
});
66+
67+
it('should return undefined when pathname is not provided (render call)', async () => {
68+
const result = await captureUnderscoreErrorException({
69+
err: new Error('Test error'),
70+
res: { statusCode: 500 } as any,
71+
});
72+
73+
expect(result).toBeUndefined();
74+
expect(mockCaptureException).not.toHaveBeenCalled();
75+
});
76+
77+
it('should capture falsy errors as messages', async () => {
78+
const result = await captureUnderscoreErrorException({
79+
err: undefined,
80+
pathname: '/test',
81+
res: { statusCode: 500 } as any,
82+
});
83+
84+
expect(result).toBe('test-event-id');
85+
expect(mockCaptureException).toHaveBeenCalledWith('_error.js called with falsy error (undefined)', {
86+
mechanism: {
87+
type: 'auto.function.nextjs.underscore_error',
88+
handled: false,
89+
data: {
90+
function: '_error.getInitialProps',
91+
},
92+
},
93+
});
94+
});
95+
96+
it('should use statusCode from contextOrProps when res is not available', async () => {
97+
const result = await captureUnderscoreErrorException({
98+
err: new Error('Test error'),
99+
pathname: '/test',
100+
statusCode: 500,
101+
});
102+
103+
expect(result).toBe('test-event-id');
104+
expect(mockCaptureException).toHaveBeenCalled();
105+
});
106+
107+
it('should return undefined when statusCode from contextOrProps is 4xx', async () => {
108+
const result = await captureUnderscoreErrorException({
109+
err: new Error('Bad request'),
110+
pathname: '/test',
111+
statusCode: 400,
112+
});
113+
114+
expect(result).toBeUndefined();
115+
expect(mockCaptureException).not.toHaveBeenCalled();
116+
});
117+
});

packages/react/test/reactrouter-compat-utils/instrumentation.test.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1114,9 +1114,8 @@ describe('tryUpdateSpanNameBeforeEnd - source upgrade logic', () => {
11141114
const { handleNavigation } = await import('../../src/reactrouter-compat-utils/instrumentation');
11151115
const { startBrowserTracingNavigationSpan } = await import('@sentry/browser');
11161116
const { spanToJSON } = await import('@sentry/core');
1117-
const { transactionNameHasWildcard, resolveRouteNameAndSource } = await import(
1118-
'../../src/reactrouter-compat-utils/utils'
1119-
);
1117+
const { transactionNameHasWildcard, resolveRouteNameAndSource } =
1118+
await import('../../src/reactrouter-compat-utils/utils');
11201119

11211120
const location: Location = {
11221121
pathname: '/users/123',

packages/replay-internal/src/types/replay.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,8 @@ export interface ReplayIntegrationPrivacyOptions {
321321
}
322322

323323
export interface ReplayConfiguration
324-
extends ReplayIntegrationPrivacyOptions,
324+
extends
325+
ReplayIntegrationPrivacyOptions,
325326
OptionalReplayPluginOptions,
326327
Pick<RecordingOptions, 'maskAllText' | 'maskAllInputs'> {}
327328

0 commit comments

Comments
 (0)