From f82f85277955b9e2d1eda3598fea9862f27c5033 Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Thu, 13 Nov 2025 13:01:02 +0000 Subject: [PATCH 1/4] fix(node): Fix Spotlight configuration precedence to match specification The Spotlight configuration logic had a precedence bug where when 'spotlight: true' was set AND the 'SENTRY_SPOTLIGHT' env var contained a URL string, the SDK would use 'true' instead of the URL from the env var. According to the Spotlight specification, when 'spotlight: true' is set and the env var contains a URL, the URL should be used. Changes: - Fixed precedence logic in getClientOptions() to properly handle the case where config is 'true' and env var is a URL string - Added 12 comprehensive test cases covering all precedence scenarios - Reused existing envToBool() utility for env var parsing Fixes the issue where developers couldn't override the Spotlight URL via environment variable when using 'spotlight: true' in config. --- CHANGELOG.md | 2 +- packages/node-core/src/sdk/index.ts | 20 ++++- packages/node-core/test/sdk/init.test.ts | 104 +++++++++++++++++++++++ 3 files changed, 123 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5cf4d9b810f..0d9f35a931a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## Unreleased -- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +- fix(node): Fix Spotlight configuration precedence to match specification (#18160) ## 10.25.0 diff --git a/packages/node-core/src/sdk/index.ts b/packages/node-core/src/sdk/index.ts index d53f5d4faefb..0814ab401535 100644 --- a/packages/node-core/src/sdk/index.ts +++ b/packages/node-core/src/sdk/index.ts @@ -182,8 +182,24 @@ function getClientOptions( getDefaultIntegrationsImpl: (options: Options) => Integration[], ): NodeClientOptions { const release = getRelease(options.release); - const spotlight = - options.spotlight ?? envToBool(process.env.SENTRY_SPOTLIGHT, { strict: true }) ?? process.env.SENTRY_SPOTLIGHT; + + // Parse spotlight configuration with proper precedence per spec + let spotlight: boolean | string | undefined; + if (options.spotlight === false) { + spotlight = false; + } else if (typeof options.spotlight === 'string') { + spotlight = options.spotlight; + } else { + // options.spotlight is true or undefined + const envBool = envToBool(process.env.SENTRY_SPOTLIGHT, { strict: true }); + const envUrl = envBool === null && process.env.SENTRY_SPOTLIGHT ? process.env.SENTRY_SPOTLIGHT : undefined; + + spotlight = + options.spotlight === true + ? (envUrl ?? true) // true: use env URL if present, otherwise true + : (envBool ?? envUrl); // undefined: use env var (bool or URL) + } + const tracesSampleRate = getTracesSampleRate(options.tracesSampleRate); const mergedOptions = { diff --git a/packages/node-core/test/sdk/init.test.ts b/packages/node-core/test/sdk/init.test.ts index 4262bffb7cda..f6e4a403415e 100644 --- a/packages/node-core/test/sdk/init.test.ts +++ b/packages/node-core/test/sdk/init.test.ts @@ -216,6 +216,110 @@ describe('init()', () => { }), ); }); + + describe('spotlight configuration', () => { + it('enables spotlight with default URL from `SENTRY_SPOTLIGHT` env variable (truthy value)', () => { + process.env.SENTRY_SPOTLIGHT = 'true'; + + const client = init({ dsn: PUBLIC_DSN }); + + expect(client?.getOptions().spotlight).toBe(true); + expect(client?.getOptions().integrations.some(integration => integration.name === 'Spotlight')).toBe(true); + }); + + it('disables spotlight from `SENTRY_SPOTLIGHT` env variable (falsy value)', () => { + process.env.SENTRY_SPOTLIGHT = 'false'; + + const client = init({ dsn: PUBLIC_DSN }); + + expect(client?.getOptions().spotlight).toBe(false); + expect(client?.getOptions().integrations.some(integration => integration.name === 'Spotlight')).toBe(false); + }); + + it('enables spotlight with custom URL from `SENTRY_SPOTLIGHT` env variable', () => { + process.env.SENTRY_SPOTLIGHT = 'http://localhost:3000/stream'; + + const client = init({ dsn: PUBLIC_DSN }); + + expect(client?.getOptions().spotlight).toBe('http://localhost:3000/stream'); + expect(client?.getOptions().integrations.some(integration => integration.name === 'Spotlight')).toBe(true); + }); + + it('enables spotlight with default URL from config `true`', () => { + const client = init({ dsn: PUBLIC_DSN, spotlight: true }); + + expect(client?.getOptions().spotlight).toBe(true); + expect(client?.getOptions().integrations.some(integration => integration.name === 'Spotlight')).toBe(true); + }); + + it('disables spotlight from config `false`', () => { + const client = init({ dsn: PUBLIC_DSN, spotlight: false }); + + expect(client?.getOptions().spotlight).toBe(false); + expect(client?.getOptions().integrations.some(integration => integration.name === 'Spotlight')).toBe(false); + }); + + it('enables spotlight with custom URL from config', () => { + const client = init({ dsn: PUBLIC_DSN, spotlight: 'http://custom:8888/stream' }); + + expect(client?.getOptions().spotlight).toBe('http://custom:8888/stream'); + expect(client?.getOptions().integrations.some(integration => integration.name === 'Spotlight')).toBe(true); + }); + + it('config `false` overrides `SENTRY_SPOTLIGHT` env variable URL', () => { + process.env.SENTRY_SPOTLIGHT = 'http://localhost:3000/stream'; + + const client = init({ dsn: PUBLIC_DSN, spotlight: false }); + + expect(client?.getOptions().spotlight).toBe(false); + expect(client?.getOptions().integrations.some(integration => integration.name === 'Spotlight')).toBe(false); + }); + + it('config `false` overrides `SENTRY_SPOTLIGHT` env variable truthy value', () => { + process.env.SENTRY_SPOTLIGHT = 'true'; + + const client = init({ dsn: PUBLIC_DSN, spotlight: false }); + + expect(client?.getOptions().spotlight).toBe(false); + expect(client?.getOptions().integrations.some(integration => integration.name === 'Spotlight')).toBe(false); + }); + + it('config `false` with `SENTRY_SPOTLIGHT` env variable falsy value keeps spotlight disabled', () => { + process.env.SENTRY_SPOTLIGHT = 'false'; + + const client = init({ dsn: PUBLIC_DSN, spotlight: false }); + + expect(client?.getOptions().spotlight).toBe(false); + expect(client?.getOptions().integrations.some(integration => integration.name === 'Spotlight')).toBe(false); + }); + + it('config URL overrides `SENTRY_SPOTLIGHT` env variable URL', () => { + process.env.SENTRY_SPOTLIGHT = 'http://env:3000/stream'; + + const client = init({ dsn: PUBLIC_DSN, spotlight: 'http://config:8888/stream' }); + + expect(client?.getOptions().spotlight).toBe('http://config:8888/stream'); + expect(client?.getOptions().integrations.some(integration => integration.name === 'Spotlight')).toBe(true); + }); + + it('config `true` with env var URL uses env var URL', () => { + process.env.SENTRY_SPOTLIGHT = 'http://localhost:3000/stream'; + + const client = init({ dsn: PUBLIC_DSN, spotlight: true }); + + expect(client?.getOptions().spotlight).toBe('http://localhost:3000/stream'); + expect(client?.getOptions().integrations.some(integration => integration.name === 'Spotlight')).toBe(true); + }); + + it('config `true` with env var truthy value uses default URL', () => { + process.env.SENTRY_SPOTLIGHT = 'true'; + + const client = init({ dsn: PUBLIC_DSN, spotlight: true }); + + expect(client?.getOptions().spotlight).toBe(true); + expect(client?.getOptions().integrations.some(integration => integration.name === 'Spotlight')).toBe(true); + }); + }); }); }); From 738e214ca95feade21bf855e723987ea3404dd56 Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Thu, 13 Nov 2025 13:01:47 +0000 Subject: [PATCH 2/4] chore: Update CHANGELOG with correct PR number --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d9f35a931a7..09030a4dc82a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## Unreleased -- fix(node): Fix Spotlight configuration precedence to match specification (#18160) +- fix(node): Fix Spotlight configuration precedence to match specification (#18195) ## 10.25.0 From e2bff22add01c5d10f844790f2ee10677ef0fa00 Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Thu, 13 Nov 2025 13:48:43 +0000 Subject: [PATCH 3/4] Fix spotlight config test assertion (#18196) Before submitting a pull request, please take a look at our [Contributing](https://github.com/getsentry/sentry-javascript/blob/master/CONTRIBUTING.md) guidelines and verify: - [x] If you've added code that should be tested, please add tests. - [x] Ensure your code lints and the test suite passes (`yarn lint`) & (`yarn test`). Fixes a test failure where `process.env.SENTRY_SPOTLIGHT` was not cleaned up after a test, causing environment variable pollution for subsequent tests. The failing test expected `spotlight: true` to resolve to `true`, but due to the lingering environment variable, it received the URL from `SENTRY_SPOTLIGHT` instead. This PR adds an `afterEach` hook to the `spotlight configuration` describe block to ensure `process.env.SENTRY_SPOTLIGHT` is deleted after each test, preventing cross-test contamination. --- Open in
Cursor Open in Web Co-authored-by: Cursor Agent --- packages/node-core/test/sdk/init.test.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/node-core/test/sdk/init.test.ts b/packages/node-core/test/sdk/init.test.ts index f6e4a403415e..1332b89abcd6 100644 --- a/packages/node-core/test/sdk/init.test.ts +++ b/packages/node-core/test/sdk/init.test.ts @@ -218,6 +218,10 @@ describe('init()', () => { }); describe('spotlight configuration', () => { + afterEach(() => { + delete process.env.SENTRY_SPOTLIGHT; + }); + it('enables spotlight with default URL from `SENTRY_SPOTLIGHT` env variable (truthy value)', () => { process.env.SENTRY_SPOTLIGHT = 'true'; From 898bd23edc1c2348646d45afcd42ce666d2b434e Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Thu, 13 Nov 2025 15:29:52 +0000 Subject: [PATCH 4/4] bump size limit --- .size-limit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.size-limit.js b/.size-limit.js index 2d07afde52ab..4e929875dad5 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -240,7 +240,7 @@ module.exports = [ import: createImport('init'), ignore: [...builtinModules, ...nodePrefixedBuiltinModules], gzip: true, - limit: '158 KB', + limit: '160 KB', }, { name: '@sentry/node - without tracing',