Skip to content

Commit c48c301

Browse files
committed
Merge branch 'develop' into cg-nextjs-e2e-16
2 parents 2112bec + da08d49 commit c48c301

File tree

17 files changed

+419
-100
lines changed

17 files changed

+419
-100
lines changed

.size-limit.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ module.exports = [
103103
path: 'packages/browser/build/npm/esm/index.js',
104104
import: createImport('init', 'feedbackAsyncIntegration'),
105105
gzip: true,
106-
limit: '34 KB',
106+
limit: '35 KB',
107107
},
108108
// React SDK (ESM)
109109
{
@@ -215,7 +215,7 @@ module.exports = [
215215
import: createImport('init'),
216216
ignore: ['$app/stores'],
217217
gzip: true,
218-
limit: '41 KB',
218+
limit: '42 KB',
219219
},
220220
// Node-Core SDK (ESM)
221221
{

dev-packages/e2e-tests/test-applications/nextjs-15-basepath/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515
"@types/node": "^18.19.1",
1616
"@types/react": "18.0.26",
1717
"@types/react-dom": "18.0.9",
18-
"next": "15.4.2-canary.1",
19-
"react": "beta",
20-
"react-dom": "beta",
18+
"next": "^15",
19+
"react": "latest",
20+
"react-dom": "latest",
2121
"typescript": "~5.0.0"
2222
},
2323
"devDependencies": {

dev-packages/e2e-tests/test-applications/nextjs-15/app/ppr-error/[param]/page.tsx

Lines changed: 0 additions & 17 deletions
This file was deleted.

dev-packages/e2e-tests/test-applications/nextjs-15/next.config.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
const { withSentryConfig } = require('@sentry/nextjs');
22

33
/** @type {import('next').NextConfig} */
4-
const nextConfig = {
5-
experimental: {
6-
ppr: true,
7-
},
8-
};
4+
const nextConfig = {};
95

106
module.exports = withSentryConfig(nextConfig, {
117
silent: true,

dev-packages/e2e-tests/test-applications/nextjs-15/package.json

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,8 @@
1010
"test:dev": "TEST_ENV=development playwright test",
1111
"test:dev-turbo": "TEST_ENV=dev-turbopack playwright test",
1212
"test:build": "pnpm install && pnpm build",
13-
"test:build-canary": "pnpm install && pnpm add next@canary && pnpm add react@beta && pnpm add react-dom@beta && pnpm build",
14-
"//": "15.0.0-canary.194 is the canary release attached to Next.js RC 1. We need to use the canary version instead of the RC because PPR will not work without. The specific react version is also attached to RC 1.",
15-
"test:build-latest": "pnpm install && pnpm add [email protected] && pnpm add [email protected] && pnpm add [email protected] && pnpm build",
16-
"test:build-turbo": "pnpm install && pnpm add [email protected] && next build --turbopack",
13+
"test:build-latest": "pnpm install && pnpm add next@15 && pnpm build",
14+
"test:build-turbo": "pnpm install && next build --turbopack",
1715
"test:assert": "pnpm test:prod && pnpm test:dev"
1816
},
1917
"dependencies": {
@@ -22,9 +20,9 @@
2220
"@types/react": "18.0.26",
2321
"@types/react-dom": "18.0.9",
2422
"ai": "^3.0.0",
25-
"next": "15.4.2-canary.1",
26-
"react": "beta",
27-
"react-dom": "beta",
23+
"next": "15.5.4",
24+
"react": "latest",
25+
"react-dom": "latest",
2826
"typescript": "~5.0.0",
2927
"zod": "^3.22.4"
3028
},
@@ -37,10 +35,6 @@
3735
},
3836
"sentryTest": {
3937
"optionalVariants": [
40-
{
41-
"build-command": "pnpm test:build-canary",
42-
"label": "nextjs-15 (canary)"
43-
},
4438
{
4539
"build-command": "pnpm test:build-latest",
4640
"label": "nextjs-15 (latest)"

dev-packages/e2e-tests/test-applications/nextjs-15/tests/ppr-error.test.ts

Lines changed: 0 additions & 22 deletions
This file was deleted.

packages/core/src/utils/debug-ids.ts

Lines changed: 54 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,56 +6,82 @@ type StackString = string;
66
type CachedResult = [string, string];
77

88
let parsedStackResults: Record<StackString, CachedResult> | undefined;
9-
let lastKeysCount: number | undefined;
9+
let lastSentryKeysCount: number | undefined;
10+
let lastNativeKeysCount: number | undefined;
1011
let cachedFilenameDebugIds: Record<string, string> | undefined;
1112

1213
/**
1314
* Returns a map of filenames to debug identifiers.
15+
* Supports both proprietary _sentryDebugIds and native _debugIds (e.g., from Vercel) formats.
1416
*/
1517
export function getFilenameToDebugIdMap(stackParser: StackParser): Record<string, string> {
16-
const debugIdMap = GLOBAL_OBJ._sentryDebugIds;
17-
if (!debugIdMap) {
18+
const sentryDebugIdMap = GLOBAL_OBJ._sentryDebugIds;
19+
const nativeDebugIdMap = GLOBAL_OBJ._debugIds;
20+
21+
if (!sentryDebugIdMap && !nativeDebugIdMap) {
1822
return {};
1923
}
2024

21-
const debugIdKeys = Object.keys(debugIdMap);
25+
const sentryDebugIdKeys = sentryDebugIdMap ? Object.keys(sentryDebugIdMap) : [];
26+
const nativeDebugIdKeys = nativeDebugIdMap ? Object.keys(nativeDebugIdMap) : [];
2227

2328
// If the count of registered globals hasn't changed since the last call, we
2429
// can just return the cached result.
25-
if (cachedFilenameDebugIds && debugIdKeys.length === lastKeysCount) {
30+
if (
31+
cachedFilenameDebugIds &&
32+
sentryDebugIdKeys.length === lastSentryKeysCount &&
33+
nativeDebugIdKeys.length === lastNativeKeysCount
34+
) {
2635
return cachedFilenameDebugIds;
2736
}
2837

29-
lastKeysCount = debugIdKeys.length;
30-
31-
// Build a map of filename -> debug_id.
32-
cachedFilenameDebugIds = debugIdKeys.reduce<Record<string, string>>((acc, stackKey) => {
33-
if (!parsedStackResults) {
34-
parsedStackResults = {};
35-
}
38+
lastSentryKeysCount = sentryDebugIdKeys.length;
39+
lastNativeKeysCount = nativeDebugIdKeys.length;
3640

37-
const result = parsedStackResults[stackKey];
41+
// Build a map of filename -> debug_id from both sources
42+
cachedFilenameDebugIds = {};
3843

39-
if (result) {
40-
acc[result[0]] = result[1];
41-
} else {
42-
const parsedStack = stackParser(stackKey);
43-
44-
for (let i = parsedStack.length - 1; i >= 0; i--) {
45-
const stackFrame = parsedStack[i];
46-
const filename = stackFrame?.filename;
47-
const debugId = debugIdMap[stackKey];
44+
if (!parsedStackResults) {
45+
parsedStackResults = {};
46+
}
4847

49-
if (filename && debugId) {
50-
acc[filename] = debugId;
51-
parsedStackResults[stackKey] = [filename, debugId];
52-
break;
48+
const processDebugIds = (debugIdKeys: string[], debugIdMap: Record<string, string>): void => {
49+
for (const key of debugIdKeys) {
50+
const debugId = debugIdMap[key];
51+
const result = parsedStackResults?.[key];
52+
53+
if (result && cachedFilenameDebugIds && debugId) {
54+
// Use cached filename but update with current debug ID
55+
cachedFilenameDebugIds[result[0]] = debugId;
56+
// Update cached result with new debug ID
57+
if (parsedStackResults) {
58+
parsedStackResults[key] = [result[0], debugId];
59+
}
60+
} else if (debugId) {
61+
const parsedStack = stackParser(key);
62+
63+
for (let i = parsedStack.length - 1; i >= 0; i--) {
64+
const stackFrame = parsedStack[i];
65+
const filename = stackFrame?.filename;
66+
67+
if (filename && cachedFilenameDebugIds && parsedStackResults) {
68+
cachedFilenameDebugIds[filename] = debugId;
69+
parsedStackResults[key] = [filename, debugId];
70+
break;
71+
}
5372
}
5473
}
5574
}
75+
};
76+
77+
if (sentryDebugIdMap) {
78+
processDebugIds(sentryDebugIdKeys, sentryDebugIdMap);
79+
}
5680

57-
return acc;
58-
}, {});
81+
// Native _debugIds will override _sentryDebugIds if same file
82+
if (nativeDebugIdMap) {
83+
processDebugIds(nativeDebugIdKeys, nativeDebugIdMap);
84+
}
5985

6086
return cachedFilenameDebugIds;
6187
}

packages/core/src/utils/worldwide.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ export type InternalGlobal = {
4141
* file.
4242
*/
4343
_sentryDebugIds?: Record<string, string>;
44+
/**
45+
* Native debug IDs implementation (e.g., from Vercel).
46+
* This uses the same format as _sentryDebugIds but with a different global name.
47+
* Keys are `error.stack` strings, values are debug IDs.
48+
*/
49+
_debugIds?: Record<string, string>;
4450
/**
4551
* Raw module metadata that is injected by bundler plugins.
4652
*

packages/core/test/lib/prepareEvent.test.ts

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { clearGlobalScope } from '../testutils';
1919
describe('applyDebugIds', () => {
2020
afterEach(() => {
2121
GLOBAL_OBJ._sentryDebugIds = undefined;
22+
GLOBAL_OBJ._debugIds = undefined;
2223
});
2324

2425
it("should put debug IDs into an event's stack frames", () => {
@@ -114,6 +115,139 @@ describe('applyDebugIds', () => {
114115
debug_id: 'bbbbbbbb-bbbb-4bbb-bbbb-bbbbbbbbbb',
115116
});
116117
});
118+
119+
it('should support native _debugIds format', () => {
120+
GLOBAL_OBJ._debugIds = {
121+
'filename1.js\nfilename1.js': 'aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaa',
122+
'filename2.js\nfilename2.js': 'bbbbbbbb-bbbb-4bbb-bbbb-bbbbbbbbbb',
123+
'filename4.js\nfilename4.js': 'cccccccc-cccc-4ccc-cccc-cccccccccc',
124+
};
125+
126+
const stackParser = createStackParser([0, line => ({ filename: line })]);
127+
128+
const event: Event = {
129+
exception: {
130+
values: [
131+
{
132+
stacktrace: {
133+
frames: [
134+
{ filename: 'filename1.js' },
135+
{ filename: 'filename2.js' },
136+
{ filename: 'filename1.js' },
137+
{ filename: 'filename3.js' },
138+
],
139+
},
140+
},
141+
],
142+
},
143+
};
144+
145+
applyDebugIds(event, stackParser);
146+
147+
expect(event.exception?.values?.[0]?.stacktrace?.frames).toContainEqual({
148+
filename: 'filename1.js',
149+
debug_id: 'aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaa',
150+
});
151+
152+
expect(event.exception?.values?.[0]?.stacktrace?.frames).toContainEqual({
153+
filename: 'filename2.js',
154+
debug_id: 'bbbbbbbb-bbbb-4bbb-bbbb-bbbbbbbbbb',
155+
});
156+
157+
// expect not to contain an image for the stack frame that doesn't have a corresponding debug id
158+
expect(event.exception?.values?.[0]?.stacktrace?.frames).not.toContainEqual(
159+
expect.objectContaining({
160+
filename3: 'filename3.js',
161+
debug_id: expect.any(String),
162+
}),
163+
);
164+
});
165+
166+
it('should merge both _sentryDebugIds and _debugIds when both exist', () => {
167+
GLOBAL_OBJ._sentryDebugIds = {
168+
'filename1.js\nfilename1.js': 'aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaa',
169+
'filename2.js\nfilename2.js': 'bbbbbbbb-bbbb-4bbb-bbbb-bbbbbbbbbb',
170+
};
171+
172+
GLOBAL_OBJ._debugIds = {
173+
'filename3.js\nfilename3.js': 'cccccccc-cccc-4ccc-cccc-cccccccccc',
174+
'filename4.js\nfilename4.js': 'dddddddd-dddd-4ddd-dddd-dddddddddd',
175+
};
176+
177+
const stackParser = createStackParser([0, line => ({ filename: line })]);
178+
179+
const event: Event = {
180+
exception: {
181+
values: [
182+
{
183+
stacktrace: {
184+
frames: [
185+
{ filename: 'filename1.js' },
186+
{ filename: 'filename2.js' },
187+
{ filename: 'filename3.js' },
188+
{ filename: 'filename4.js' },
189+
],
190+
},
191+
},
192+
],
193+
},
194+
};
195+
196+
applyDebugIds(event, stackParser);
197+
198+
// Should have debug IDs from both sources
199+
expect(event.exception?.values?.[0]?.stacktrace?.frames).toContainEqual({
200+
filename: 'filename1.js',
201+
debug_id: 'aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaa',
202+
});
203+
204+
expect(event.exception?.values?.[0]?.stacktrace?.frames).toContainEqual({
205+
filename: 'filename2.js',
206+
debug_id: 'bbbbbbbb-bbbb-4bbb-bbbb-bbbbbbbbbb',
207+
});
208+
209+
expect(event.exception?.values?.[0]?.stacktrace?.frames).toContainEqual({
210+
filename: 'filename3.js',
211+
debug_id: 'cccccccc-cccc-4ccc-cccc-cccccccccc',
212+
});
213+
214+
expect(event.exception?.values?.[0]?.stacktrace?.frames).toContainEqual({
215+
filename: 'filename4.js',
216+
debug_id: 'dddddddd-dddd-4ddd-dddd-dddddddddd',
217+
});
218+
});
219+
220+
it('should prioritize _debugIds over _sentryDebugIds for the same file', () => {
221+
GLOBAL_OBJ._sentryDebugIds = {
222+
'filename1.js\nfilename1.js': 'old-debug-id-aaaa-aaaa-aaaa-aaaaaaaaaa',
223+
};
224+
225+
GLOBAL_OBJ._debugIds = {
226+
'filename1.js\nfilename1.js': 'new-debug-id-bbbb-bbbb-bbbb-bbbbbbbbbb',
227+
};
228+
229+
const stackParser = createStackParser([0, line => ({ filename: line })]);
230+
231+
const event: Event = {
232+
exception: {
233+
values: [
234+
{
235+
stacktrace: {
236+
frames: [{ filename: 'filename1.js' }],
237+
},
238+
},
239+
],
240+
},
241+
};
242+
243+
applyDebugIds(event, stackParser);
244+
245+
// Should use the newer native _debugIds format
246+
expect(event.exception?.values?.[0]?.stacktrace?.frames).toContainEqual({
247+
filename: 'filename1.js',
248+
debug_id: 'new-debug-id-bbbb-bbbb-bbbb-bbbbbbbbbb',
249+
});
250+
});
117251
});
118252

119253
describe('applyDebugMeta', () => {

0 commit comments

Comments
 (0)