Skip to content

Commit c9c49f5

Browse files
authored
Merge pull request #16115 from getsentry/prepare-release/9.14.0
meta(changelog): Update changelog for 9.14.0
2 parents fd00654 + 7c0365c commit c9c49f5

File tree

88 files changed

+3506
-297
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

88 files changed

+3506
-297
lines changed

.github/workflows/issue-package-label.yml

+37-11
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ jobs:
4444
"@sentry.bun": {
4545
"label": "Bun"
4646
},
47-
"@sentry.cloudflare - hono": {
47+
"@sentry.cloudflare.-.hono": {
4848
"label": "Hono"
4949
},
5050
"@sentry.cloudflare": {
@@ -68,20 +68,20 @@ jobs:
6868
"@sentry.nextjs": {
6969
"label": "Next.js"
7070
},
71-
"@sentry.node - express": {
71+
"@sentry.node.-.express": {
7272
"label": "Express"
7373
},
74-
"@sentry.node - fastify": {
74+
"@sentry.node.-.fastify": {
7575
"label": "Fastify"
7676
},
77-
"@sentry.node - koa": {
77+
"@sentry.node.-.koa": {
7878
"label": "Koa"
7979
},
80-
"@sentry.node - hapi": {
81-
"label": "Hapi
80+
"@sentry.node.-.hapi": {
81+
"label": "Hapi"
8282
},
83-
"@sentry.node - connect": {
84-
"label": "Connect
83+
"@sentry.node.-.connect": {
84+
"label": "Connect"
8585
},
8686
"@sentry.node": {
8787
"label": "Node.js"
@@ -90,7 +90,7 @@ jobs:
9090
"label": "Nuxt"
9191
},
9292
"@sentry.react-router": {
93-
"label": "React Router Framework "
93+
"label": "React Router Framework"
9494
},
9595
"@sentry.react": {
9696
"label": "React"
@@ -120,10 +120,10 @@ jobs:
120120
"label": "WASM"
121121
},
122122
"Sentry.Browser.Loader": {
123-
"label": "Browser\nLoader Script"
123+
"label": "Browser"
124124
},
125125
"Sentry.Browser.CDN.bundle": {
126-
"label": "Browser\nCDN Bundle"
126+
"label": "Browser"
127127
}
128128
}
129129
export_to: output
@@ -134,3 +134,29 @@ jobs:
134134
uses: actions-ecosystem/action-add-labels@v1
135135
with:
136136
labels: ${{ steps.packageLabel.outputs.label }}
137+
138+
- name: Map additional to issue label
139+
# https://github.com/kanga333/variable-mapper
140+
uses: kanga333/[email protected]
141+
id: additionalLabel
142+
if: steps.packageName.outputs.match != ''
143+
with:
144+
key: '${{ steps.packageName.outputs.group1 }}'
145+
# Note: Since this is handled as a regex, and JSON parse wrangles slashes /, we just use `.` instead
146+
map: |
147+
{
148+
"Sentry.Browser.Loader": {
149+
"label": "Loader Script"
150+
},
151+
"Sentry.Browser.CDN.bundle": {
152+
"label": "CDN Bundle"
153+
}
154+
}
155+
export_to: output
156+
157+
- name: Add additional label if applicable
158+
# Note: We only add the label if the issue is still open
159+
if: steps.additionalLabel.outputs.label != ''
160+
uses: actions-ecosystem/action-add-labels@v1
161+
with:
162+
labels: ${{ steps.additionalLabel.outputs.label }}

CHANGELOG.md

+24
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,30 @@
1010

1111
- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott
1212

13+
## 9.14.0
14+
15+
### Important Changes
16+
17+
- **feat: Add Supabase Integration ([#15719](https://github.com/getsentry/sentry-javascript/pull/15719))**
18+
19+
This PR adds Supabase integration to `@sentry/core`, allowing automatic instrumentation of Supabase client operations (database queries and authentication) for performance monitoring and error tracking.
20+
21+
- **feat(nestjs): Gracefully handle RPC scenarios in `SentryGlobalFilter` ([#16066](https://github.com/getsentry/sentry-javascript/pull/16066))**
22+
23+
This PR adds better RPC exception handling to `@sentry/nestjs`, preventing application crashes while still capturing errors and warning users when a dedicated filter is needed. The implementation gracefully handles the 'rpc' context type in `SentryGlobalFilter` to improve reliability in hybrid applications.
24+
25+
- **feat(react-router): Trace propagation ([#16070](https://github.com/getsentry/sentry-javascript/pull/16070))**
26+
27+
This PR adds trace propagation to `@sentry/react-router` by providing utilities to inject trace meta tags into HTML headers and offering a pre-built Sentry-instrumented request handler, improving distributed tracing capabilities across page loads.
28+
29+
### Other Changes
30+
31+
- feat(deps): Bump @prisma/instrumentation from 6.5.0 to 6.6.0 ([#16102](https://github.com/getsentry/sentry-javascript/pull/16102))
32+
- feat(nextjs): Improve server component data ([#15996](https://github.com/getsentry/sentry-javascript/pull/15996))
33+
- feat(nuxt): Log when adding HTML trace meta tags ([#16044](https://github.com/getsentry/sentry-javascript/pull/16044))
34+
- fix(node): Make body capturing more robust ([#16105](https://github.com/getsentry/sentry-javascript/pull/16105))
35+
- ref(node): Log when incoming request bodies are being captured ([#16104](https://github.com/getsentry/sentry-javascript/pull/16104))
36+
1337
## 9.13.0
1438

1539
### Important Changes

dev-packages/browser-integration-tests/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"@playwright/test": "~1.50.0",
4444
"@sentry-internal/rrweb": "2.34.0",
4545
"@sentry/browser": "9.13.0",
46+
"@supabase/supabase-js": "2.49.3",
4647
"axios": "1.8.2",
4748
"babel-loader": "^8.2.2",
4849
"fflate": "0.8.2",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
import { createClient } from '@supabase/supabase-js';
4+
window.Sentry = Sentry;
5+
6+
const supabaseClient = createClient('https://test.supabase.co', 'test-key');
7+
8+
Sentry.init({
9+
dsn: 'https://[email protected]/1337',
10+
integrations: [Sentry.browserTracingIntegration(), Sentry.supabaseIntegration({ supabaseClient })],
11+
tracesSampleRate: 1.0,
12+
});
13+
14+
// Simulate authentication operations
15+
async function performAuthenticationOperations() {
16+
await supabaseClient.auth.signInWithPassword({
17+
18+
password: 'test-password',
19+
});
20+
21+
await supabaseClient.auth.signOut();
22+
}
23+
24+
performAuthenticationOperations();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import type { Page } from '@playwright/test';
2+
import { expect } from '@playwright/test';
3+
import type { Event } from '@sentry/core';
4+
5+
import { sentryTest } from '../../../../utils/fixtures';
6+
import {
7+
getFirstSentryEnvelopeRequest,
8+
getMultipleSentryEnvelopeRequests,
9+
shouldSkipTracingTest,
10+
} from '../../../../utils/helpers';
11+
12+
async function mockSupabaseAuthRoutesSuccess(page: Page) {
13+
await page.route('**/auth/v1/token?grant_type=password**', route => {
14+
return route.fulfill({
15+
status: 200,
16+
body: JSON.stringify({
17+
access_token: 'test-access-token',
18+
refresh_token: 'test-refresh-token',
19+
token_type: 'bearer',
20+
expires_in: 3600,
21+
}),
22+
headers: {
23+
'Content-Type': 'application/json',
24+
},
25+
});
26+
});
27+
28+
await page.route('**/auth/v1/logout**', route => {
29+
return route.fulfill({
30+
status: 200,
31+
body: JSON.stringify({
32+
message: 'Logged out',
33+
}),
34+
headers: {
35+
'Content-Type': 'application/json',
36+
},
37+
});
38+
});
39+
}
40+
41+
async function mockSupabaseAuthRoutesFailure(page: Page) {
42+
await page.route('**/auth/v1/token?grant_type=password**', route => {
43+
return route.fulfill({
44+
status: 400,
45+
body: JSON.stringify({
46+
error_description: 'Invalid email or password',
47+
error: 'invalid_grant',
48+
}),
49+
headers: {
50+
'Content-Type': 'application/json',
51+
},
52+
});
53+
});
54+
55+
await page.route('**/auth/v1/logout**', route => {
56+
return route.fulfill({
57+
status: 400,
58+
body: JSON.stringify({
59+
error_description: 'Invalid refresh token',
60+
error: 'invalid_grant',
61+
}),
62+
headers: {
63+
'Content-Type': 'application/json',
64+
},
65+
});
66+
});
67+
}
68+
69+
70+
const bundle = process.env.PW_BUNDLE || '';
71+
// We only want to run this in non-CDN bundle mode
72+
if (bundle.startsWith('bundle')) {
73+
sentryTest.skip();
74+
}
75+
76+
sentryTest('should capture Supabase authentication spans', async ({ getLocalTestUrl, page }) => {
77+
if (shouldSkipTracingTest()) {
78+
return;
79+
}
80+
81+
await mockSupabaseAuthRoutesSuccess(page);
82+
83+
const url = await getLocalTestUrl({ testDir: __dirname });
84+
85+
const eventData = await getFirstSentryEnvelopeRequest<Event>(page, url);
86+
const supabaseSpans = eventData.spans?.filter(({ op }) => op?.startsWith('db.auth'));
87+
88+
expect(supabaseSpans).toHaveLength(2);
89+
expect(supabaseSpans![0]).toMatchObject({
90+
description: 'signInWithPassword',
91+
parent_span_id: eventData.contexts?.trace?.span_id,
92+
span_id: expect.any(String),
93+
start_timestamp: expect.any(Number),
94+
timestamp: expect.any(Number),
95+
trace_id: eventData.contexts?.trace?.trace_id,
96+
status: 'ok',
97+
data: expect.objectContaining({
98+
'sentry.op': 'db.auth.signInWithPassword',
99+
'sentry.origin': 'auto.db.supabase',
100+
}),
101+
});
102+
103+
expect(supabaseSpans![1]).toMatchObject({
104+
description: 'signOut',
105+
parent_span_id: eventData.contexts?.trace?.span_id,
106+
span_id: expect.any(String),
107+
start_timestamp: expect.any(Number),
108+
timestamp: expect.any(Number),
109+
trace_id: eventData.contexts?.trace?.trace_id,
110+
status: 'ok',
111+
data: expect.objectContaining({
112+
'sentry.op': 'db.auth.signOut',
113+
'sentry.origin': 'auto.db.supabase',
114+
}),
115+
});
116+
});
117+
118+
sentryTest('should capture Supabase authentication errors', async ({ getLocalTestUrl, page }) => {
119+
if (shouldSkipTracingTest()) {
120+
return;
121+
}
122+
123+
await mockSupabaseAuthRoutesFailure(page);
124+
125+
const url = await getLocalTestUrl({ testDir: __dirname });
126+
127+
const [errorEvent, transactionEvent] = await getMultipleSentryEnvelopeRequests<Event>(page, 2, { url });
128+
129+
const supabaseSpans = transactionEvent.spans?.filter(({ op }) => op?.startsWith('db.auth'));
130+
131+
expect(errorEvent.exception?.values?.[0].value).toBe('Invalid email or password');
132+
133+
expect(supabaseSpans).toHaveLength(2);
134+
expect(supabaseSpans![0]).toMatchObject({
135+
description: 'signInWithPassword',
136+
parent_span_id: transactionEvent.contexts?.trace?.span_id,
137+
span_id: expect.any(String),
138+
start_timestamp: expect.any(Number),
139+
timestamp: expect.any(Number),
140+
trace_id: transactionEvent.contexts?.trace?.trace_id,
141+
status: 'unknown_error',
142+
data: expect.objectContaining({
143+
'sentry.op': 'db.auth.signInWithPassword',
144+
'sentry.origin': 'auto.db.supabase',
145+
}),
146+
});
147+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
import { createClient } from '@supabase/supabase-js';
4+
window.Sentry = Sentry;
5+
6+
const supabaseClient = createClient('https://test.supabase.co', 'test-key');
7+
8+
Sentry.init({
9+
dsn: 'https://[email protected]/1337',
10+
integrations: [Sentry.browserTracingIntegration(), Sentry.supabaseIntegration({ supabaseClient })],
11+
tracesSampleRate: 1.0,
12+
});
13+
14+
// Simulate database operations
15+
async function performDatabaseOperations() {
16+
try {
17+
await supabaseClient.from('todos').insert([{ title: 'Test Todo' }]);
18+
19+
await supabaseClient.from('todos').select('*');
20+
21+
// Trigger an error to capture the breadcrumbs
22+
throw new Error('Test Error');
23+
} catch (error) {
24+
Sentry.captureException(error);
25+
}
26+
}
27+
28+
performDatabaseOperations();

0 commit comments

Comments
 (0)