Skip to content

Commit cc4d168

Browse files
authored
Merge pull request #17943 from getsentry/prepare-release/10.20.0
meta(changelog): Update changelog for 10.20.0
2 parents 2657761 + baa7a06 commit cc4d168

File tree

229 files changed

+5514
-1094
lines changed

Some content is hidden

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

229 files changed

+5514
-1094
lines changed

.github/workflows/canary.yml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,8 @@ jobs:
7676
build-command: 'test:build-canary'
7777
label: 'create-react-app (canary)'
7878
- test-application: 'nextjs-app-dir'
79-
build-command: 'test:build-canary'
80-
label: 'nextjs-app-dir (canary)'
81-
- test-application: 'nextjs-app-dir'
82-
build-command: 'test:build-latest'
83-
label: 'nextjs-app-dir (latest)'
79+
build-command: 'test:build-15'
80+
label: 'nextjs-app-dir (next@15)'
8481
- test-application: 'nextjs-13'
8582
build-command: 'test:build-latest'
8683
label: 'nextjs-13 (latest)'
@@ -90,12 +87,15 @@ jobs:
9087
- test-application: 'nextjs-14'
9188
build-command: 'test:build-latest'
9289
label: 'nextjs-14 (latest)'
93-
- test-application: 'nextjs-15'
94-
build-command: 'test:build-canary'
95-
label: 'nextjs-15 (canary)'
9690
- test-application: 'nextjs-15'
9791
build-command: 'test:build-latest'
9892
label: 'nextjs-15 (latest)'
93+
- test-application: 'nextjs-16'
94+
build-command: 'test:build-canary'
95+
label: 'nextjs-16 (canary)'
96+
- test-application: 'nextjs-16'
97+
build-command: 'test:build-canary-webpack'
98+
label: 'nextjs-16 (canary-webpack)'
9999
- test-application: 'nextjs-turbo'
100100
build-command: 'test:build-canary'
101101
label: 'nextjs-turbo (canary)'

.size-limit.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ module.exports = [
3838
path: 'packages/browser/build/npm/esm/index.js',
3939
import: createImport('init', 'browserTracingIntegration'),
4040
gzip: true,
41-
limit: '40.7 KB',
41+
limit: '41 KB',
4242
},
4343
{
4444
name: '@sentry/browser (incl. Tracing, Replay)',
@@ -82,7 +82,7 @@ module.exports = [
8282
path: 'packages/browser/build/npm/esm/index.js',
8383
import: createImport('init', 'browserTracingIntegration', 'replayIntegration', 'feedbackIntegration'),
8484
gzip: true,
85-
limit: '96 KB',
85+
limit: '97 KB',
8686
},
8787
{
8888
name: '@sentry/browser (incl. Feedback)',
@@ -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
{
@@ -128,7 +128,7 @@ module.exports = [
128128
path: 'packages/vue/build/esm/index.js',
129129
import: createImport('init'),
130130
gzip: true,
131-
limit: '29 KB',
131+
limit: '30 KB',
132132
},
133133
{
134134
name: '@sentry/vue (incl. Tracing)',
@@ -183,7 +183,7 @@ module.exports = [
183183
path: createCDNPath('bundle.tracing.min.js'),
184184
gzip: false,
185185
brotli: false,
186-
limit: '123 KB',
186+
limit: '124 KB',
187187
},
188188
{
189189
name: 'CDN Bundle (incl. Tracing, Replay) - uncompressed',
@@ -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
{

CHANGELOG.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,46 @@
44

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

7+
## 10.20.0
8+
9+
### Important Changes
10+
11+
- **feat(flags): Add Growthbook integration ([#17440](https://github.com/getsentry/sentry-javascript/pull/17440))**
12+
13+
Adds a new Growthbook integration for feature flag support.
14+
15+
- **feat(solid): Add support for TanStack Router Solid ([#17735](https://github.com/getsentry/sentry-javascript/pull/17735))**
16+
17+
Adds support for TanStack Router in the Solid SDK, enabling better routing instrumentation for Solid applications.
18+
19+
- **feat(nextjs): Support native debugIds in turbopack ([#17853](https://github.com/getsentry/sentry-javascript/pull/17853))**
20+
21+
Adds support for native Debug IDs in Turbopack, improving source map resolution and error tracking for Next.js applications using Turbopack. Native Debug ID generation will be enabled automatically for compatible versions.
22+
23+
### Other Changes
24+
25+
- feat(nextjs): Prepare for next 16 bundler default ([#17868](https://github.com/getsentry/sentry-javascript/pull/17868))
26+
- feat(node): Capture `pino` logger name ([#17930](https://github.com/getsentry/sentry-javascript/pull/17930))
27+
- fix(browser): Ignore React 19.2+ component render measure entries ([#17905](https://github.com/getsentry/sentry-javascript/pull/17905))
28+
- fix(nextjs): Fix createRouteManifest with basePath ([#17838](https://github.com/getsentry/sentry-javascript/pull/17838))
29+
- fix(react): Add `POP` guard for long-running `pageload` spans ([#17867](https://github.com/getsentry/sentry-javascript/pull/17867))
30+
- fix(tracemetrics): Send boolean for internal replay attribute ([#17908](https://github.com/getsentry/sentry-javascript/pull/17908))
31+
- ref(core): Add weight tracking logic to browser logs/metrics ([#17901](https://github.com/getsentry/sentry-javascript/pull/17901))
32+
33+
<details>
34+
<summary> <strong>Internal Changes</strong> </summary>
35+
- chore(nextjs): Add Next.js 16 peer dependency ([#17925](https://github.com/getsentry/sentry-javascript/pull/17925))
36+
- chore(ci): Update Next.js canary testing ([#17939](https://github.com/getsentry/sentry-javascript/pull/17939))
37+
- chore: Bump size limit ([#17941](https://github.com/getsentry/sentry-javascript/pull/17941))
38+
- test(nextjs): Add next@16 e2e test ([#17922](https://github.com/getsentry/sentry-javascript/pull/17922))
39+
- test(nextjs): Update next 15 tests ([#17919](https://github.com/getsentry/sentry-javascript/pull/17919))
40+
- chore: Add external contributor to CHANGELOG.md ([#17915](https://github.com/getsentry/sentry-javascript/pull/17915))
41+
- chore: Add external contributor to CHANGELOG.md ([#17928](https://github.com/getsentry/sentry-javascript/pull/17928))
42+
- chore: Add external contributor to CHANGELOG.md ([#17940](https://github.com/getsentry/sentry-javascript/pull/17940))
43+
</details>
44+
45+
Work in this release was contributed by @seoyeon9888, @madhuchavva and @thedanchez . Thank you for your contributions!
46+
747
## 10.19.0
848

949
- feat(tracemetrics): Add trace metrics behind an experiments flag ([#17883](https://github.com/getsentry/sentry-javascript/pull/17883))
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { expect } from '@playwright/test';
2+
import { _INTERNAL_FLAG_BUFFER_SIZE as FLAG_BUFFER_SIZE } from '@sentry/core';
3+
import { sentryTest } from '../../../../../../utils/fixtures';
4+
import {
5+
envelopeRequestParser,
6+
shouldSkipFeatureFlagsTest,
7+
waitForErrorRequest,
8+
} from '../../../../../../utils/helpers';
9+
10+
sentryTest('GrowthBook onError: basic eviction/update and no async tasks', async ({ getLocalTestUrl, page }) => {
11+
if (shouldSkipFeatureFlagsTest()) {
12+
sentryTest.skip();
13+
}
14+
15+
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
16+
return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ id: 'test-id' }) });
17+
});
18+
19+
const url = await getLocalTestUrl({ testDir: __dirname, skipDsnRouteHandler: true });
20+
await page.goto(url);
21+
22+
await page.evaluate(bufferSize => {
23+
const gb = new (window as any).GrowthBook();
24+
25+
for (let i = 1; i <= bufferSize; i++) {
26+
gb.isOn(`feat${i}`);
27+
}
28+
29+
gb.__setOn(`feat${bufferSize + 1}`, true);
30+
gb.isOn(`feat${bufferSize + 1}`); // eviction
31+
32+
gb.__setOn('feat3', true);
33+
gb.isOn('feat3'); // update
34+
35+
// Test getFeatureValue with boolean values (should be captured)
36+
gb.__setFeatureValue('bool-feat', true);
37+
gb.getFeatureValue('bool-feat', false);
38+
39+
// Test getFeatureValue with non-boolean values (should be ignored)
40+
gb.__setFeatureValue('string-feat', 'hello');
41+
gb.getFeatureValue('string-feat', 'default');
42+
gb.__setFeatureValue('number-feat', 42);
43+
gb.getFeatureValue('number-feat', 0);
44+
}, FLAG_BUFFER_SIZE);
45+
46+
const reqPromise = waitForErrorRequest(page);
47+
await page.locator('#error').click();
48+
const req = await reqPromise;
49+
const event = envelopeRequestParser(req);
50+
51+
const values = event.contexts?.flags?.values || [];
52+
53+
// After the sequence of operations:
54+
// 1. feat1-feat100 are added (100 items)
55+
// 2. feat101 is added, evicts feat1 (100 items: feat2-feat100, feat101)
56+
// 3. feat3 is updated to true, moves to end (100 items: feat2, feat4-feat100, feat101, feat3)
57+
// 4. bool-feat is added, evicts feat2 (100 items: feat4-feat100, feat101, feat3, bool-feat)
58+
59+
const expectedFlags = [];
60+
for (let i = 4; i <= FLAG_BUFFER_SIZE; i++) {
61+
expectedFlags.push({ flag: `feat${i}`, result: false });
62+
}
63+
expectedFlags.push({ flag: `feat${FLAG_BUFFER_SIZE + 1}`, result: true });
64+
expectedFlags.push({ flag: 'feat3', result: true });
65+
expectedFlags.push({ flag: 'bool-feat', result: true }); // Only boolean getFeatureValue should be captured
66+
67+
expect(values).toEqual(expectedFlags);
68+
});
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
// Minimal mock GrowthBook class for tests
4+
window.GrowthBook = class {
5+
constructor() {
6+
this._onFlags = Object.create(null);
7+
this._featureValues = Object.create(null);
8+
}
9+
10+
isOn(featureKey) {
11+
return !!this._onFlags[featureKey];
12+
}
13+
14+
getFeatureValue(featureKey, defaultValue) {
15+
return Object.prototype.hasOwnProperty.call(this._featureValues, featureKey)
16+
? this._featureValues[featureKey]
17+
: defaultValue;
18+
}
19+
20+
// Helpers for tests
21+
__setOn(featureKey, value) {
22+
this._onFlags[featureKey] = !!value;
23+
}
24+
25+
__setFeatureValue(featureKey, value) {
26+
this._featureValues[featureKey] = value;
27+
}
28+
};
29+
30+
window.Sentry = Sentry;
31+
window.sentryGrowthBookIntegration = Sentry.growthbookIntegration({ growthbookClass: window.GrowthBook });
32+
33+
Sentry.init({
34+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
35+
sampleRate: 1.0,
36+
integrations: [window.sentryGrowthBookIntegration],
37+
});
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
document.getElementById('error').addEventListener('click', () => {
2+
throw new Error('Button triggered error');
3+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
</head>
6+
<body>
7+
<button id="error">Throw Error</button>
8+
</body>
9+
<script src="./subject.js"></script>
10+
</html>
11+
12+
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { expect } from '@playwright/test';
2+
import type { Scope } from '@sentry/browser';
3+
import { sentryTest } from '../../../../../../utils/fixtures';
4+
import {
5+
envelopeRequestParser,
6+
shouldSkipFeatureFlagsTest,
7+
waitForErrorRequest,
8+
} from '../../../../../../utils/helpers';
9+
10+
sentryTest('GrowthBook onError: forked scopes are isolated', async ({ getLocalTestUrl, page }) => {
11+
if (shouldSkipFeatureFlagsTest()) {
12+
sentryTest.skip();
13+
}
14+
15+
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
16+
return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ id: 'test-id' }) });
17+
});
18+
19+
const url = await getLocalTestUrl({ testDir: __dirname, skipDsnRouteHandler: true });
20+
await page.goto(url);
21+
22+
const forkedReqPromise = waitForErrorRequest(page, event => !!event.tags?.isForked === true);
23+
const mainReqPromise = waitForErrorRequest(page, event => !!event.tags?.isForked === false);
24+
25+
await page.evaluate(() => {
26+
const Sentry = (window as any).Sentry;
27+
const errorButton = document.querySelector('#error') as HTMLButtonElement;
28+
const gb = new (window as any).GrowthBook();
29+
30+
gb.__setOn('shared', true);
31+
gb.__setOn('main', true);
32+
33+
gb.isOn('shared');
34+
35+
Sentry.withScope((scope: Scope) => {
36+
gb.__setOn('forked', true);
37+
gb.__setOn('shared', false);
38+
gb.isOn('forked');
39+
gb.isOn('shared');
40+
scope.setTag('isForked', true);
41+
errorButton.click();
42+
});
43+
44+
gb.isOn('main');
45+
Sentry.getCurrentScope().setTag('isForked', false);
46+
errorButton.click();
47+
return true;
48+
});
49+
50+
const forkedReq = await forkedReqPromise;
51+
const forkedEvent = envelopeRequestParser(forkedReq);
52+
53+
const mainReq = await mainReqPromise;
54+
const mainEvent = envelopeRequestParser(mainReq);
55+
56+
expect(forkedEvent.contexts?.flags?.values).toEqual([
57+
{ flag: 'forked', result: true },
58+
{ flag: 'shared', result: false },
59+
]);
60+
61+
expect(mainEvent.contexts?.flags?.values).toEqual([
62+
{ flag: 'shared', result: true },
63+
{ flag: 'main', result: true },
64+
]);
65+
});
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
window.GrowthBook = class {
4+
constructor() {
5+
this._onFlags = Object.create(null);
6+
this._featureValues = Object.create(null);
7+
}
8+
9+
isOn(featureKey) {
10+
return !!this._onFlags[featureKey];
11+
}
12+
13+
getFeatureValue(featureKey, defaultValue) {
14+
return Object.prototype.hasOwnProperty.call(this._featureValues, featureKey)
15+
? this._featureValues[featureKey]
16+
: defaultValue;
17+
}
18+
19+
__setOn(featureKey, value) {
20+
this._onFlags[featureKey] = !!value;
21+
}
22+
23+
__setFeatureValue(featureKey, value) {
24+
this._featureValues[featureKey] = value;
25+
}
26+
};
27+
28+
window.Sentry = Sentry;
29+
window.sentryGrowthBookIntegration = Sentry.growthbookIntegration({ growthbookClass: window.GrowthBook });
30+
31+
Sentry.init({
32+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
33+
sampleRate: 1.0,
34+
tracesSampleRate: 1.0,
35+
integrations: [
36+
window.sentryGrowthBookIntegration,
37+
Sentry.browserTracingIntegration({ instrumentNavigation: false, instrumentPageLoad: false }),
38+
],
39+
});
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
const btnStartSpan = document.getElementById('btnStartSpan');
2+
const btnEndSpan = document.getElementById('btnEndSpan');
3+
const btnStartNestedSpan = document.getElementById('btnStartNestedSpan');
4+
const btnEndNestedSpan = document.getElementById('btnEndNestedSpan');
5+
6+
window.withNestedSpans = callback => {
7+
window.Sentry.startSpan({ name: 'test-root-span' }, rootSpan => {
8+
window.traceId = rootSpan.spanContext().traceId;
9+
10+
window.Sentry.startSpan({ name: 'test-span' }, _span => {
11+
window.Sentry.startSpan({ name: 'test-nested-span' }, _nestedSpan => {
12+
callback();
13+
});
14+
});
15+
});
16+
};

0 commit comments

Comments
 (0)