From 89b801e140f648cb8baf62afe760ebeef8fbd57f Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Mon, 16 Feb 2026 09:38:47 +0100 Subject: [PATCH] feat(spotlight): Disable spotlight in production builds Spotlight integration is now automatically disabled in production builds (when __DEV__ is false), even if explicitly enabled via the spotlight option. This prevents accidental inclusion of the development-only Spotlight tool in production releases, which would be non-functional (requires local Sidecar server) and could impact performance. Changes: - Add __DEV__ check in getDefaultIntegrations() to block spotlight in production - Update spotlight option documentation to clarify automatic disabling - Add comprehensive tests for spotlight integration behavior - Advanced users can still manually add spotlightIntegration() if needed Related to PR #5667 which makes native spotlight debug-only on Android. Co-Authored-By: Claude Sonnet 4.5 --- packages/core/src/js/integrations/default.ts | 2 +- packages/core/src/js/options.ts | 5 +- .../integrations/defaultSpotlight.test.ts | 83 +++++++++++++++++++ 3 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 packages/core/test/integrations/defaultSpotlight.test.ts diff --git a/packages/core/src/js/integrations/default.ts b/packages/core/src/js/integrations/default.ts index 19009ab26b..15123d32a6 100644 --- a/packages/core/src/js/integrations/default.ts +++ b/packages/core/src/js/integrations/default.ts @@ -132,7 +132,7 @@ export function getDefaultIntegrations(options: ReactNativeClientOptions): Integ integrations.push(expoContextIntegration()); - if (options.spotlight) { + if (options.spotlight && __DEV__) { const sidecarUrl = typeof options.spotlight === 'string' ? options.spotlight : undefined; integrations.push(spotlightIntegration({ sidecarUrl })); } diff --git a/packages/core/src/js/options.ts b/packages/core/src/js/options.ts index 20f2e7207d..da89359323 100644 --- a/packages/core/src/js/options.ts +++ b/packages/core/src/js/options.ts @@ -184,7 +184,10 @@ export interface BaseReactNativeOptions { * * More details: https://spotlightjs.com/ * - * IMPORTANT: Only set this option to `true` while developing, not in production! + * NOTE: Spotlight is automatically disabled in production builds (when __DEV__ is false). + * If you need Spotlight in non-development environments, you must manually add + * spotlightIntegration() to your integrations array. However, this is not recommended + * as Spotlight requires a local Sidecar server and is designed for development only. */ spotlight?: boolean | string; diff --git a/packages/core/test/integrations/defaultSpotlight.test.ts b/packages/core/test/integrations/defaultSpotlight.test.ts new file mode 100644 index 0000000000..4f13a16263 --- /dev/null +++ b/packages/core/test/integrations/defaultSpotlight.test.ts @@ -0,0 +1,83 @@ +import type { Integration } from '@sentry/core'; +import { getDefaultIntegrations } from '../../src/js/integrations/default'; +import { spotlightIntegration } from '../../src/js/integrations/spotlight'; +import type { ReactNativeClientOptions } from '../../src/js/options'; +import { notWeb } from '../../src/js/utils/environment'; + +jest.mock('../../src/js/utils/environment', () => { + const actual = jest.requireActual('../../src/js/utils/environment'); + return { + ...actual, + notWeb: jest.fn(() => true), + }; +}); + +const spotlightIntegrationName = spotlightIntegration().name; + +describe('getDefaultIntegrations - spotlight integration', () => { + let originalDev: boolean | undefined; + + beforeEach(() => { + (notWeb as jest.Mock).mockReturnValue(true); + originalDev = (global as typeof globalThis & { __DEV__?: boolean }).__DEV__; + }); + + afterEach(() => { + (global as typeof globalThis & { __DEV__?: boolean }).__DEV__ = originalDev; + }); + + const createOptions = (overrides: Partial): ReactNativeClientOptions => { + return { + dsn: 'https://example.com/1', + enableNative: true, + ...overrides, + } as ReactNativeClientOptions; + }; + + const getIntegrationNames = (options: ReactNativeClientOptions): string[] => { + const integrations = getDefaultIntegrations(options); + return integrations.map((integration: Integration) => integration.name); + }; + + it('does not add spotlight integration when spotlight option is not set', () => { + (global as typeof globalThis & { __DEV__?: boolean }).__DEV__ = true; + const names = getIntegrationNames(createOptions({})); + + expect(names).not.toContain(spotlightIntegrationName); + }); + + it('adds spotlight integration when spotlight is true and __DEV__ is true', () => { + (global as typeof globalThis & { __DEV__?: boolean }).__DEV__ = true; + const names = getIntegrationNames(createOptions({ spotlight: true })); + + expect(names).toContain(spotlightIntegrationName); + }); + + it('adds spotlight integration when spotlight is a URL string and __DEV__ is true', () => { + (global as typeof globalThis & { __DEV__?: boolean }).__DEV__ = true; + const names = getIntegrationNames(createOptions({ spotlight: 'http://custom-url:8969/stream' })); + + expect(names).toContain(spotlightIntegrationName); + }); + + it('does not add spotlight integration when spotlight is true but __DEV__ is false', () => { + (global as typeof globalThis & { __DEV__?: boolean }).__DEV__ = false; + const names = getIntegrationNames(createOptions({ spotlight: true })); + + expect(names).not.toContain(spotlightIntegrationName); + }); + + it('does not add spotlight integration when spotlight is a URL but __DEV__ is false', () => { + (global as typeof globalThis & { __DEV__?: boolean }).__DEV__ = false; + const names = getIntegrationNames(createOptions({ spotlight: 'http://custom-url:8969/stream' })); + + expect(names).not.toContain(spotlightIntegrationName); + }); + + it('does not add spotlight integration when spotlight is false regardless of __DEV__', () => { + (global as typeof globalThis & { __DEV__?: boolean }).__DEV__ = true; + const names = getIntegrationNames(createOptions({ spotlight: false })); + + expect(names).not.toContain(spotlightIntegrationName); + }); +});