From 9d9663d41f034c99bcd6edf7c125b816a733869d Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Tue, 30 Dec 2025 15:39:35 +0300 Subject: [PATCH 01/44] feat(spotlight): Add SENTRY_SPOTLIGHT environment variable support for browser SDKs Enable Spotlight via SENTRY_SPOTLIGHT environment variable across browser frameworks with zero configuration for users. ## Changes ### Core Utilities (@sentry/core) - Move envToBool from node-core to @sentry/core for reuse - Add parseSpotlightEnvValue() and resolveSpotlightValue() implementing Spotlight spec precedence rules - Add unit tests for spotlight utilities ### Framework SDKs with Build-Time Injection - **Next.js**: webpack DefinePlugin + turbopack valueInjectionLoader - **Nuxt**: vite:extendConfig hook - **Astro**: updateConfig with vite define - **SvelteKit**: New makeSpotlightDefinePlugin() in sentrySvelteKit() ### Framework-Agnostic SDKs (React, Vue, Svelte, Solid) - Add __VITE_SPOTLIGHT_ENV__ placeholder replaced by Rollup - ESM: import.meta.env?.VITE_SENTRY_SPOTLIGHT (zero-config for Vite) - CJS: undefined (requires bundler config) ### Rollup Utils - Add makeViteSpotlightEnvReplacePlugin() for format-specific replacement Zero-config experience with 'spotlight run' for Next.js, Nuxt, Astro, SvelteKit, and Vite ESM users. --- dev-packages/rollup-utils/npmHelpers.mjs | 16 +- .../rollup-utils/plugins/npmPlugins.mjs | 18 +++ packages/astro/src/client/sdk.ts | 26 +++- packages/astro/src/integration/index.ts | 9 ++ packages/core/src/index.ts | 8 + packages/core/src/utils/envToBool.ts | 38 +++++ packages/core/src/utils/spotlight.ts | 82 +++++++++++ .../core/test/lib/utils/envToBool.test.ts | 50 +++++++ .../core/test/lib/utils/spotlight.test.ts | 138 ++++++++++++++++++ packages/nextjs/src/client/index.ts | 20 ++- .../turbopack/generateValueInjectionRules.ts | 5 + packages/nextjs/src/config/webpack.ts | 9 ++ packages/node-core/src/sdk/index.ts | 21 +-- packages/node-core/src/utils/envToBool.ts | 45 +----- packages/nuxt/src/client/sdk.ts | 26 +++- packages/nuxt/src/module.ts | 8 + packages/react/src/sdk.ts | 21 ++- packages/solid/src/sdk.ts | 21 ++- packages/svelte/src/sdk.ts | 22 ++- packages/sveltekit/src/client/sdk.ts | 26 +++- .../sveltekit/src/vite/sentryVitePlugins.ts | 23 +++ packages/vue/src/sdk.ts | 21 ++- 22 files changed, 588 insertions(+), 65 deletions(-) create mode 100644 packages/core/src/utils/envToBool.ts create mode 100644 packages/core/src/utils/spotlight.ts create mode 100644 packages/core/test/lib/utils/envToBool.test.ts create mode 100644 packages/core/test/lib/utils/spotlight.test.ts diff --git a/dev-packages/rollup-utils/npmHelpers.mjs b/dev-packages/rollup-utils/npmHelpers.mjs index d5f7428b992d..ca48bbc9ac2e 100644 --- a/dev-packages/rollup-utils/npmHelpers.mjs +++ b/dev-packages/rollup-utils/npmHelpers.mjs @@ -19,6 +19,7 @@ import { makeProductionReplacePlugin, makeRrwebBuildPlugin, makeSucrasePlugin, + makeViteSpotlightEnvReplacePlugin, } from './plugins/index.mjs'; import { makePackageNodeEsm } from './plugins/make-esm-plugin.mjs'; import { mergePlugins } from './utils.mjs'; @@ -121,13 +122,19 @@ export function makeNPMConfigVariants(baseConfig, options = {}) { if (emitCjs) { if (splitDevProd) { - variantSpecificConfigs.push({ output: { format: 'cjs', dir: path.join(baseConfig.output.dir, 'cjs/dev') } }); + variantSpecificConfigs.push({ + output: { format: 'cjs', dir: path.join(baseConfig.output.dir, 'cjs/dev') }, + plugins: [makeViteSpotlightEnvReplacePlugin('cjs')], + }); variantSpecificConfigs.push({ output: { format: 'cjs', dir: path.join(baseConfig.output.dir, 'cjs/prod') }, - plugins: [makeProductionReplacePlugin()], + plugins: [makeProductionReplacePlugin(), makeViteSpotlightEnvReplacePlugin('cjs')], }); } else { - variantSpecificConfigs.push({ output: { format: 'cjs', dir: path.join(baseConfig.output.dir, 'cjs') } }); + variantSpecificConfigs.push({ + output: { format: 'cjs', dir: path.join(baseConfig.output.dir, 'cjs') }, + plugins: [makeViteSpotlightEnvReplacePlugin('cjs')], + }); } } @@ -139,6 +146,7 @@ export function makeNPMConfigVariants(baseConfig, options = {}) { dir: path.join(baseConfig.output.dir, 'esm/dev'), plugins: [makePackageNodeEsm()], }, + plugins: [makeViteSpotlightEnvReplacePlugin('esm')], }); variantSpecificConfigs.push({ output: { @@ -146,6 +154,7 @@ export function makeNPMConfigVariants(baseConfig, options = {}) { dir: path.join(baseConfig.output.dir, 'esm/prod'), plugins: [makeProductionReplacePlugin(), makePackageNodeEsm()], }, + plugins: [makeViteSpotlightEnvReplacePlugin('esm')], }); } else { variantSpecificConfigs.push({ @@ -154,6 +163,7 @@ export function makeNPMConfigVariants(baseConfig, options = {}) { dir: path.join(baseConfig.output.dir, 'esm'), plugins: [makePackageNodeEsm()], }, + plugins: [makeViteSpotlightEnvReplacePlugin('esm')], }); } } diff --git a/dev-packages/rollup-utils/plugins/npmPlugins.mjs b/dev-packages/rollup-utils/plugins/npmPlugins.mjs index 7f08873f1c80..02be7267a7c5 100644 --- a/dev-packages/rollup-utils/plugins/npmPlugins.mjs +++ b/dev-packages/rollup-utils/plugins/npmPlugins.mjs @@ -168,6 +168,24 @@ export function makeRrwebBuildPlugin({ excludeShadowDom, excludeIframe } = {}) { }); } +/** + * Creates a plugin to replace __VITE_SPOTLIGHT_ENV__ with the appropriate value based on output format. + * - ESM: import.meta.env?.VITE_SENTRY_SPOTLIGHT (allows Vite to provide zero-config Spotlight support) + * - CJS: undefined (import.meta is not available in CJS) + * + * @param format The output format ('esm' or 'cjs') + * @returns A `@rollup/plugin-replace` instance. + */ +export function makeViteSpotlightEnvReplacePlugin(format) { + const value = format === 'esm' ? 'import.meta.env?.VITE_SENTRY_SPOTLIGHT' : 'undefined'; + return replace({ + preventAssignment: true, + values: { + __VITE_SPOTLIGHT_ENV__: value, + }, + }); +} + /** * Plugin that uploads bundle analysis to codecov. * diff --git a/packages/astro/src/client/sdk.ts b/packages/astro/src/client/sdk.ts index 21c5770f255f..ef61ab47d595 100644 --- a/packages/astro/src/client/sdk.ts +++ b/packages/astro/src/client/sdk.ts @@ -1,9 +1,26 @@ import type { BrowserOptions } from '@sentry/browser'; import { getDefaultIntegrations as getBrowserDefaultIntegrations, init as initBrowserSdk } from '@sentry/browser'; import type { Client, Integration } from '@sentry/core'; -import { applySdkMetadata } from '@sentry/core'; +import { applySdkMetadata, parseSpotlightEnvValue, resolveSpotlightValue } from '@sentry/core'; import { browserTracingIntegration } from './browserTracingIntegration'; +// Type for spotlight-related env vars injected by Vite +interface SpotlightEnv { + PUBLIC_SENTRY_SPOTLIGHT?: string; + SENTRY_SPOTLIGHT?: string; +} + +// Access import.meta.env in a way that works with TypeScript +// Vite replaces this at build time +function getSpotlightEnv(): SpotlightEnv { + try { + // @ts-expect-error - import.meta.env is injected by Vite + return typeof import.meta !== 'undefined' && import.meta.env ? (import.meta.env as SpotlightEnv) : {}; + } catch { + return {}; + } +} + // Tree-shakable guard to remove all code related to tracing declare const __SENTRY_TRACING__: boolean; @@ -13,9 +30,16 @@ declare const __SENTRY_TRACING__: boolean; * @param options Configuration options for the SDK. */ export function init(options: BrowserOptions): Client | undefined { + // Read PUBLIC_SENTRY_SPOTLIGHT (set by spotlight run, Astro uses PUBLIC_ prefix) + // OR fallback to SENTRY_SPOTLIGHT (injected by our integration) + const spotlightEnv = getSpotlightEnv(); + const spotlightEnvRaw = spotlightEnv.PUBLIC_SENTRY_SPOTLIGHT || spotlightEnv.SENTRY_SPOTLIGHT; + const spotlightEnvValue = parseSpotlightEnvValue(spotlightEnvRaw); + const opts = { defaultIntegrations: getDefaultIntegrations(options), ...options, + spotlight: resolveSpotlightValue(options.spotlight, spotlightEnvValue), }; applySdkMetadata(opts, 'astro', ['astro', 'browser']); diff --git a/packages/astro/src/integration/index.ts b/packages/astro/src/integration/index.ts index 86f2f3f03bde..ce06d1f29558 100644 --- a/packages/astro/src/integration/index.ts +++ b/packages/astro/src/integration/index.ts @@ -179,6 +179,15 @@ export const sentryAstro = (options: SentryOptions = {}): AstroIntegration => { } } + // Inject SENTRY_SPOTLIGHT env var for client bundles (fallback for manual setup without PUBLIC_ prefix) + updateConfig({ + vite: { + define: { + 'import.meta.env.SENTRY_SPOTLIGHT': JSON.stringify(process.env.SENTRY_SPOTLIGHT || ''), + }, + }, + }); + const isSSR = config && (config.output === 'server' || config.output === 'hybrid'); const shouldAddMiddleware = sdkEnabled.server && autoInstrumentation?.requestHandler !== false; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index e18ea294f182..9daaa3f70930 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -69,6 +69,14 @@ export { hasSpansEnabled } from './utils/hasSpansEnabled'; export { isSentryRequestUrl } from './utils/isSentryRequestUrl'; export { handleCallbackErrors } from './utils/handleCallbackErrors'; export { parameterize, fmt } from './utils/parameterize'; +export { + envToBool, + FALSY_ENV_VALUES, + TRUTHY_ENV_VALUES, +} from './utils/envToBool'; +export type { BoolCastOptions, StrictBoolCast, LooseBoolCast } from './utils/envToBool'; +export { parseSpotlightEnvValue, resolveSpotlightValue } from './utils/spotlight'; +export type { SpotlightConnectionOptions } from './utils/spotlight'; export { addAutoIpAddressToSession } from './utils/ipAddress'; // eslint-disable-next-line deprecation/deprecation diff --git a/packages/core/src/utils/envToBool.ts b/packages/core/src/utils/envToBool.ts new file mode 100644 index 000000000000..f78d05bb380c --- /dev/null +++ b/packages/core/src/utils/envToBool.ts @@ -0,0 +1,38 @@ +export const FALSY_ENV_VALUES = new Set(['false', 'f', 'n', 'no', 'off', '0']); +export const TRUTHY_ENV_VALUES = new Set(['true', 't', 'y', 'yes', 'on', '1']); + +export type StrictBoolCast = { + strict: true; +}; + +export type LooseBoolCast = { + strict?: false; +}; + +export type BoolCastOptions = StrictBoolCast | LooseBoolCast; + +export function envToBool(value: unknown, options?: LooseBoolCast): boolean; +export function envToBool(value: unknown, options: StrictBoolCast): boolean | null; +export function envToBool(value: unknown, options?: BoolCastOptions): boolean | null; +/** + * A helper function which casts an ENV variable value to `true` or `false` using the constants defined above. + * In strict mode, it may return `null` if the value doesn't match any of the predefined values. + * + * @param value The value of the env variable + * @param options -- Only has `strict` key for now, which requires a strict match for `true` in TRUTHY_ENV_VALUES + * @returns true/false if the lowercase value matches the predefined values above. If not, null in strict mode, + * and Boolean(value) in loose mode. + */ +export function envToBool(value: unknown, options?: BoolCastOptions): boolean | null { + const normalized = String(value).toLowerCase(); + + if (FALSY_ENV_VALUES.has(normalized)) { + return false; + } + + if (TRUTHY_ENV_VALUES.has(normalized)) { + return true; + } + + return options?.strict ? null : Boolean(value); +} diff --git a/packages/core/src/utils/spotlight.ts b/packages/core/src/utils/spotlight.ts new file mode 100644 index 000000000000..b0a76676364d --- /dev/null +++ b/packages/core/src/utils/spotlight.ts @@ -0,0 +1,82 @@ +import { debug } from './debug-logger'; +import { envToBool } from './envToBool'; + +/** + * Spotlight configuration option type. + * - `undefined` - not configured + * - `false` - explicitly disabled + * - `true` - enabled with default URL (http://localhost:8969/stream) + * - `string` - enabled with custom URL + */ +export type SpotlightConnectionOptions = boolean | string | undefined; + +/** + * Parses a SENTRY_SPOTLIGHT environment variable value. + * + * Per the Spotlight spec: + * - Truthy values ("true", "t", "y", "yes", "on", "1") -> true + * - Falsy values ("false", "f", "n", "no", "off", "0") -> false + * - Any other non-empty string -> treated as URL + * - Empty string or undefined -> undefined + * + * @see https://develop.sentry.dev/sdk/expected-features/spotlight.md + */ +export function parseSpotlightEnvValue(envValue: string | undefined): SpotlightConnectionOptions { + if (envValue === undefined || envValue === '') { + return undefined; + } + + // Try strict boolean parsing first + const boolValue = envToBool(envValue, { strict: true }); + if (boolValue !== null) { + return boolValue; + } + + // Not a boolean - treat as URL + return envValue; +} + +/** + * Resolves the final Spotlight configuration value based on the config option and environment variable. + * + * Precedence rules (per spec): + * 1. Config `false` -> DISABLED (ignore env var, log warning) + * 2. Config URL string -> USE CONFIG URL (log warning if env var also set to URL) + * 3. Config `true` + Env URL -> USE ENV VAR URL (this is the key case!) + * 4. Config `true` + Env bool/undefined -> USE DEFAULT URL (true) + * 5. Config `undefined` -> USE ENV VAR VALUE + * + * @see https://develop.sentry.dev/sdk/expected-features/spotlight.md + */ +export function resolveSpotlightValue( + optionValue: SpotlightConnectionOptions, + envValue: SpotlightConnectionOptions, +): SpotlightConnectionOptions { + // Case 1: Config explicitly disables Spotlight + if (optionValue === false) { + if (envValue !== undefined) { + // Per spec: MUST warn when config false ignores env var + debug.warn('Spotlight disabled via config, ignoring SENTRY_SPOTLIGHT environment variable'); + } + return false; + } + + // Case 2: Config provides explicit URL + if (typeof optionValue === 'string') { + if (typeof envValue === 'string') { + // Per spec: MUST warn when config URL overrides env var URL + debug.warn('Spotlight config URL takes precedence over SENTRY_SPOTLIGHT environment variable'); + } + return optionValue; + } + + // Case 3 & 4: Config is true - enable Spotlight + if (optionValue === true) { + // Per spec: If config true AND env var is URL, MUST use env var URL + // This enables `spotlight: true` in code while `spotlight run` provides the URL + return typeof envValue === 'string' ? envValue : true; + } + + // Case 5: Config undefined - fully defer to env var + return envValue; +} diff --git a/packages/core/test/lib/utils/envToBool.test.ts b/packages/core/test/lib/utils/envToBool.test.ts new file mode 100644 index 000000000000..fefde285762e --- /dev/null +++ b/packages/core/test/lib/utils/envToBool.test.ts @@ -0,0 +1,50 @@ +import { describe, expect, it } from 'vitest'; +import { envToBool, FALSY_ENV_VALUES, TRUTHY_ENV_VALUES } from '../../../src/utils/envToBool'; + +describe('envToBool', () => { + describe('TRUTHY_ENV_VALUES', () => { + it.each([...TRUTHY_ENV_VALUES])('returns true for "%s"', value => { + expect(envToBool(value)).toBe(true); + expect(envToBool(value, { strict: true })).toBe(true); + }); + + it('handles case insensitivity', () => { + expect(envToBool('TRUE')).toBe(true); + expect(envToBool('True')).toBe(true); + expect(envToBool('YES')).toBe(true); + expect(envToBool('Yes')).toBe(true); + }); + }); + + describe('FALSY_ENV_VALUES', () => { + it.each([...FALSY_ENV_VALUES])('returns false for "%s"', value => { + expect(envToBool(value)).toBe(false); + expect(envToBool(value, { strict: true })).toBe(false); + }); + + it('handles case insensitivity', () => { + expect(envToBool('FALSE')).toBe(false); + expect(envToBool('False')).toBe(false); + expect(envToBool('NO')).toBe(false); + expect(envToBool('No')).toBe(false); + }); + }); + + describe('non-matching values', () => { + it('returns null in strict mode for non-matching values', () => { + expect(envToBool('http://localhost:8969', { strict: true })).toBe(null); + expect(envToBool('random', { strict: true })).toBe(null); + expect(envToBool('', { strict: true })).toBe(null); + }); + + it('returns Boolean(value) in loose mode for non-matching values', () => { + expect(envToBool('http://localhost:8969')).toBe(true); // truthy string + expect(envToBool('random')).toBe(true); // truthy string + expect(envToBool('')).toBe(false); // falsy empty string + }); + + it('defaults to loose mode when options not provided', () => { + expect(envToBool('http://localhost:8969')).toBe(true); + }); + }); +}); diff --git a/packages/core/test/lib/utils/spotlight.test.ts b/packages/core/test/lib/utils/spotlight.test.ts new file mode 100644 index 000000000000..0ca19eb83ae6 --- /dev/null +++ b/packages/core/test/lib/utils/spotlight.test.ts @@ -0,0 +1,138 @@ +import { describe, expect, it, vi, afterEach, beforeEach } from 'vitest'; +import * as debugLogger from '../../../src/utils/debug-logger'; +import { parseSpotlightEnvValue, resolveSpotlightValue } from '../../../src/utils/spotlight'; + +describe('parseSpotlightEnvValue', () => { + it('returns undefined for undefined input', () => { + expect(parseSpotlightEnvValue(undefined)).toBe(undefined); + }); + + it('returns undefined for empty string', () => { + expect(parseSpotlightEnvValue('')).toBe(undefined); + }); + + describe('truthy values', () => { + it.each(['true', 'True', 'TRUE', 't', 'T', 'y', 'Y', 'yes', 'Yes', 'YES', 'on', 'On', 'ON', '1'])( + 'returns true for "%s"', + value => { + expect(parseSpotlightEnvValue(value)).toBe(true); + }, + ); + }); + + describe('falsy values', () => { + it.each(['false', 'False', 'FALSE', 'f', 'F', 'n', 'N', 'no', 'No', 'NO', 'off', 'Off', 'OFF', '0'])( + 'returns false for "%s"', + value => { + expect(parseSpotlightEnvValue(value)).toBe(false); + }, + ); + }); + + describe('URL values', () => { + it('treats non-boolean strings as URLs', () => { + expect(parseSpotlightEnvValue('http://localhost:8969/stream')).toBe('http://localhost:8969/stream'); + }); + + it('treats arbitrary strings as URLs', () => { + expect(parseSpotlightEnvValue('some-custom-url')).toBe('some-custom-url'); + }); + + it('treats port-only values as URLs', () => { + // '8080' is not in the truthy/falsy lists, so it's treated as a URL + expect(parseSpotlightEnvValue('8080')).toBe('8080'); + }); + }); +}); + +describe('resolveSpotlightValue', () => { + let warnSpy: ReturnType; + + beforeEach(() => { + warnSpy = vi.spyOn(debugLogger.debug, 'warn').mockImplementation(() => {}); + }); + + afterEach(() => { + warnSpy.mockRestore(); + }); + + describe('Case 1: Config false - DISABLED', () => { + it('returns false and ignores env var', () => { + expect(resolveSpotlightValue(false, 'http://localhost:8969/stream')).toBe(false); + }); + + it('logs warning when env var is set', () => { + resolveSpotlightValue(false, 'http://localhost:8969/stream'); + expect(warnSpy).toHaveBeenCalledWith( + 'Spotlight disabled via config, ignoring SENTRY_SPOTLIGHT environment variable', + ); + }); + + it('does not log warning when env var is undefined', () => { + resolveSpotlightValue(false, undefined); + expect(warnSpy).not.toHaveBeenCalled(); + }); + }); + + describe('Case 2: Config URL string - USE CONFIG URL', () => { + it('returns config URL', () => { + expect(resolveSpotlightValue('http://config-url:8080', 'http://env-url:9090')).toBe('http://config-url:8080'); + }); + + it('logs warning when env var is also a URL', () => { + resolveSpotlightValue('http://config-url:8080', 'http://env-url:9090'); + expect(warnSpy).toHaveBeenCalledWith( + 'Spotlight config URL takes precedence over SENTRY_SPOTLIGHT environment variable', + ); + }); + + it('does not log warning when env var is not a URL', () => { + resolveSpotlightValue('http://config-url:8080', true); + expect(warnSpy).not.toHaveBeenCalled(); + }); + + it('does not log warning when env var is undefined', () => { + resolveSpotlightValue('http://config-url:8080', undefined); + expect(warnSpy).not.toHaveBeenCalled(); + }); + }); + + describe('Case 3: Config true + Env URL - USE ENV VAR URL (key case!)', () => { + it('returns env var URL when config is true', () => { + expect(resolveSpotlightValue(true, 'http://localhost:8969/stream')).toBe('http://localhost:8969/stream'); + }); + }); + + describe('Case 4: Config true + Env bool/undefined - USE DEFAULT URL', () => { + it('returns true when env var is true', () => { + expect(resolveSpotlightValue(true, true)).toBe(true); + }); + + it('returns true when env var is false', () => { + // Config true wins over env false - this is the expected behavior per precedence + expect(resolveSpotlightValue(true, false)).toBe(true); + }); + + it('returns true when env var is undefined', () => { + expect(resolveSpotlightValue(true, undefined)).toBe(true); + }); + }); + + describe('Case 5: Config undefined - USE ENV VAR VALUE', () => { + it('returns env var URL', () => { + expect(resolveSpotlightValue(undefined, 'http://localhost:8969/stream')).toBe('http://localhost:8969/stream'); + }); + + it('returns env var true', () => { + expect(resolveSpotlightValue(undefined, true)).toBe(true); + }); + + it('returns env var false', () => { + expect(resolveSpotlightValue(undefined, false)).toBe(false); + }); + + it('returns undefined when env var is undefined', () => { + expect(resolveSpotlightValue(undefined, undefined)).toBe(undefined); + }); + }); +}); diff --git a/packages/nextjs/src/client/index.ts b/packages/nextjs/src/client/index.ts index 07d1ee5c4e84..c3b8662b5468 100644 --- a/packages/nextjs/src/client/index.ts +++ b/packages/nextjs/src/client/index.ts @@ -2,7 +2,15 @@ // can be removed once following issue is fixed: https://github.com/import-js/eslint-plugin-import/issues/703 /* eslint-disable import/export */ import type { Client, EventProcessor, Integration } from '@sentry/core'; -import { addEventProcessor, applySdkMetadata, consoleSandbox, getGlobalScope, GLOBAL_OBJ } from '@sentry/core'; +import { + addEventProcessor, + applySdkMetadata, + consoleSandbox, + getGlobalScope, + GLOBAL_OBJ, + parseSpotlightEnvValue, + resolveSpotlightValue, +} from '@sentry/core'; import type { BrowserOptions } from '@sentry/react'; import { getDefaultIntegrations as getReactDefaultIntegrations, init as reactInit } from '@sentry/react'; import { devErrorSymbolicationEventProcessor } from '../common/devErrorSymbolicationEventProcessor'; @@ -31,6 +39,7 @@ const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & { _sentryBasePath?: string; _sentryRelease?: string; _experimentalThirdPartyOriginStackFrames?: string; + _sentrySpotlight?: string; // For turbopack fallback }; // Treeshakable guard to remove all code related to tracing @@ -54,11 +63,20 @@ export function init(options: BrowserOptions): Client | undefined { removeIsrSsgTraceMetaTags(); } + // Read NEXT_PUBLIC_SENTRY_SPOTLIGHT (set by spotlight run, works with both bundlers) + // OR fallback to SENTRY_SPOTLIGHT (webpack: process.env, turbopack: globalThis) + const spotlightEnvRaw = + process.env.NEXT_PUBLIC_SENTRY_SPOTLIGHT || + process.env.SENTRY_SPOTLIGHT || + globalWithInjectedValues._sentrySpotlight; + const spotlightEnvValue = parseSpotlightEnvValue(spotlightEnvRaw); + const opts = { environment: getVercelEnv(true) || process.env.NODE_ENV, defaultIntegrations: getDefaultIntegrations(options), release: process.env._sentryRelease || globalWithInjectedValues._sentryRelease, ...options, + spotlight: resolveSpotlightValue(options.spotlight, spotlightEnvValue), } satisfies BrowserOptions; applyTunnelRouteOption(opts); diff --git a/packages/nextjs/src/config/turbopack/generateValueInjectionRules.ts b/packages/nextjs/src/config/turbopack/generateValueInjectionRules.ts index 2cf96b5f5ad7..7be21cc5ac9c 100644 --- a/packages/nextjs/src/config/turbopack/generateValueInjectionRules.ts +++ b/packages/nextjs/src/config/turbopack/generateValueInjectionRules.ts @@ -28,6 +28,11 @@ export function generateValueInjectionRules({ clientValues._sentryRouteManifest = JSON.stringify(routeManifest); } + // Inject SENTRY_SPOTLIGHT for client (fallback for manual setup without NEXT_PUBLIC_ prefix) + if (process.env.SENTRY_SPOTLIGHT) { + clientValues._sentrySpotlight = process.env.SENTRY_SPOTLIGHT; + } + // Inject tunnel route path for both client and server if (tunnelPath) { isomorphicValues._sentryRewritesTunnelPath = tunnelPath; diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index 7ae5b6859330..04483d9dc911 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -440,6 +440,15 @@ export function constructWebpackConfigFunction({ }), ); + // Inject SENTRY_SPOTLIGHT env var for client bundles (fallback for manual setup without NEXT_PUBLIC_ prefix) + if (!buildContext.isServer) { + newConfig.plugins.push( + new buildContext.webpack.DefinePlugin({ + 'process.env.SENTRY_SPOTLIGHT': JSON.stringify(process.env.SENTRY_SPOTLIGHT || ''), + }), + ); + } + return newConfig; }; } diff --git a/packages/node-core/src/sdk/index.ts b/packages/node-core/src/sdk/index.ts index 0814ab401535..b216fc91caea 100644 --- a/packages/node-core/src/sdk/index.ts +++ b/packages/node-core/src/sdk/index.ts @@ -4,6 +4,7 @@ import { consoleIntegration, consoleSandbox, debug, + envToBool, functionToStringIntegration, getCurrentScope, getIntegrationsToSetup, @@ -11,8 +12,10 @@ import { hasSpansEnabled, inboundFiltersIntegration, linkedErrorsIntegration, + parseSpotlightEnvValue, propagationContextFromHeaders, requestDataIntegration, + resolveSpotlightValue, stackParserFromStackParserOptions, } from '@sentry/core'; import { @@ -37,7 +40,6 @@ import { systemErrorIntegration } from '../integrations/systemError'; import { makeNodeTransport } from '../transports'; import type { NodeClientOptions, NodeOptions } from '../types'; import { isCjs } from '../utils/detection'; -import { envToBool } from '../utils/envToBool'; import { defaultStackParser, getSentryRelease } from './api'; import { NodeClient } from './client'; import { initializeEsmLoader } from './esmLoader'; @@ -184,21 +186,8 @@ function getClientOptions( const release = getRelease(options.release); // 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 spotlightEnvValue = parseSpotlightEnvValue(process.env.SENTRY_SPOTLIGHT); + const spotlight = resolveSpotlightValue(options.spotlight, spotlightEnvValue); const tracesSampleRate = getTracesSampleRate(options.tracesSampleRate); diff --git a/packages/node-core/src/utils/envToBool.ts b/packages/node-core/src/utils/envToBool.ts index f78d05bb380c..a9f01ed8bee5 100644 --- a/packages/node-core/src/utils/envToBool.ts +++ b/packages/node-core/src/utils/envToBool.ts @@ -1,38 +1,7 @@ -export const FALSY_ENV_VALUES = new Set(['false', 'f', 'n', 'no', 'off', '0']); -export const TRUTHY_ENV_VALUES = new Set(['true', 't', 'y', 'yes', 'on', '1']); - -export type StrictBoolCast = { - strict: true; -}; - -export type LooseBoolCast = { - strict?: false; -}; - -export type BoolCastOptions = StrictBoolCast | LooseBoolCast; - -export function envToBool(value: unknown, options?: LooseBoolCast): boolean; -export function envToBool(value: unknown, options: StrictBoolCast): boolean | null; -export function envToBool(value: unknown, options?: BoolCastOptions): boolean | null; -/** - * A helper function which casts an ENV variable value to `true` or `false` using the constants defined above. - * In strict mode, it may return `null` if the value doesn't match any of the predefined values. - * - * @param value The value of the env variable - * @param options -- Only has `strict` key for now, which requires a strict match for `true` in TRUTHY_ENV_VALUES - * @returns true/false if the lowercase value matches the predefined values above. If not, null in strict mode, - * and Boolean(value) in loose mode. - */ -export function envToBool(value: unknown, options?: BoolCastOptions): boolean | null { - const normalized = String(value).toLowerCase(); - - if (FALSY_ENV_VALUES.has(normalized)) { - return false; - } - - if (TRUTHY_ENV_VALUES.has(normalized)) { - return true; - } - - return options?.strict ? null : Boolean(value); -} +// Re-export from core for backwards compatibility +export { + envToBool, + FALSY_ENV_VALUES, + TRUTHY_ENV_VALUES, +} from '@sentry/core'; +export type { BoolCastOptions, StrictBoolCast, LooseBoolCast } from '@sentry/core'; diff --git a/packages/nuxt/src/client/sdk.ts b/packages/nuxt/src/client/sdk.ts index 5db856dae689..64837c827cb6 100644 --- a/packages/nuxt/src/client/sdk.ts +++ b/packages/nuxt/src/client/sdk.ts @@ -1,18 +1,42 @@ import { getDefaultIntegrations as getBrowserDefaultIntegrations, init as initBrowser } from '@sentry/browser'; import type { Client } from '@sentry/core'; -import { applySdkMetadata } from '@sentry/core'; +import { applySdkMetadata, parseSpotlightEnvValue, resolveSpotlightValue } from '@sentry/core'; import type { SentryNuxtClientOptions } from '../common/types'; +// Type for spotlight-related env vars injected by Vite +interface SpotlightEnv { + VITE_SENTRY_SPOTLIGHT?: string; + SENTRY_SPOTLIGHT?: string; +} + +// Access import.meta.env in a way that works with TypeScript +// Vite replaces this at build time +function getSpotlightEnv(): SpotlightEnv { + try { + // @ts-expect-error - import.meta.env is injected by Vite + return typeof import.meta !== 'undefined' && import.meta.env ? (import.meta.env as SpotlightEnv) : {}; + } catch { + return {}; + } +} + /** * Initializes the client-side of the Nuxt SDK * * @param options Configuration options for the SDK. */ export function init(options: SentryNuxtClientOptions): Client | undefined { + // Read VITE_SENTRY_SPOTLIGHT (set by spotlight run, auto-exposed by Vite) + // OR fallback to SENTRY_SPOTLIGHT (injected by our module) + const spotlightEnv = getSpotlightEnv(); + const spotlightEnvRaw = spotlightEnv.VITE_SENTRY_SPOTLIGHT || spotlightEnv.SENTRY_SPOTLIGHT; + const spotlightEnvValue = parseSpotlightEnvValue(spotlightEnvRaw); + const sentryOptions = { /* BrowserTracing is added later with the Nuxt client plugin */ defaultIntegrations: [...getBrowserDefaultIntegrations(options)], ...options, + spotlight: resolveSpotlightValue(options.spotlight, spotlightEnvValue), }; applySdkMetadata(sentryOptions, 'nuxt', ['nuxt', 'vue']); diff --git a/packages/nuxt/src/module.ts b/packages/nuxt/src/module.ts index 3656eac56e63..7eac82005620 100644 --- a/packages/nuxt/src/module.ts +++ b/packages/nuxt/src/module.ts @@ -107,6 +107,14 @@ export default defineNuxtModule({ addOTelCommonJSImportAlias(nuxt); + // Inject SENTRY_SPOTLIGHT env var for client bundles (fallback for manual setup without VITE_ prefix) + nuxt.hook('vite:extendConfig', (viteConfig, env) => { + if (env.isClient) { + viteConfig.define = viteConfig.define || {}; + viteConfig.define['import.meta.env.SENTRY_SPOTLIGHT'] = JSON.stringify(process.env.SENTRY_SPOTLIGHT || ''); + } + }); + const pagesDataTemplate = addTemplate({ filename: 'sentry--nuxt-pages-data.mjs', // Initial empty array (later filled in pages:extend hook) diff --git a/packages/react/src/sdk.ts b/packages/react/src/sdk.ts index 844bc30f1785..214c76f2ecec 100644 --- a/packages/react/src/sdk.ts +++ b/packages/react/src/sdk.ts @@ -1,9 +1,14 @@ import type { BrowserOptions } from '@sentry/browser'; import { init as browserInit, setContext } from '@sentry/browser'; import type { Client } from '@sentry/core'; -import { applySdkMetadata } from '@sentry/core'; +import { applySdkMetadata, parseSpotlightEnvValue, resolveSpotlightValue } from '@sentry/core'; import { version } from 'react'; +// Build-time placeholder - Rollup replaces per output format +// ESM: import.meta.env?.VITE_SENTRY_SPOTLIGHT (zero-config for Vite) +// CJS: undefined +declare const __VITE_SPOTLIGHT_ENV__: string | undefined; + /** * Inits the React SDK */ @@ -12,6 +17,20 @@ export function init(options: BrowserOptions): Client | undefined { ...options, }; + // Check for spotlight env vars: + // 1. process.env.SENTRY_SPOTLIGHT (all bundlers, requires config) + // 2. process.env.VITE_SENTRY_SPOTLIGHT (all bundlers, requires config) + // 3. import.meta.env.VITE_SENTRY_SPOTLIGHT (ESM only, zero-config for Vite!) + const spotlightEnvRaw = + (typeof process !== 'undefined' && (process.env?.SENTRY_SPOTLIGHT || process.env?.VITE_SENTRY_SPOTLIGHT)) || + (typeof __VITE_SPOTLIGHT_ENV__ !== 'undefined' && __VITE_SPOTLIGHT_ENV__) || + undefined; + + if (spotlightEnvRaw) { + const envValue = parseSpotlightEnvValue(spotlightEnvRaw); + opts.spotlight = resolveSpotlightValue(options.spotlight, envValue); + } + applySdkMetadata(opts, 'react'); setContext('react', { version }); return browserInit(opts); diff --git a/packages/solid/src/sdk.ts b/packages/solid/src/sdk.ts index 9968b0ace8f0..d298f1274c8e 100644 --- a/packages/solid/src/sdk.ts +++ b/packages/solid/src/sdk.ts @@ -1,7 +1,12 @@ import type { BrowserOptions } from '@sentry/browser'; import { init as browserInit } from '@sentry/browser'; import type { Client } from '@sentry/core'; -import { applySdkMetadata } from '@sentry/core'; +import { applySdkMetadata, parseSpotlightEnvValue, resolveSpotlightValue } from '@sentry/core'; + +// Build-time placeholder - Rollup replaces per output format +// ESM: import.meta.env?.VITE_SENTRY_SPOTLIGHT (zero-config for Vite) +// CJS: undefined +declare const __VITE_SPOTLIGHT_ENV__: string | undefined; /** * Initializes the Solid SDK @@ -11,6 +16,20 @@ export function init(options: BrowserOptions): Client | undefined { ...options, }; + // Check for spotlight env vars: + // 1. process.env.SENTRY_SPOTLIGHT (all bundlers, requires config) + // 2. process.env.VITE_SENTRY_SPOTLIGHT (all bundlers, requires config) + // 3. import.meta.env.VITE_SENTRY_SPOTLIGHT (ESM only, zero-config for Vite!) + const spotlightEnvRaw = + (typeof process !== 'undefined' && (process.env?.SENTRY_SPOTLIGHT || process.env?.VITE_SENTRY_SPOTLIGHT)) || + (typeof __VITE_SPOTLIGHT_ENV__ !== 'undefined' && __VITE_SPOTLIGHT_ENV__) || + undefined; + + if (spotlightEnvRaw) { + const envValue = parseSpotlightEnvValue(spotlightEnvRaw); + opts.spotlight = resolveSpotlightValue(options.spotlight, envValue); + } + applySdkMetadata(opts, 'solid'); return browserInit(opts); diff --git a/packages/svelte/src/sdk.ts b/packages/svelte/src/sdk.ts index b46a09bfdfa9..4f4440cb04b4 100644 --- a/packages/svelte/src/sdk.ts +++ b/packages/svelte/src/sdk.ts @@ -1,7 +1,13 @@ import type { BrowserOptions } from '@sentry/browser'; import { init as browserInit } from '@sentry/browser'; import type { Client } from '@sentry/core'; -import { applySdkMetadata } from '@sentry/core'; +import { applySdkMetadata, parseSpotlightEnvValue, resolveSpotlightValue } from '@sentry/core'; + +// Build-time placeholder - Rollup replaces per output format +// ESM: import.meta.env?.VITE_SENTRY_SPOTLIGHT (zero-config for Vite) +// CJS: undefined +declare const __VITE_SPOTLIGHT_ENV__: string | undefined; + /** * Inits the Svelte SDK */ @@ -10,6 +16,20 @@ export function init(options: BrowserOptions): Client | undefined { ...options, }; + // Check for spotlight env vars: + // 1. process.env.SENTRY_SPOTLIGHT (all bundlers, requires config) + // 2. process.env.VITE_SENTRY_SPOTLIGHT (all bundlers, requires config) + // 3. import.meta.env.VITE_SENTRY_SPOTLIGHT (ESM only, zero-config for Vite!) + const spotlightEnvRaw = + (typeof process !== 'undefined' && (process.env?.SENTRY_SPOTLIGHT || process.env?.VITE_SENTRY_SPOTLIGHT)) || + (typeof __VITE_SPOTLIGHT_ENV__ !== 'undefined' && __VITE_SPOTLIGHT_ENV__) || + undefined; + + if (spotlightEnvRaw) { + const envValue = parseSpotlightEnvValue(spotlightEnvRaw); + opts.spotlight = resolveSpotlightValue(options.spotlight, envValue); + } + applySdkMetadata(opts, 'svelte'); return browserInit(opts); diff --git a/packages/sveltekit/src/client/sdk.ts b/packages/sveltekit/src/client/sdk.ts index a6294ad25977..d964b83d3539 100644 --- a/packages/sveltekit/src/client/sdk.ts +++ b/packages/sveltekit/src/client/sdk.ts @@ -1,9 +1,26 @@ import type { Client, Integration } from '@sentry/core'; -import { applySdkMetadata } from '@sentry/core'; +import { applySdkMetadata, parseSpotlightEnvValue, resolveSpotlightValue } from '@sentry/core'; import type { BrowserOptions } from '@sentry/svelte'; import { getDefaultIntegrations as getDefaultSvelteIntegrations, init as initSvelteSdk, WINDOW } from '@sentry/svelte'; import { browserTracingIntegration as svelteKitBrowserTracingIntegration } from './browserTracingIntegration'; +// Type for spotlight-related env vars injected by Vite +interface SpotlightEnv { + PUBLIC_SENTRY_SPOTLIGHT?: string; + SENTRY_SPOTLIGHT?: string; +} + +// Access import.meta.env in a way that works with TypeScript +// Vite replaces this at build time +function getSpotlightEnv(): SpotlightEnv { + try { + // @ts-expect-error - import.meta.env is injected by Vite + return typeof import.meta !== 'undefined' && import.meta.env ? (import.meta.env as SpotlightEnv) : {}; + } catch { + return {}; + } +} + type WindowWithSentryFetchProxy = typeof WINDOW & { _sentryFetchProxy?: typeof fetch; }; @@ -17,9 +34,16 @@ declare const __SENTRY_TRACING__: boolean; * @param options Configuration options for the SDK. */ export function init(options: BrowserOptions): Client | undefined { + // Read PUBLIC_SENTRY_SPOTLIGHT (set by spotlight run, SvelteKit uses PUBLIC_ prefix) + // OR fallback to SENTRY_SPOTLIGHT (injected by our vite plugin) + const spotlightEnv = getSpotlightEnv(); + const spotlightEnvRaw = spotlightEnv.PUBLIC_SENTRY_SPOTLIGHT || spotlightEnv.SENTRY_SPOTLIGHT; + const spotlightEnvValue = parseSpotlightEnvValue(spotlightEnvRaw); + const opts = { defaultIntegrations: getDefaultIntegrations(options), ...options, + spotlight: resolveSpotlightValue(options.spotlight, spotlightEnvValue), }; applySdkMetadata(opts, 'sveltekit', ['sveltekit', 'svelte']); diff --git a/packages/sveltekit/src/vite/sentryVitePlugins.ts b/packages/sveltekit/src/vite/sentryVitePlugins.ts index 56493e071a55..2b1ac5628741 100644 --- a/packages/sveltekit/src/vite/sentryVitePlugins.ts +++ b/packages/sveltekit/src/vite/sentryVitePlugins.ts @@ -13,6 +13,26 @@ const DEFAULT_PLUGIN_OPTIONS: SentrySvelteKitPluginOptions = { debug: false, }; +/** + * Creates a Vite plugin that injects SENTRY_SPOTLIGHT env var for client bundles. + * This enables zero-config Spotlight support when using `spotlight run`. + */ +function makeSpotlightDefinePlugin(): Plugin { + return { + name: 'sentry-sveltekit-spotlight-define', + config() { + // Read PUBLIC_SENTRY_SPOTLIGHT (set by spotlight run, SvelteKit uses PUBLIC_ prefix) + // OR fallback to SENTRY_SPOTLIGHT (manual setup) + const spotlightValue = process.env.PUBLIC_SENTRY_SPOTLIGHT || process.env.SENTRY_SPOTLIGHT || ''; + return { + define: { + 'import.meta.env.SENTRY_SPOTLIGHT': JSON.stringify(spotlightValue), + }, + }; + }, + }; +} + /** * Vite Plugins for the Sentry SvelteKit SDK, taking care of creating * Sentry releases and uploading source maps to Sentry. @@ -31,6 +51,9 @@ export async function sentrySvelteKit(options: SentrySvelteKitPluginOptions = {} const sentryPlugins: Plugin[] = []; + // Add spotlight plugin unconditionally for zero-config Spotlight support + sentryPlugins.push(makeSpotlightDefinePlugin()); + if (mergedOptions.autoInstrument) { // TODO: Once tracing is promoted stable, we need to adjust this check! const kitTracingEnabled = !!svelteConfig.kit?.experimental?.tracing?.server; diff --git a/packages/vue/src/sdk.ts b/packages/vue/src/sdk.ts index 689a17dacbc4..493a0823932a 100644 --- a/packages/vue/src/sdk.ts +++ b/packages/vue/src/sdk.ts @@ -1,9 +1,14 @@ import { getDefaultIntegrations, init as browserInit } from '@sentry/browser'; import type { Client } from '@sentry/core'; -import { applySdkMetadata } from '@sentry/core'; +import { applySdkMetadata, parseSpotlightEnvValue, resolveSpotlightValue } from '@sentry/core'; import { vueIntegration } from './integration'; import type { Options } from './types'; +// Build-time placeholder - Rollup replaces per output format +// ESM: import.meta.env?.VITE_SENTRY_SPOTLIGHT (zero-config for Vite) +// CJS: undefined +declare const __VITE_SPOTLIGHT_ENV__: string | undefined; + /** * Inits the Vue SDK */ @@ -13,6 +18,20 @@ export function init(options: Partial> = {}): Cl ...options, }; + // Check for spotlight env vars: + // 1. process.env.SENTRY_SPOTLIGHT (all bundlers, requires config) + // 2. process.env.VITE_SENTRY_SPOTLIGHT (all bundlers, requires config) + // 3. import.meta.env.VITE_SENTRY_SPOTLIGHT (ESM only, zero-config for Vite!) + const spotlightEnvRaw = + (typeof process !== 'undefined' && (process.env?.SENTRY_SPOTLIGHT || process.env?.VITE_SENTRY_SPOTLIGHT)) || + (typeof __VITE_SPOTLIGHT_ENV__ !== 'undefined' && __VITE_SPOTLIGHT_ENV__) || + undefined; + + if (spotlightEnvRaw) { + const envValue = parseSpotlightEnvValue(spotlightEnvRaw); + opts.spotlight = resolveSpotlightValue(options.spotlight, envValue); + } + applySdkMetadata(opts, 'vue'); return browserInit(opts); From f67c8a9ef24ea37bb2d49e3d1e9258735d257395 Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Tue, 30 Dec 2025 15:49:09 +0300 Subject: [PATCH 02/44] fix: Format and lint fixes --- packages/core/src/index.ts | 6 +----- packages/core/test/lib/utils/spotlight.test.ts | 2 +- packages/node-core/src/utils/envToBool.ts | 6 +----- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 9daaa3f70930..d6384bcc4b09 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -69,11 +69,7 @@ export { hasSpansEnabled } from './utils/hasSpansEnabled'; export { isSentryRequestUrl } from './utils/isSentryRequestUrl'; export { handleCallbackErrors } from './utils/handleCallbackErrors'; export { parameterize, fmt } from './utils/parameterize'; -export { - envToBool, - FALSY_ENV_VALUES, - TRUTHY_ENV_VALUES, -} from './utils/envToBool'; +export { envToBool, FALSY_ENV_VALUES, TRUTHY_ENV_VALUES } from './utils/envToBool'; export type { BoolCastOptions, StrictBoolCast, LooseBoolCast } from './utils/envToBool'; export { parseSpotlightEnvValue, resolveSpotlightValue } from './utils/spotlight'; export type { SpotlightConnectionOptions } from './utils/spotlight'; diff --git a/packages/core/test/lib/utils/spotlight.test.ts b/packages/core/test/lib/utils/spotlight.test.ts index 0ca19eb83ae6..70bdf110ab6a 100644 --- a/packages/core/test/lib/utils/spotlight.test.ts +++ b/packages/core/test/lib/utils/spotlight.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it, vi, afterEach, beforeEach } from 'vitest'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import * as debugLogger from '../../../src/utils/debug-logger'; import { parseSpotlightEnvValue, resolveSpotlightValue } from '../../../src/utils/spotlight'; diff --git a/packages/node-core/src/utils/envToBool.ts b/packages/node-core/src/utils/envToBool.ts index a9f01ed8bee5..b52da2d5ab3d 100644 --- a/packages/node-core/src/utils/envToBool.ts +++ b/packages/node-core/src/utils/envToBool.ts @@ -1,7 +1,3 @@ // Re-export from core for backwards compatibility -export { - envToBool, - FALSY_ENV_VALUES, - TRUTHY_ENV_VALUES, -} from '@sentry/core'; +export { envToBool, FALSY_ENV_VALUES, TRUTHY_ENV_VALUES } from '@sentry/core'; export type { BoolCastOptions, StrictBoolCast, LooseBoolCast } from '@sentry/core'; From b0a08afa915fedd6a6686cdf40355a11a323f9a9 Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Tue, 30 Dec 2025 16:30:51 +0300 Subject: [PATCH 03/44] fix: Only inject spotlight env var when set --- packages/astro/src/integration/index.ts | 15 +++++++++------ packages/nuxt/src/module.ts | 15 +++++++++------ packages/sveltekit/src/vite/sentryVitePlugins.ts | 6 ++++-- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/packages/astro/src/integration/index.ts b/packages/astro/src/integration/index.ts index ce06d1f29558..ba3b6caba32a 100644 --- a/packages/astro/src/integration/index.ts +++ b/packages/astro/src/integration/index.ts @@ -180,13 +180,16 @@ export const sentryAstro = (options: SentryOptions = {}): AstroIntegration => { } // Inject SENTRY_SPOTLIGHT env var for client bundles (fallback for manual setup without PUBLIC_ prefix) - updateConfig({ - vite: { - define: { - 'import.meta.env.SENTRY_SPOTLIGHT': JSON.stringify(process.env.SENTRY_SPOTLIGHT || ''), + // Only add if we have a value to inject and the SDK is enabled + if (process.env.SENTRY_SPOTLIGHT && sdkEnabled.client) { + updateConfig({ + vite: { + define: { + 'import.meta.env.SENTRY_SPOTLIGHT': JSON.stringify(process.env.SENTRY_SPOTLIGHT), + }, }, - }, - }); + }); + } const isSSR = config && (config.output === 'server' || config.output === 'hybrid'); const shouldAddMiddleware = sdkEnabled.server && autoInstrumentation?.requestHandler !== false; diff --git a/packages/nuxt/src/module.ts b/packages/nuxt/src/module.ts index 7eac82005620..7b4e473f3d93 100644 --- a/packages/nuxt/src/module.ts +++ b/packages/nuxt/src/module.ts @@ -108,12 +108,15 @@ export default defineNuxtModule({ addOTelCommonJSImportAlias(nuxt); // Inject SENTRY_SPOTLIGHT env var for client bundles (fallback for manual setup without VITE_ prefix) - nuxt.hook('vite:extendConfig', (viteConfig, env) => { - if (env.isClient) { - viteConfig.define = viteConfig.define || {}; - viteConfig.define['import.meta.env.SENTRY_SPOTLIGHT'] = JSON.stringify(process.env.SENTRY_SPOTLIGHT || ''); - } - }); + // Only add the hook if we have a value to inject + if (process.env.SENTRY_SPOTLIGHT) { + nuxt.hook('vite:extendConfig', (viteConfig, env) => { + if (env.isClient) { + viteConfig.define = viteConfig.define || {}; + viteConfig.define['import.meta.env.SENTRY_SPOTLIGHT'] = JSON.stringify(process.env.SENTRY_SPOTLIGHT); + } + }); + } const pagesDataTemplate = addTemplate({ filename: 'sentry--nuxt-pages-data.mjs', diff --git a/packages/sveltekit/src/vite/sentryVitePlugins.ts b/packages/sveltekit/src/vite/sentryVitePlugins.ts index 2b1ac5628741..5cbcad3d8453 100644 --- a/packages/sveltekit/src/vite/sentryVitePlugins.ts +++ b/packages/sveltekit/src/vite/sentryVitePlugins.ts @@ -51,8 +51,10 @@ export async function sentrySvelteKit(options: SentrySvelteKitPluginOptions = {} const sentryPlugins: Plugin[] = []; - // Add spotlight plugin unconditionally for zero-config Spotlight support - sentryPlugins.push(makeSpotlightDefinePlugin()); + // Add spotlight plugin for zero-config Spotlight support (only if env var is set) + if (process.env.PUBLIC_SENTRY_SPOTLIGHT || process.env.SENTRY_SPOTLIGHT) { + sentryPlugins.push(makeSpotlightDefinePlugin()); + } if (mergedOptions.autoInstrument) { // TODO: Once tracing is promoted stable, we need to adjust this check! From c71233e3d4f7a01fdec838f8cec4800c9e04c946 Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Tue, 30 Dec 2025 16:33:47 +0300 Subject: [PATCH 04/44] test: Add bundler tests for __VITE_SPOTLIGHT_ENV__ replacement --- .../bundler-tests/tests/bundling.test.ts | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/dev-packages/bundler-tests/tests/bundling.test.ts b/dev-packages/bundler-tests/tests/bundling.test.ts index 2cc8113ca83b..ca40d3bf14c6 100644 --- a/dev-packages/bundler-tests/tests/bundling.test.ts +++ b/dev-packages/bundler-tests/tests/bundling.test.ts @@ -142,3 +142,49 @@ describe('spotlight', () => { }); } }); + +describe('__VITE_SPOTLIGHT_ENV__ rollup replacement', () => { + // Test that our rollup build correctly replaces __VITE_SPOTLIGHT_ENV__ + // ESM bundles should have import.meta.env access, CJS should have undefined + + function readSdkFile(packageName: string, format: 'esm' | 'cjs'): string { + const sdkPath = path.join( + rootDir(), + 'packages', + packageName, + 'build', + format, + 'sdk.js', + ); + if (!fs.existsSync(sdkPath)) { + throw new Error(`SDK file not found: ${sdkPath}. Make sure to run yarn build:dev first.`); + } + return fs.readFileSync(sdkPath, 'utf8'); + } + + // Remove comments from code to test only actual code + function stripComments(code: string): string { + // Remove single-line comments + return code.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, ''); + } + + test.each(['react', 'vue', 'svelte', 'solid'] as const)( + '%s ESM bundle contains import.meta.env?.VITE_SENTRY_SPOTLIGHT access', + packageName => { + const code = stripComments(readSdkFile(packageName, 'esm')); + // ESM bundles should have import.meta.env access for Vite support + // The replacement is: import.meta.env?.VITE_SENTRY_SPOTLIGHT + expect(code).toMatch(/import\.meta\.env\?\.[A-Z_]+SPOTLIGHT/); + }, + ); + + test.each(['react', 'vue', 'svelte', 'solid'] as const)( + '%s CJS bundle does not contain import.meta.env (CJS incompatible)', + packageName => { + const code = stripComments(readSdkFile(packageName, 'cjs')); + // CJS bundles should NOT have import.meta.env as it's ESM-only syntax + // The __VITE_SPOTLIGHT_ENV__ placeholder should be replaced with 'undefined' + expect(code).not.toMatch(/import\.meta\.env/); + }, + ); +}); From 1edca18e51d0db4eb49697c2e366192173983c53 Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Tue, 30 Dec 2025 17:15:50 +0300 Subject: [PATCH 05/44] test: Add E2E tests for SENTRY_SPOTLIGHT env var support - Add startSpotlightProxyServer to test-utils for capturing Spotlight events - Add waitForSpotlightError and waitForSpotlightTransaction helpers - Create browser-spotlight test app that validates env var parsing - Test verifies events are sent to both tunnel AND Spotlight sidecar --- .../browser-spotlight/build.mjs | 55 +++++++++++++++ .../browser-spotlight/package.json | 31 ++++++++ .../browser-spotlight/playwright.config.mjs | 16 +++++ .../browser-spotlight/public/index.html | 18 +++++ .../browser-spotlight/src/index.js | 21 ++++++ .../browser-spotlight/start-event-proxy.mjs | 7 ++ .../start-spotlight-proxy.mjs | 8 +++ .../browser-spotlight/tests/spotlight.test.ts | 69 ++++++++++++++++++ .../browser-spotlight/tsconfig.json | 10 +++ .../test-utils/src/event-proxy-server.ts | 70 +++++++++++++++++++ dev-packages/test-utils/src/index.ts | 3 + 11 files changed, 308 insertions(+) create mode 100644 dev-packages/e2e-tests/test-applications/browser-spotlight/build.mjs create mode 100644 dev-packages/e2e-tests/test-applications/browser-spotlight/package.json create mode 100644 dev-packages/e2e-tests/test-applications/browser-spotlight/playwright.config.mjs create mode 100644 dev-packages/e2e-tests/test-applications/browser-spotlight/public/index.html create mode 100644 dev-packages/e2e-tests/test-applications/browser-spotlight/src/index.js create mode 100644 dev-packages/e2e-tests/test-applications/browser-spotlight/start-event-proxy.mjs create mode 100644 dev-packages/e2e-tests/test-applications/browser-spotlight/start-spotlight-proxy.mjs create mode 100644 dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/browser-spotlight/tsconfig.json diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/build.mjs b/dev-packages/e2e-tests/test-applications/browser-spotlight/build.mjs new file mode 100644 index 000000000000..a6b6901389c1 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/build.mjs @@ -0,0 +1,55 @@ +import * as path from 'path'; +import * as url from 'url'; +import HtmlWebpackPlugin from 'html-webpack-plugin'; +import TerserPlugin from 'terser-webpack-plugin'; +import webpack from 'webpack'; + +const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); + +webpack( + { + entry: path.join(__dirname, 'src/index.js'), + output: { + path: path.join(__dirname, 'build'), + filename: 'app.js', + }, + optimization: { + minimize: true, + minimizer: [new TerserPlugin()], + }, + plugins: [ + new webpack.DefinePlugin({ + // E2E_TEST_DSN can be passed from environment or defaults to empty + 'process.env.E2E_TEST_DSN': JSON.stringify(process.env.E2E_TEST_DSN || ''), + // SENTRY_SPOTLIGHT is hardcoded to point to the Spotlight proxy server on port 3032 + // This tests that the SDK correctly parses the env var and sends events to Spotlight + 'process.env.SENTRY_SPOTLIGHT': JSON.stringify('http://localhost:3032/stream'), + }), + new HtmlWebpackPlugin({ + template: path.join(__dirname, 'public/index.html'), + }), + ], + mode: 'production', + }, + (err, stats) => { + if (err) { + console.error(err.stack || err); + if (err.details) { + console.error(err.details); + } + return; + } + + const info = stats.toJson(); + + if (stats.hasErrors()) { + console.error(info.errors); + process.exit(1); + } + + if (stats.hasWarnings()) { + console.warn(info.warnings); + process.exit(1); + } + }, +); diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/package.json b/dev-packages/e2e-tests/test-applications/browser-spotlight/package.json new file mode 100644 index 000000000000..705e3701ca25 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/package.json @@ -0,0 +1,31 @@ +{ + "name": "browser-spotlight-test-app", + "version": "0.1.0", + "private": true, + "dependencies": { + "@sentry/react": "latest || *", + "@types/node": "^18.19.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "typescript": "~5.0.0" + }, + "scripts": { + "start": "serve -s build", + "build": "node build.mjs", + "test": "playwright test", + "clean": "npx rimraf node_modules pnpm-lock.yaml", + "test:build": "SENTRY_SPOTLIGHT=http://localhost:3032/stream pnpm install && SENTRY_SPOTLIGHT=http://localhost:3032/stream pnpm build", + "test:assert": "pnpm test" + }, + "devDependencies": { + "@playwright/test": "~1.56.0", + "@sentry-internal/test-utils": "link:../../../test-utils", + "webpack": "^5.91.0", + "serve": "14.0.1", + "terser-webpack-plugin": "^5.3.10", + "html-webpack-plugin": "^5.6.0" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/browser-spotlight/playwright.config.mjs new file mode 100644 index 000000000000..055d826c8055 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/playwright.config.mjs @@ -0,0 +1,16 @@ +import { getPlaywrightConfig } from '@sentry-internal/test-utils'; + +const config = getPlaywrightConfig({ + startCommand: `pnpm start`, +}); + +// Add the Spotlight proxy server as an additional webServer +// This runs alongside the main event proxy and app server +config.webServer.push({ + command: 'node start-spotlight-proxy.mjs', + port: 3032, + stdout: 'pipe', + stderr: 'pipe', +}); + +export default config; diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/public/index.html b/dev-packages/e2e-tests/test-applications/browser-spotlight/public/index.html new file mode 100644 index 000000000000..1c69e89d9ccb --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/public/index.html @@ -0,0 +1,18 @@ + + + + + + Browser Spotlight Test App + + +
+

Sentry Spotlight E2E Test

+

This app tests that SENTRY_SPOTLIGHT env var enables Spotlight integration.

+
+ + + + + + diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/src/index.js b/dev-packages/e2e-tests/test-applications/browser-spotlight/src/index.js new file mode 100644 index 000000000000..1151f906a422 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/src/index.js @@ -0,0 +1,21 @@ +import * as Sentry from '@sentry/react'; + +// Initialize Sentry with DSN and tunnel for regular event capture +// SENTRY_SPOTLIGHT is injected via webpack's EnvironmentPlugin at build time +// The @sentry/react SDK automatically parses SENTRY_SPOTLIGHT and enables Spotlight +Sentry.init({ + dsn: process.env.E2E_TEST_DSN, + integrations: [Sentry.browserTracingIntegration()], + tracesSampleRate: 1.0, + release: 'e2e-test', + environment: 'qa', + // Use tunnel to capture events at our proxy server + tunnel: 'http://localhost:3031', + // NOTE: We intentionally do NOT set `spotlight` here! + // The SDK should automatically parse SENTRY_SPOTLIGHT env var + // and enable Spotlight with the URL from the env var +}); + +document.getElementById('exception-button').addEventListener('click', () => { + throw new Error('Spotlight test error!'); +}); diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/browser-spotlight/start-event-proxy.mjs new file mode 100644 index 000000000000..d2543f009fe8 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/start-event-proxy.mjs @@ -0,0 +1,7 @@ +import { startEventProxyServer } from '@sentry-internal/test-utils'; + +// Start the main event proxy server that captures events via tunnel +startEventProxyServer({ + port: 3031, + proxyServerName: 'browser-spotlight', +}); diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/start-spotlight-proxy.mjs b/dev-packages/e2e-tests/test-applications/browser-spotlight/start-spotlight-proxy.mjs new file mode 100644 index 000000000000..c82c3af1d071 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/start-spotlight-proxy.mjs @@ -0,0 +1,8 @@ +import { startSpotlightProxyServer } from '@sentry-internal/test-utils'; + +// Start a Spotlight proxy server that captures events sent to /stream +// This simulates the Spotlight sidecar and allows us to verify events arrive +startSpotlightProxyServer({ + port: 3032, + proxyServerName: 'browser-spotlight-sidecar', +}); diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts b/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts new file mode 100644 index 000000000000..4ede4805cfba --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts @@ -0,0 +1,69 @@ +import { expect, test } from '@playwright/test'; +import { waitForError, waitForSpotlightError, waitForSpotlightTransaction } from '@sentry-internal/test-utils'; + +/** + * Test that SENTRY_SPOTLIGHT environment variable enables Spotlight integration. + * + * This test verifies that: + * 1. The SDK correctly parses SENTRY_SPOTLIGHT env var at build time (via webpack DefinePlugin) + * 2. The SDK resolves the spotlight option from the env var + * 3. Events are sent to both the tunnel AND the Spotlight sidecar URL + * 4. The Spotlight sidecar receives the events at the /stream endpoint + * + * Test setup: + * - SENTRY_SPOTLIGHT is set to 'http://localhost:3032/stream' at build time + * - tunnel is set to 'http://localhost:3031' for regular event capture + * - A Spotlight proxy server runs on port 3032 to capture Spotlight events + * - A regular event proxy server runs on port 3031 to capture tunnel events + */ +test('SENTRY_SPOTLIGHT env var enables Spotlight and events are sent to sidecar', async ({ page }) => { + // Wait for the error event to arrive at the Spotlight sidecar (port 3032) + const spotlightErrorPromise = waitForSpotlightError('browser-spotlight-sidecar', event => { + return !event.type && event.exception?.values?.[0]?.value === 'Spotlight test error!'; + }); + + // Also wait for the error to arrive at the regular tunnel (port 3031) + // This verifies that Spotlight doesn't interfere with normal event sending + const tunnelErrorPromise = waitForError('browser-spotlight', event => { + return !event.type && event.exception?.values?.[0]?.value === 'Spotlight test error!'; + }); + + await page.goto('/'); + + const exceptionButton = page.locator('id=exception-button'); + await exceptionButton.click(); + + // Both promises should resolve - the error should be sent to BOTH destinations + const [spotlightError, tunnelError] = await Promise.all([spotlightErrorPromise, tunnelErrorPromise]); + + // Verify the Spotlight sidecar received the error + expect(spotlightError.exception?.values).toHaveLength(1); + expect(spotlightError.exception?.values?.[0]?.value).toBe('Spotlight test error!'); + expect(spotlightError.exception?.values?.[0]?.type).toBe('Error'); + + // Verify the tunnel also received the error (normal Sentry flow still works) + expect(tunnelError.exception?.values).toHaveLength(1); + expect(tunnelError.exception?.values?.[0]?.value).toBe('Spotlight test error!'); + + // Both events should have the same trace context + expect(spotlightError.contexts?.trace?.trace_id).toBe(tunnelError.contexts?.trace?.trace_id); +}); + +/** + * Test that Spotlight receives transaction events as well. + */ +test('SENTRY_SPOTLIGHT sends transactions to sidecar', async ({ page }) => { + // Wait for a pageload transaction to arrive at the Spotlight sidecar + const spotlightTransactionPromise = waitForSpotlightTransaction('browser-spotlight-sidecar', event => { + return event.type === 'transaction' && event.contexts?.trace?.op === 'pageload'; + }); + + await page.goto('/'); + + const spotlightTransaction = await spotlightTransactionPromise; + + // Verify the Spotlight sidecar received the transaction + expect(spotlightTransaction.type).toBe('transaction'); + expect(spotlightTransaction.contexts?.trace?.op).toBe('pageload'); + expect(spotlightTransaction.transaction).toBe('/'); +}); diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/tsconfig.json b/dev-packages/e2e-tests/test-applications/browser-spotlight/tsconfig.json new file mode 100644 index 000000000000..c03239a36032 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "types": ["node"], + "esModuleInterop": true, + "lib": ["DOM", "ES2020"], + "strict": true, + "outDir": "dist" + }, + "include": ["*.ts", "src/**/*.ts", "tests/**/*.ts"] +} diff --git a/dev-packages/test-utils/src/event-proxy-server.ts b/dev-packages/test-utils/src/event-proxy-server.ts index 08fa39db950f..599d8e6e1325 100644 --- a/dev-packages/test-utils/src/event-proxy-server.ts +++ b/dev-packages/test-utils/src/event-proxy-server.ts @@ -25,6 +25,7 @@ interface SentryRequestCallbackData { envelope: Envelope; rawProxyRequestBody: string; rawProxyRequestHeaders: Record; + rawProxyRequestUrl?: string; rawSentryResponseBody: string; sentryResponseStatusCode?: number; } @@ -184,6 +185,7 @@ export async function startEventProxyServer(options: EventProxyServerOptions): P envelope: parseEnvelope(proxyRequestBody), rawProxyRequestBody: proxyRequestBody, rawProxyRequestHeaders: proxyRequest.headers, + rawProxyRequestUrl: proxyRequest.url, rawSentryResponseBody: '', sentryResponseStatusCode: 200, }; @@ -391,6 +393,74 @@ export function waitForTransaction( }); } +interface SpotlightProxyServerOptions { + /** Port to start the spotlight proxy server at. */ + port: number; + /** The name for the proxy server used for referencing it with listener functions */ + proxyServerName: string; +} + +/** + * Starts a proxy server that acts like a Spotlight sidecar. + * It accepts envelopes at /stream and allows tests to wait for them. + * Point the SDK's `spotlight` option or `SENTRY_SPOTLIGHT` env var to this server. + */ +export async function startSpotlightProxyServer(options: SpotlightProxyServerOptions): Promise { + await startProxyServer(options, async (eventCallbackListeners, proxyRequest, proxyRequestBody, eventBuffer) => { + // Spotlight sends to /stream endpoint + const url = proxyRequest.url || ''; + if (!url.includes('/stream')) { + // Return 404 for non-spotlight requests + return [404, 'Not Found', {}]; + } + + const data: SentryRequestCallbackData = { + envelope: parseEnvelope(proxyRequestBody), + rawProxyRequestBody: proxyRequestBody, + rawProxyRequestHeaders: proxyRequest.headers, + rawProxyRequestUrl: proxyRequest.url, + rawSentryResponseBody: '', + sentryResponseStatusCode: 200, + }; + + const dataString = Buffer.from(JSON.stringify(data)).toString('base64'); + + eventBuffer.push({ data: dataString, timestamp: getNanosecondTimestamp() }); + + eventCallbackListeners.forEach(listener => { + listener(dataString); + }); + + return [ + 200, + '{}', + { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'POST, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type', + }, + ]; + }); +} + +/** Wait for an error to be sent to Spotlight. */ +export function waitForSpotlightError( + proxyServerName: string, + callback: (errorEvent: Event) => Promise | boolean, +): Promise { + // Reuse the same logic as waitForError - just uses a different proxy server name + return waitForError(proxyServerName, callback); +} + +/** Wait for a transaction to be sent to Spotlight. */ +export function waitForSpotlightTransaction( + proxyServerName: string, + callback: (transactionEvent: Event) => Promise | boolean, +): Promise { + // Reuse the same logic as waitForTransaction - just uses a different proxy server name + return waitForTransaction(proxyServerName, callback); +} + const TEMP_FILE_PREFIX = 'event-proxy-server-'; async function registerCallbackServerPort(serverName: string, port: string): Promise { diff --git a/dev-packages/test-utils/src/index.ts b/dev-packages/test-utils/src/index.ts index e9ae76f592ed..efd0ee75ac62 100644 --- a/dev-packages/test-utils/src/index.ts +++ b/dev-packages/test-utils/src/index.ts @@ -1,12 +1,15 @@ export { startProxyServer, startEventProxyServer, + startSpotlightProxyServer, waitForEnvelopeItem, waitForError, waitForRequest, waitForTransaction, waitForSession, waitForPlainRequest, + waitForSpotlightError, + waitForSpotlightTransaction, } from './event-proxy-server'; export { getPlaywrightConfig } from './playwright-config'; From 039e87368c263b66f1de9e0cffc6f7b6a6e85029 Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Tue, 30 Dec 2025 17:23:48 +0300 Subject: [PATCH 06/44] chore: Fix prettier formatting --- dev-packages/bundler-tests/tests/bundling.test.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/dev-packages/bundler-tests/tests/bundling.test.ts b/dev-packages/bundler-tests/tests/bundling.test.ts index ca40d3bf14c6..955287df0095 100644 --- a/dev-packages/bundler-tests/tests/bundling.test.ts +++ b/dev-packages/bundler-tests/tests/bundling.test.ts @@ -148,14 +148,7 @@ describe('__VITE_SPOTLIGHT_ENV__ rollup replacement', () => { // ESM bundles should have import.meta.env access, CJS should have undefined function readSdkFile(packageName: string, format: 'esm' | 'cjs'): string { - const sdkPath = path.join( - rootDir(), - 'packages', - packageName, - 'build', - format, - 'sdk.js', - ); + const sdkPath = path.join(rootDir(), 'packages', packageName, 'build', format, 'sdk.js'); if (!fs.existsSync(sdkPath)) { throw new Error(`SDK file not found: ${sdkPath}. Make sure to run yarn build:dev first.`); } From 004113535b106e1d5282c099bb75c29cd1f8d5ef Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Tue, 30 Dec 2025 17:59:11 +0300 Subject: [PATCH 07/44] fix: Bump size limits and fix E2E test webpack config - Bump @sentry/react (incl. Tracing) limit from 44 KB to 44.5 KB - Bump @sentry/svelte limit from 25 KB to 25.5 KB - Bump @sentry/sveltekit (client) limit from 42 KB to 42.5 KB - Use EnvironmentPlugin instead of DefinePlugin for more robust env var replacement --- .size-limit.js | 6 +++--- .../test-applications/browser-spotlight/build.mjs | 8 ++++---- .../test-applications/browser-spotlight/package.json | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.size-limit.js b/.size-limit.js index 24772d8380f5..787e572f0d77 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -148,7 +148,7 @@ module.exports = [ import: createImport('init', 'ErrorBoundary', 'reactRouterV6BrowserTracingIntegration'), ignore: ['react/jsx-runtime'], gzip: true, - limit: '44 KB', + limit: '44.5 KB', }, // Vue SDK (ESM) { @@ -171,7 +171,7 @@ module.exports = [ path: 'packages/svelte/build/esm/index.js', import: createImport('init'), gzip: true, - limit: '25 KB', + limit: '25.5 KB', }, // Browser CDN bundles { @@ -243,7 +243,7 @@ module.exports = [ import: createImport('init'), ignore: ['$app/stores'], gzip: true, - limit: '42 KB', + limit: '42.5 KB', }, // Node-Core SDK (ESM) { diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/build.mjs b/dev-packages/e2e-tests/test-applications/browser-spotlight/build.mjs index a6b6901389c1..6b128766e93e 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/build.mjs +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/build.mjs @@ -18,12 +18,12 @@ webpack( minimizer: [new TerserPlugin()], }, plugins: [ - new webpack.DefinePlugin({ - // E2E_TEST_DSN can be passed from environment or defaults to empty - 'process.env.E2E_TEST_DSN': JSON.stringify(process.env.E2E_TEST_DSN || ''), + // EnvironmentPlugin is more robust for replacing process.env.* references + new webpack.EnvironmentPlugin({ + E2E_TEST_DSN: '', // SENTRY_SPOTLIGHT is hardcoded to point to the Spotlight proxy server on port 3032 // This tests that the SDK correctly parses the env var and sends events to Spotlight - 'process.env.SENTRY_SPOTLIGHT': JSON.stringify('http://localhost:3032/stream'), + SENTRY_SPOTLIGHT: 'http://localhost:3032/stream', }), new HtmlWebpackPlugin({ template: path.join(__dirname, 'public/index.html'), diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/package.json b/dev-packages/e2e-tests/test-applications/browser-spotlight/package.json index 705e3701ca25..9c7e9479c76b 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/package.json +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/package.json @@ -14,7 +14,7 @@ "build": "node build.mjs", "test": "playwright test", "clean": "npx rimraf node_modules pnpm-lock.yaml", - "test:build": "SENTRY_SPOTLIGHT=http://localhost:3032/stream pnpm install && SENTRY_SPOTLIGHT=http://localhost:3032/stream pnpm build", + "test:build": "pnpm install && pnpm build", "test:assert": "pnpm test" }, "devDependencies": { From f4bdbd33872fb46f7aa98d14aa463cf246e5e03f Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Tue, 30 Dec 2025 21:18:12 +0300 Subject: [PATCH 08/44] test: Remove browser-spotlight E2E test temporarily The E2E test has issues with the proxy server setup that need to be investigated. The core Spotlight env var support is verified through unit tests. --- .../browser-spotlight/build.mjs | 55 --------------- .../browser-spotlight/package.json | 31 --------- .../browser-spotlight/playwright.config.mjs | 16 ----- .../browser-spotlight/public/index.html | 18 ----- .../browser-spotlight/src/index.js | 21 ------ .../browser-spotlight/start-event-proxy.mjs | 7 -- .../start-spotlight-proxy.mjs | 8 --- .../browser-spotlight/tests/spotlight.test.ts | 69 ------------------- .../browser-spotlight/tsconfig.json | 10 --- 9 files changed, 235 deletions(-) delete mode 100644 dev-packages/e2e-tests/test-applications/browser-spotlight/build.mjs delete mode 100644 dev-packages/e2e-tests/test-applications/browser-spotlight/package.json delete mode 100644 dev-packages/e2e-tests/test-applications/browser-spotlight/playwright.config.mjs delete mode 100644 dev-packages/e2e-tests/test-applications/browser-spotlight/public/index.html delete mode 100644 dev-packages/e2e-tests/test-applications/browser-spotlight/src/index.js delete mode 100644 dev-packages/e2e-tests/test-applications/browser-spotlight/start-event-proxy.mjs delete mode 100644 dev-packages/e2e-tests/test-applications/browser-spotlight/start-spotlight-proxy.mjs delete mode 100644 dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/browser-spotlight/tsconfig.json diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/build.mjs b/dev-packages/e2e-tests/test-applications/browser-spotlight/build.mjs deleted file mode 100644 index 6b128766e93e..000000000000 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/build.mjs +++ /dev/null @@ -1,55 +0,0 @@ -import * as path from 'path'; -import * as url from 'url'; -import HtmlWebpackPlugin from 'html-webpack-plugin'; -import TerserPlugin from 'terser-webpack-plugin'; -import webpack from 'webpack'; - -const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); - -webpack( - { - entry: path.join(__dirname, 'src/index.js'), - output: { - path: path.join(__dirname, 'build'), - filename: 'app.js', - }, - optimization: { - minimize: true, - minimizer: [new TerserPlugin()], - }, - plugins: [ - // EnvironmentPlugin is more robust for replacing process.env.* references - new webpack.EnvironmentPlugin({ - E2E_TEST_DSN: '', - // SENTRY_SPOTLIGHT is hardcoded to point to the Spotlight proxy server on port 3032 - // This tests that the SDK correctly parses the env var and sends events to Spotlight - SENTRY_SPOTLIGHT: 'http://localhost:3032/stream', - }), - new HtmlWebpackPlugin({ - template: path.join(__dirname, 'public/index.html'), - }), - ], - mode: 'production', - }, - (err, stats) => { - if (err) { - console.error(err.stack || err); - if (err.details) { - console.error(err.details); - } - return; - } - - const info = stats.toJson(); - - if (stats.hasErrors()) { - console.error(info.errors); - process.exit(1); - } - - if (stats.hasWarnings()) { - console.warn(info.warnings); - process.exit(1); - } - }, -); diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/package.json b/dev-packages/e2e-tests/test-applications/browser-spotlight/package.json deleted file mode 100644 index 9c7e9479c76b..000000000000 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "browser-spotlight-test-app", - "version": "0.1.0", - "private": true, - "dependencies": { - "@sentry/react": "latest || *", - "@types/node": "^18.19.1", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "typescript": "~5.0.0" - }, - "scripts": { - "start": "serve -s build", - "build": "node build.mjs", - "test": "playwright test", - "clean": "npx rimraf node_modules pnpm-lock.yaml", - "test:build": "pnpm install && pnpm build", - "test:assert": "pnpm test" - }, - "devDependencies": { - "@playwright/test": "~1.56.0", - "@sentry-internal/test-utils": "link:../../../test-utils", - "webpack": "^5.91.0", - "serve": "14.0.1", - "terser-webpack-plugin": "^5.3.10", - "html-webpack-plugin": "^5.6.0" - }, - "volta": { - "extends": "../../package.json" - } -} diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/browser-spotlight/playwright.config.mjs deleted file mode 100644 index 055d826c8055..000000000000 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/playwright.config.mjs +++ /dev/null @@ -1,16 +0,0 @@ -import { getPlaywrightConfig } from '@sentry-internal/test-utils'; - -const config = getPlaywrightConfig({ - startCommand: `pnpm start`, -}); - -// Add the Spotlight proxy server as an additional webServer -// This runs alongside the main event proxy and app server -config.webServer.push({ - command: 'node start-spotlight-proxy.mjs', - port: 3032, - stdout: 'pipe', - stderr: 'pipe', -}); - -export default config; diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/public/index.html b/dev-packages/e2e-tests/test-applications/browser-spotlight/public/index.html deleted file mode 100644 index 1c69e89d9ccb..000000000000 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/public/index.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - Browser Spotlight Test App - - -
-

Sentry Spotlight E2E Test

-

This app tests that SENTRY_SPOTLIGHT env var enables Spotlight integration.

-
- - - - - - diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/src/index.js b/dev-packages/e2e-tests/test-applications/browser-spotlight/src/index.js deleted file mode 100644 index 1151f906a422..000000000000 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/src/index.js +++ /dev/null @@ -1,21 +0,0 @@ -import * as Sentry from '@sentry/react'; - -// Initialize Sentry with DSN and tunnel for regular event capture -// SENTRY_SPOTLIGHT is injected via webpack's EnvironmentPlugin at build time -// The @sentry/react SDK automatically parses SENTRY_SPOTLIGHT and enables Spotlight -Sentry.init({ - dsn: process.env.E2E_TEST_DSN, - integrations: [Sentry.browserTracingIntegration()], - tracesSampleRate: 1.0, - release: 'e2e-test', - environment: 'qa', - // Use tunnel to capture events at our proxy server - tunnel: 'http://localhost:3031', - // NOTE: We intentionally do NOT set `spotlight` here! - // The SDK should automatically parse SENTRY_SPOTLIGHT env var - // and enable Spotlight with the URL from the env var -}); - -document.getElementById('exception-button').addEventListener('click', () => { - throw new Error('Spotlight test error!'); -}); diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/browser-spotlight/start-event-proxy.mjs deleted file mode 100644 index d2543f009fe8..000000000000 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/start-event-proxy.mjs +++ /dev/null @@ -1,7 +0,0 @@ -import { startEventProxyServer } from '@sentry-internal/test-utils'; - -// Start the main event proxy server that captures events via tunnel -startEventProxyServer({ - port: 3031, - proxyServerName: 'browser-spotlight', -}); diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/start-spotlight-proxy.mjs b/dev-packages/e2e-tests/test-applications/browser-spotlight/start-spotlight-proxy.mjs deleted file mode 100644 index c82c3af1d071..000000000000 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/start-spotlight-proxy.mjs +++ /dev/null @@ -1,8 +0,0 @@ -import { startSpotlightProxyServer } from '@sentry-internal/test-utils'; - -// Start a Spotlight proxy server that captures events sent to /stream -// This simulates the Spotlight sidecar and allows us to verify events arrive -startSpotlightProxyServer({ - port: 3032, - proxyServerName: 'browser-spotlight-sidecar', -}); diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts b/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts deleted file mode 100644 index 4ede4805cfba..000000000000 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { waitForError, waitForSpotlightError, waitForSpotlightTransaction } from '@sentry-internal/test-utils'; - -/** - * Test that SENTRY_SPOTLIGHT environment variable enables Spotlight integration. - * - * This test verifies that: - * 1. The SDK correctly parses SENTRY_SPOTLIGHT env var at build time (via webpack DefinePlugin) - * 2. The SDK resolves the spotlight option from the env var - * 3. Events are sent to both the tunnel AND the Spotlight sidecar URL - * 4. The Spotlight sidecar receives the events at the /stream endpoint - * - * Test setup: - * - SENTRY_SPOTLIGHT is set to 'http://localhost:3032/stream' at build time - * - tunnel is set to 'http://localhost:3031' for regular event capture - * - A Spotlight proxy server runs on port 3032 to capture Spotlight events - * - A regular event proxy server runs on port 3031 to capture tunnel events - */ -test('SENTRY_SPOTLIGHT env var enables Spotlight and events are sent to sidecar', async ({ page }) => { - // Wait for the error event to arrive at the Spotlight sidecar (port 3032) - const spotlightErrorPromise = waitForSpotlightError('browser-spotlight-sidecar', event => { - return !event.type && event.exception?.values?.[0]?.value === 'Spotlight test error!'; - }); - - // Also wait for the error to arrive at the regular tunnel (port 3031) - // This verifies that Spotlight doesn't interfere with normal event sending - const tunnelErrorPromise = waitForError('browser-spotlight', event => { - return !event.type && event.exception?.values?.[0]?.value === 'Spotlight test error!'; - }); - - await page.goto('/'); - - const exceptionButton = page.locator('id=exception-button'); - await exceptionButton.click(); - - // Both promises should resolve - the error should be sent to BOTH destinations - const [spotlightError, tunnelError] = await Promise.all([spotlightErrorPromise, tunnelErrorPromise]); - - // Verify the Spotlight sidecar received the error - expect(spotlightError.exception?.values).toHaveLength(1); - expect(spotlightError.exception?.values?.[0]?.value).toBe('Spotlight test error!'); - expect(spotlightError.exception?.values?.[0]?.type).toBe('Error'); - - // Verify the tunnel also received the error (normal Sentry flow still works) - expect(tunnelError.exception?.values).toHaveLength(1); - expect(tunnelError.exception?.values?.[0]?.value).toBe('Spotlight test error!'); - - // Both events should have the same trace context - expect(spotlightError.contexts?.trace?.trace_id).toBe(tunnelError.contexts?.trace?.trace_id); -}); - -/** - * Test that Spotlight receives transaction events as well. - */ -test('SENTRY_SPOTLIGHT sends transactions to sidecar', async ({ page }) => { - // Wait for a pageload transaction to arrive at the Spotlight sidecar - const spotlightTransactionPromise = waitForSpotlightTransaction('browser-spotlight-sidecar', event => { - return event.type === 'transaction' && event.contexts?.trace?.op === 'pageload'; - }); - - await page.goto('/'); - - const spotlightTransaction = await spotlightTransactionPromise; - - // Verify the Spotlight sidecar received the transaction - expect(spotlightTransaction.type).toBe('transaction'); - expect(spotlightTransaction.contexts?.trace?.op).toBe('pageload'); - expect(spotlightTransaction.transaction).toBe('/'); -}); diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/tsconfig.json b/dev-packages/e2e-tests/test-applications/browser-spotlight/tsconfig.json deleted file mode 100644 index c03239a36032..000000000000 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "compilerOptions": { - "types": ["node"], - "esModuleInterop": true, - "lib": ["DOM", "ES2020"], - "strict": true, - "outDir": "dist" - }, - "include": ["*.ts", "src/**/*.ts", "tests/**/*.ts"] -} From 0b171136f1c03b64e621f77e5b06300f3f1cbb3f Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Tue, 30 Dec 2025 21:43:18 +0300 Subject: [PATCH 09/44] fix: Bump @sentry/nextjs client size limit to 47 KB --- .size-limit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.size-limit.js b/.size-limit.js index 787e572f0d77..a8ab8b842146 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -234,7 +234,7 @@ module.exports = [ import: createImport('init'), ignore: ['next/router', 'next/constants'], gzip: true, - limit: '46.5 KB', + limit: '47 KB', }, // SvelteKit SDK (ESM) { From f02d9861fa9120e915acbb5a976aed1432b412a6 Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Tue, 30 Dec 2025 23:25:13 +0300 Subject: [PATCH 10/44] test: Add E2E tests for SENTRY_SPOTLIGHT env var support - Fix CORS preflight (OPTIONS) handling in startProxyServer - Add browser-spotlight E2E test application that validates: - SDK correctly parses SENTRY_SPOTLIGHT env var at build time - Events are sent to both tunnel AND Spotlight sidecar - Transactions are also forwarded to Spotlight - Use development mode in webpack so Spotlight code is included - Define process in webpack so typeof check works in browser --- .../browser-spotlight/build.mjs | 59 ++++++++++++++++ .../browser-spotlight/package.json | 29 ++++++++ .../browser-spotlight/playwright.config.mjs | 16 +++++ .../browser-spotlight/public/index.html | 12 ++++ .../browser-spotlight/src/index.js | 21 ++++++ .../browser-spotlight/start-event-proxy.mjs | 7 ++ .../start-spotlight-proxy.mjs | 8 +++ .../browser-spotlight/tests/spotlight.test.ts | 68 +++++++++++++++++++ .../browser-spotlight/tsconfig.json | 12 ++++ .../test-utils/src/event-proxy-server.ts | 11 +++ 10 files changed, 243 insertions(+) create mode 100644 dev-packages/e2e-tests/test-applications/browser-spotlight/build.mjs create mode 100644 dev-packages/e2e-tests/test-applications/browser-spotlight/package.json create mode 100644 dev-packages/e2e-tests/test-applications/browser-spotlight/playwright.config.mjs create mode 100644 dev-packages/e2e-tests/test-applications/browser-spotlight/public/index.html create mode 100644 dev-packages/e2e-tests/test-applications/browser-spotlight/src/index.js create mode 100644 dev-packages/e2e-tests/test-applications/browser-spotlight/start-event-proxy.mjs create mode 100644 dev-packages/e2e-tests/test-applications/browser-spotlight/start-spotlight-proxy.mjs create mode 100644 dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/browser-spotlight/tsconfig.json diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/build.mjs b/dev-packages/e2e-tests/test-applications/browser-spotlight/build.mjs new file mode 100644 index 000000000000..7ffbabcdd405 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/build.mjs @@ -0,0 +1,59 @@ +import * as path from 'path'; +import * as url from 'url'; +import HtmlWebpackPlugin from 'html-webpack-plugin'; +import TerserPlugin from 'terser-webpack-plugin'; +import webpack from 'webpack'; + +const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); + +webpack( + { + entry: path.join(__dirname, 'src/index.js'), + output: { + path: path.join(__dirname, 'build'), + filename: 'app.js', + }, + optimization: { + minimize: true, + minimizer: [new TerserPlugin()], + }, + plugins: [ + // Use DefinePlugin to properly set process.env.* values + // Must also define 'process' so the typeof check works in browser + new webpack.DefinePlugin({ + 'process.env.E2E_TEST_DSN': JSON.stringify( + process.env.E2E_TEST_DSN || 'https://public@dsn.ingest.sentry.io/1234567', + ), + 'process.env.SENTRY_SPOTLIGHT': JSON.stringify('http://localhost:3032/stream'), + // Define process so "typeof process !== 'undefined'" is true in browser + process: JSON.stringify({ env: {} }), + }), + new HtmlWebpackPlugin({ + template: path.join(__dirname, 'public/index.html'), + }), + ], + // Use development mode so Spotlight integration code is included + // (Spotlight is stripped from production builds by design) + mode: 'development', + }, + (err, stats) => { + if (err) { + console.error(err.stack || err); + if (err.details) { + console.error(err.details); + } + process.exit(1); + } + + const info = stats.toJson(); + + if (stats.hasErrors()) { + console.error(info.errors); + process.exit(1); + } + + if (stats.hasWarnings()) { + console.warn(info.warnings); + } + }, +); diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/package.json b/dev-packages/e2e-tests/test-applications/browser-spotlight/package.json new file mode 100644 index 000000000000..2645a57e83db --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/package.json @@ -0,0 +1,29 @@ +{ + "name": "browser-spotlight-test-app", + "version": "0.1.0", + "private": true, + "dependencies": { + "@sentry/react": "latest || *", + "@types/node": "^18.19.1", + "typescript": "~5.0.0" + }, + "scripts": { + "start": "serve -s build", + "build": "node build.mjs", + "test": "playwright test", + "clean": "npx rimraf node_modules pnpm-lock.yaml", + "test:build": "pnpm install && pnpm build", + "test:assert": "pnpm test" + }, + "devDependencies": { + "@playwright/test": "~1.56.0", + "@sentry-internal/test-utils": "link:../../../test-utils", + "webpack": "^5.91.0", + "serve": "14.0.1", + "terser-webpack-plugin": "^5.3.10", + "html-webpack-plugin": "^5.6.0" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/browser-spotlight/playwright.config.mjs new file mode 100644 index 000000000000..055d826c8055 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/playwright.config.mjs @@ -0,0 +1,16 @@ +import { getPlaywrightConfig } from '@sentry-internal/test-utils'; + +const config = getPlaywrightConfig({ + startCommand: `pnpm start`, +}); + +// Add the Spotlight proxy server as an additional webServer +// This runs alongside the main event proxy and app server +config.webServer.push({ + command: 'node start-spotlight-proxy.mjs', + port: 3032, + stdout: 'pipe', + stderr: 'pipe', +}); + +export default config; diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/public/index.html b/dev-packages/e2e-tests/test-applications/browser-spotlight/public/index.html new file mode 100644 index 000000000000..da88d7f966d7 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/public/index.html @@ -0,0 +1,12 @@ + + + + + Spotlight E2E Test + + +

Spotlight E2E Test

+

This page tests that SENTRY_SPOTLIGHT env var enables Spotlight integration.

+ + + diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/src/index.js b/dev-packages/e2e-tests/test-applications/browser-spotlight/src/index.js new file mode 100644 index 000000000000..41806c83bdaa --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/src/index.js @@ -0,0 +1,21 @@ +import * as Sentry from '@sentry/react'; + +// Initialize Sentry with DSN and tunnel for regular event capture +// SENTRY_SPOTLIGHT is injected via webpack's DefinePlugin at build time +// The @sentry/react SDK automatically parses SENTRY_SPOTLIGHT and enables Spotlight +Sentry.init({ + dsn: process.env.E2E_TEST_DSN, + integrations: [Sentry.browserTracingIntegration()], + tracesSampleRate: 1.0, + release: 'e2e-test', + environment: 'qa', + // Use tunnel to capture events at our proxy server + tunnel: 'http://localhost:3031', + // NOTE: We intentionally do NOT set `spotlight` here! + // The SDK should automatically parse SENTRY_SPOTLIGHT env var + // and enable Spotlight with the URL from the env var +}); + +document.getElementById('exception-button').addEventListener('click', () => { + throw new Error('Spotlight test error!'); +}); diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/browser-spotlight/start-event-proxy.mjs new file mode 100644 index 000000000000..d2543f009fe8 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/start-event-proxy.mjs @@ -0,0 +1,7 @@ +import { startEventProxyServer } from '@sentry-internal/test-utils'; + +// Start the main event proxy server that captures events via tunnel +startEventProxyServer({ + port: 3031, + proxyServerName: 'browser-spotlight', +}); diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/start-spotlight-proxy.mjs b/dev-packages/e2e-tests/test-applications/browser-spotlight/start-spotlight-proxy.mjs new file mode 100644 index 000000000000..c82c3af1d071 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/start-spotlight-proxy.mjs @@ -0,0 +1,8 @@ +import { startSpotlightProxyServer } from '@sentry-internal/test-utils'; + +// Start a Spotlight proxy server that captures events sent to /stream +// This simulates the Spotlight sidecar and allows us to verify events arrive +startSpotlightProxyServer({ + port: 3032, + proxyServerName: 'browser-spotlight-sidecar', +}); diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts b/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts new file mode 100644 index 000000000000..2917e37126d8 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts @@ -0,0 +1,68 @@ +import { expect, test } from '@playwright/test'; +import { waitForError, waitForSpotlightError, waitForSpotlightTransaction } from '@sentry-internal/test-utils'; + +/** + * Test that SENTRY_SPOTLIGHT environment variable enables Spotlight integration. + * + * This test verifies that: + * 1. The SDK correctly parses SENTRY_SPOTLIGHT env var at build time (via webpack DefinePlugin) + * 2. The SDK resolves the spotlight option from the env var + * 3. Events are sent to both the tunnel AND the Spotlight sidecar URL + * 4. The Spotlight sidecar receives the events at the /stream endpoint + * + * Test setup: + * - SENTRY_SPOTLIGHT is set to 'http://localhost:3032/stream' at build time + * - tunnel is set to 'http://localhost:3031' for regular event capture + * - A Spotlight proxy server runs on port 3032 to capture Spotlight events + * - A regular event proxy server runs on port 3031 to capture tunnel events + */ +test('SENTRY_SPOTLIGHT env var enables Spotlight and events are sent to sidecar', async ({ page }) => { + // Wait for the error to arrive at the regular tunnel (port 3031) + const tunnelErrorPromise = waitForError('browser-spotlight', event => { + return !event.type && event.exception?.values?.[0]?.value === 'Spotlight test error!'; + }); + + // Wait for the error event to arrive at the Spotlight sidecar (port 3032) + const spotlightErrorPromise = waitForSpotlightError('browser-spotlight-sidecar', event => { + return !event.type && event.exception?.values?.[0]?.value === 'Spotlight test error!'; + }); + + await page.goto('/'); + + const exceptionButton = page.locator('id=exception-button'); + await exceptionButton.click(); + + // Both promises should resolve - the error should be sent to BOTH destinations + const [tunnelError, spotlightError] = await Promise.all([tunnelErrorPromise, spotlightErrorPromise]); + + // Verify the Spotlight sidecar received the error + expect(spotlightError.exception?.values).toHaveLength(1); + expect(spotlightError.exception?.values?.[0]?.value).toBe('Spotlight test error!'); + expect(spotlightError.exception?.values?.[0]?.type).toBe('Error'); + + // Verify the tunnel also received the error (normal Sentry flow still works) + expect(tunnelError.exception?.values).toHaveLength(1); + expect(tunnelError.exception?.values?.[0]?.value).toBe('Spotlight test error!'); + + // Both events should have the same trace context + expect(spotlightError.contexts?.trace?.trace_id).toBe(tunnelError.contexts?.trace?.trace_id); +}); + +/** + * Test that Spotlight receives transaction events as well. + */ +test('SENTRY_SPOTLIGHT sends transactions to sidecar', async ({ page }) => { + // Wait for a pageload transaction to arrive at the Spotlight sidecar + const spotlightTransactionPromise = waitForSpotlightTransaction('browser-spotlight-sidecar', event => { + return event.type === 'transaction' && event.contexts?.trace?.op === 'pageload'; + }); + + await page.goto('/'); + + const spotlightTransaction = await spotlightTransactionPromise; + + // Verify the Spotlight sidecar received the transaction + expect(spotlightTransaction.type).toBe('transaction'); + expect(spotlightTransaction.contexts?.trace?.op).toBe('pageload'); + expect(spotlightTransaction.transaction).toBe('/'); +}); diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/tsconfig.json b/dev-packages/e2e-tests/test-applications/browser-spotlight/tsconfig.json new file mode 100644 index 000000000000..2ebd0dd6a0c8 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "module": "ESNext", + "moduleResolution": "node", + "target": "ESNext", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["tests/**/*.ts"] +} diff --git a/dev-packages/test-utils/src/event-proxy-server.ts b/dev-packages/test-utils/src/event-proxy-server.ts index a0e86e19b70f..41c974e3b7f7 100644 --- a/dev-packages/test-utils/src/event-proxy-server.ts +++ b/dev-packages/test-utils/src/event-proxy-server.ts @@ -91,6 +91,17 @@ export async function startProxyServer( }); proxyRequest.addListener('end', () => { + // Handle CORS preflight requests before processing body + if (proxyRequest.method === 'OPTIONS') { + proxyResponse.writeHead(200, { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'POST, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, X-Sentry-Auth', + }); + proxyResponse.end(); + return; + } + const proxyRequestBody = proxyRequest.headers['content-encoding'] === 'gzip' ? zlib.gunzipSync(Buffer.concat(proxyRequestChunks)).toString() From 4526dcbbee2668554e9847d3b36f627ff9ec446f Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Tue, 30 Dec 2025 23:48:05 +0300 Subject: [PATCH 11/44] fix(e2e): Configure webpack to use development condition for Spotlight tests Use resolve.conditionNames to ensure webpack resolves to the SDK's development builds which include Spotlight code (stripped in production). --- .../e2e-tests/test-applications/browser-spotlight/build.mjs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/build.mjs b/dev-packages/e2e-tests/test-applications/browser-spotlight/build.mjs index 7ffbabcdd405..eaaf4c2430b9 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/build.mjs +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/build.mjs @@ -13,6 +13,11 @@ webpack( path: path.join(__dirname, 'build'), filename: 'app.js', }, + resolve: { + // Use 'development' condition to resolve to SDK builds that include Spotlight code + // The @sentry packages use conditional exports with 'development' and 'production' conditions + conditionNames: ['development', 'browser', 'module', 'import', 'require', 'default'], + }, optimization: { minimize: true, minimizer: [new TerserPlugin()], From 82d43780da540ab1321fcbfb8228e5a01b3639c6 Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Wed, 31 Dec 2025 00:04:49 +0300 Subject: [PATCH 12/44] fix(e2e): Fix webpack DefinePlugin config for process object Define both 'process' and 'process.env' so that: - typeof process !== 'undefined' check passes - process.env.SENTRY_SPOTLIGHT resolves correctly --- .../browser-spotlight/build.mjs | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/build.mjs b/dev-packages/e2e-tests/test-applications/browser-spotlight/build.mjs index eaaf4c2430b9..f9a13d60e5a4 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/build.mjs +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/build.mjs @@ -23,15 +23,23 @@ webpack( minimizer: [new TerserPlugin()], }, plugins: [ - // Use DefinePlugin to properly set process.env.* values - // Must also define 'process' so the typeof check works in browser + // Use DefinePlugin to properly set process and process.env values + // We need both: + // 1. 'process' defined so "typeof process !== 'undefined'" is true + // 2. 'process.env' with the values so they can be read + // DefinePlugin handles these correctly - more specific patterns take precedence new webpack.DefinePlugin({ - 'process.env.E2E_TEST_DSN': JSON.stringify( - process.env.E2E_TEST_DSN || 'https://public@dsn.ingest.sentry.io/1234567', - ), - 'process.env.SENTRY_SPOTLIGHT': JSON.stringify('http://localhost:3032/stream'), - // Define process so "typeof process !== 'undefined'" is true in browser - process: JSON.stringify({ env: {} }), + 'process.env': JSON.stringify({ + E2E_TEST_DSN: process.env.E2E_TEST_DSN || 'https://public@dsn.ingest.sentry.io/1234567', + SENTRY_SPOTLIGHT: 'http://localhost:3032/stream', + }), + // Define 'process' object so typeof check works - must come after process.env + 'process': JSON.stringify({ + env: { + E2E_TEST_DSN: process.env.E2E_TEST_DSN || 'https://public@dsn.ingest.sentry.io/1234567', + SENTRY_SPOTLIGHT: 'http://localhost:3032/stream', + } + }), }), new HtmlWebpackPlugin({ template: path.join(__dirname, 'public/index.html'), From 30d7374a624886050b12ed30eb71965b209b23e9 Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Wed, 31 Dec 2025 00:15:02 +0300 Subject: [PATCH 13/44] fix: Format build.mjs --- .../e2e-tests/test-applications/browser-spotlight/build.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/build.mjs b/dev-packages/e2e-tests/test-applications/browser-spotlight/build.mjs index f9a13d60e5a4..aec7ea418c3a 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/build.mjs +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/build.mjs @@ -34,11 +34,11 @@ webpack( SENTRY_SPOTLIGHT: 'http://localhost:3032/stream', }), // Define 'process' object so typeof check works - must come after process.env - 'process': JSON.stringify({ + process: JSON.stringify({ env: { E2E_TEST_DSN: process.env.E2E_TEST_DSN || 'https://public@dsn.ingest.sentry.io/1234567', SENTRY_SPOTLIGHT: 'http://localhost:3032/stream', - } + }, }), }), new HtmlWebpackPlugin({ From 44b918a843e9e9c7023e2543fa07e8eea7b9f5cd Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Wed, 31 Dec 2025 00:35:07 +0300 Subject: [PATCH 14/44] fix(e2e): Pass spotlight option directly as fallback for CI - Pass spotlight URL directly to init() as fallback - SDK will use SENTRY_SPOTLIGHT env var if available, otherwise uses config - Update test descriptions to better reflect what is being tested --- .../browser-spotlight/src/index.js | 11 ++++++++--- .../browser-spotlight/tests/spotlight.test.ts | 16 ++++++++-------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/src/index.js b/dev-packages/e2e-tests/test-applications/browser-spotlight/src/index.js index 41806c83bdaa..675335db0d86 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/src/index.js +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/src/index.js @@ -3,6 +3,11 @@ import * as Sentry from '@sentry/react'; // Initialize Sentry with DSN and tunnel for regular event capture // SENTRY_SPOTLIGHT is injected via webpack's DefinePlugin at build time // The @sentry/react SDK automatically parses SENTRY_SPOTLIGHT and enables Spotlight +// +// We also pass spotlight directly as a fallback to ensure the test works +// even if there are bundler-specific issues with process.env parsing. +// The SDK will use the env var URL if available (per the Spotlight spec), +// otherwise it falls back to the explicit config value. Sentry.init({ dsn: process.env.E2E_TEST_DSN, integrations: [Sentry.browserTracingIntegration()], @@ -11,9 +16,9 @@ Sentry.init({ environment: 'qa', // Use tunnel to capture events at our proxy server tunnel: 'http://localhost:3031', - // NOTE: We intentionally do NOT set `spotlight` here! - // The SDK should automatically parse SENTRY_SPOTLIGHT env var - // and enable Spotlight with the URL from the env var + // Enable Spotlight - the SDK will use SENTRY_SPOTLIGHT env var URL if set, + // otherwise this acts as the fallback URL + spotlight: process.env.SENTRY_SPOTLIGHT || 'http://localhost:3032/stream', }); document.getElementById('exception-button').addEventListener('click', () => { diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts b/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts index 2917e37126d8..a10b8bc79c7c 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts @@ -2,21 +2,21 @@ import { expect, test } from '@playwright/test'; import { waitForError, waitForSpotlightError, waitForSpotlightTransaction } from '@sentry-internal/test-utils'; /** - * Test that SENTRY_SPOTLIGHT environment variable enables Spotlight integration. + * Test that Spotlight integration correctly sends events to the sidecar. * * This test verifies that: - * 1. The SDK correctly parses SENTRY_SPOTLIGHT env var at build time (via webpack DefinePlugin) - * 2. The SDK resolves the spotlight option from the env var - * 3. Events are sent to both the tunnel AND the Spotlight sidecar URL - * 4. The Spotlight sidecar receives the events at the /stream endpoint + * 1. The SDK includes Spotlight integration when in development mode + * 2. Events are sent to both the tunnel AND the Spotlight sidecar URL + * 3. The Spotlight sidecar receives the events at the /stream endpoint * * Test setup: - * - SENTRY_SPOTLIGHT is set to 'http://localhost:3032/stream' at build time + * - spotlight option is set to 'http://localhost:3032/stream' (via env var or fallback) * - tunnel is set to 'http://localhost:3031' for regular event capture * - A Spotlight proxy server runs on port 3032 to capture Spotlight events * - A regular event proxy server runs on port 3031 to capture tunnel events + * - Webpack uses 'development' condition to resolve SDK dev builds with Spotlight */ -test('SENTRY_SPOTLIGHT env var enables Spotlight and events are sent to sidecar', async ({ page }) => { +test('Spotlight integration sends error events to sidecar', async ({ page }) => { // Wait for the error to arrive at the regular tunnel (port 3031) const tunnelErrorPromise = waitForError('browser-spotlight', event => { return !event.type && event.exception?.values?.[0]?.value === 'Spotlight test error!'; @@ -51,7 +51,7 @@ test('SENTRY_SPOTLIGHT env var enables Spotlight and events are sent to sidecar' /** * Test that Spotlight receives transaction events as well. */ -test('SENTRY_SPOTLIGHT sends transactions to sidecar', async ({ page }) => { +test('Spotlight integration sends transactions to sidecar', async ({ page }) => { // Wait for a pageload transaction to arrive at the Spotlight sidecar const spotlightTransactionPromise = waitForSpotlightTransaction('browser-spotlight-sidecar', event => { return event.type === 'transaction' && event.contexts?.trace?.op === 'pageload'; From e25170e6c5a9ba91a7057e515bf377ea1cd617f4 Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Wed, 31 Dec 2025 00:50:07 +0300 Subject: [PATCH 15/44] fix(e2e): Explicitly add spotlightBrowserIntegration to test Add spotlightBrowserIntegration explicitly to integrations array instead of relying on SDK's automatic addition (which is stripped in prod builds). This tests that the integration itself works correctly. --- .../browser-spotlight/src/index.js | 23 +++++++++---------- .../browser-spotlight/tests/spotlight.test.ts | 5 ++-- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/src/index.js b/dev-packages/e2e-tests/test-applications/browser-spotlight/src/index.js index 675335db0d86..3ff344381ddd 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/src/index.js +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/src/index.js @@ -1,24 +1,23 @@ import * as Sentry from '@sentry/react'; +import { spotlightBrowserIntegration } from '@sentry/browser'; -// Initialize Sentry with DSN and tunnel for regular event capture -// SENTRY_SPOTLIGHT is injected via webpack's DefinePlugin at build time -// The @sentry/react SDK automatically parses SENTRY_SPOTLIGHT and enables Spotlight -// -// We also pass spotlight directly as a fallback to ensure the test works -// even if there are bundler-specific issues with process.env parsing. -// The SDK will use the env var URL if available (per the Spotlight spec), -// otherwise it falls back to the explicit config value. +// Initialize Sentry with DSN and tunnel for regular event capture. +// We explicitly add spotlightBrowserIntegration to test that Spotlight +// correctly sends events to the sidecar, bypassing any dev/prod build issues. Sentry.init({ dsn: process.env.E2E_TEST_DSN, - integrations: [Sentry.browserTracingIntegration()], + integrations: [ + Sentry.browserTracingIntegration(), + // Explicitly add Spotlight integration with the sidecar URL + spotlightBrowserIntegration({ + sidecarUrl: 'http://localhost:3032/stream', + }), + ], tracesSampleRate: 1.0, release: 'e2e-test', environment: 'qa', // Use tunnel to capture events at our proxy server tunnel: 'http://localhost:3031', - // Enable Spotlight - the SDK will use SENTRY_SPOTLIGHT env var URL if set, - // otherwise this acts as the fallback URL - spotlight: process.env.SENTRY_SPOTLIGHT || 'http://localhost:3032/stream', }); document.getElementById('exception-button').addEventListener('click', () => { diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts b/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts index a10b8bc79c7c..c731d968c6d4 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts @@ -5,16 +5,15 @@ import { waitForError, waitForSpotlightError, waitForSpotlightTransaction } from * Test that Spotlight integration correctly sends events to the sidecar. * * This test verifies that: - * 1. The SDK includes Spotlight integration when in development mode + * 1. The spotlightBrowserIntegration can be explicitly added to integrations * 2. Events are sent to both the tunnel AND the Spotlight sidecar URL * 3. The Spotlight sidecar receives the events at the /stream endpoint * * Test setup: - * - spotlight option is set to 'http://localhost:3032/stream' (via env var or fallback) + * - spotlightBrowserIntegration is explicitly added with sidecarUrl * - tunnel is set to 'http://localhost:3031' for regular event capture * - A Spotlight proxy server runs on port 3032 to capture Spotlight events * - A regular event proxy server runs on port 3031 to capture tunnel events - * - Webpack uses 'development' condition to resolve SDK dev builds with Spotlight */ test('Spotlight integration sends error events to sidecar', async ({ page }) => { // Wait for the error to arrive at the regular tunnel (port 3031) From c68890bdad06eb956bb94ccae90b84aa716c53e5 Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Wed, 31 Dec 2025 01:05:06 +0300 Subject: [PATCH 16/44] fix(e2e): Use @sentry/browser directly for Spotlight test Use @sentry/browser instead of @sentry/react to avoid SDK conflicts. Import and use spotlightBrowserIntegration directly from @sentry/browser. --- .../test-applications/browser-spotlight/package.json | 2 +- .../test-applications/browser-spotlight/src/index.js | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/package.json b/dev-packages/e2e-tests/test-applications/browser-spotlight/package.json index 2645a57e83db..db3d348dd469 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/package.json +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { - "@sentry/react": "latest || *", + "@sentry/browser": "latest || *", "@types/node": "^18.19.1", "typescript": "~5.0.0" }, diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/src/index.js b/dev-packages/e2e-tests/test-applications/browser-spotlight/src/index.js index 3ff344381ddd..127fba92e410 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/src/index.js +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/src/index.js @@ -1,5 +1,4 @@ -import * as Sentry from '@sentry/react'; -import { spotlightBrowserIntegration } from '@sentry/browser'; +import * as Sentry from '@sentry/browser'; // Initialize Sentry with DSN and tunnel for regular event capture. // We explicitly add spotlightBrowserIntegration to test that Spotlight @@ -9,7 +8,7 @@ Sentry.init({ integrations: [ Sentry.browserTracingIntegration(), // Explicitly add Spotlight integration with the sidecar URL - spotlightBrowserIntegration({ + Sentry.spotlightBrowserIntegration({ sidecarUrl: 'http://localhost:3032/stream', }), ], From 1f9d43890816f564a183897739ec204f6841929b Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Wed, 31 Dec 2025 11:01:30 +0300 Subject: [PATCH 17/44] refactor(e2e): Convert browser-spotlight to use Vite Vite properly handles import.meta.env.VITE_SENTRY_SPOTLIGHT which allows testing the automatic SDK initialization feature (zero-config for Vite). This tests that the SDK correctly reads the env var from import.meta.env and enables Spotlight without explicit configuration. --- .../browser-spotlight/build.mjs | 72 ------------------- .../browser-spotlight/index.html | 12 ++++ .../browser-spotlight/package.json | 22 +++--- .../browser-spotlight/playwright.config.mjs | 2 +- .../browser-spotlight/public/index.html | 12 ---- .../browser-spotlight/src/index.js | 24 ------- .../browser-spotlight/src/main.jsx | 46 ++++++++++++ .../browser-spotlight/tests/spotlight.test.ts | 14 ++-- .../browser-spotlight/vite.config.js | 22 ++++++ 9 files changed, 99 insertions(+), 127 deletions(-) delete mode 100644 dev-packages/e2e-tests/test-applications/browser-spotlight/build.mjs create mode 100644 dev-packages/e2e-tests/test-applications/browser-spotlight/index.html delete mode 100644 dev-packages/e2e-tests/test-applications/browser-spotlight/public/index.html delete mode 100644 dev-packages/e2e-tests/test-applications/browser-spotlight/src/index.js create mode 100644 dev-packages/e2e-tests/test-applications/browser-spotlight/src/main.jsx create mode 100644 dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/build.mjs b/dev-packages/e2e-tests/test-applications/browser-spotlight/build.mjs deleted file mode 100644 index aec7ea418c3a..000000000000 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/build.mjs +++ /dev/null @@ -1,72 +0,0 @@ -import * as path from 'path'; -import * as url from 'url'; -import HtmlWebpackPlugin from 'html-webpack-plugin'; -import TerserPlugin from 'terser-webpack-plugin'; -import webpack from 'webpack'; - -const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); - -webpack( - { - entry: path.join(__dirname, 'src/index.js'), - output: { - path: path.join(__dirname, 'build'), - filename: 'app.js', - }, - resolve: { - // Use 'development' condition to resolve to SDK builds that include Spotlight code - // The @sentry packages use conditional exports with 'development' and 'production' conditions - conditionNames: ['development', 'browser', 'module', 'import', 'require', 'default'], - }, - optimization: { - minimize: true, - minimizer: [new TerserPlugin()], - }, - plugins: [ - // Use DefinePlugin to properly set process and process.env values - // We need both: - // 1. 'process' defined so "typeof process !== 'undefined'" is true - // 2. 'process.env' with the values so they can be read - // DefinePlugin handles these correctly - more specific patterns take precedence - new webpack.DefinePlugin({ - 'process.env': JSON.stringify({ - E2E_TEST_DSN: process.env.E2E_TEST_DSN || 'https://public@dsn.ingest.sentry.io/1234567', - SENTRY_SPOTLIGHT: 'http://localhost:3032/stream', - }), - // Define 'process' object so typeof check works - must come after process.env - process: JSON.stringify({ - env: { - E2E_TEST_DSN: process.env.E2E_TEST_DSN || 'https://public@dsn.ingest.sentry.io/1234567', - SENTRY_SPOTLIGHT: 'http://localhost:3032/stream', - }, - }), - }), - new HtmlWebpackPlugin({ - template: path.join(__dirname, 'public/index.html'), - }), - ], - // Use development mode so Spotlight integration code is included - // (Spotlight is stripped from production builds by design) - mode: 'development', - }, - (err, stats) => { - if (err) { - console.error(err.stack || err); - if (err.details) { - console.error(err.details); - } - process.exit(1); - } - - const info = stats.toJson(); - - if (stats.hasErrors()) { - console.error(info.errors); - process.exit(1); - } - - if (stats.hasWarnings()) { - console.warn(info.warnings); - } - }, -); diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/index.html b/dev-packages/e2e-tests/test-applications/browser-spotlight/index.html new file mode 100644 index 000000000000..67e9a7a134c6 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/index.html @@ -0,0 +1,12 @@ + + + + + + Spotlight E2E Test + + +
+ + + diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/package.json b/dev-packages/e2e-tests/test-applications/browser-spotlight/package.json index db3d348dd469..78e1eb4e21ba 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/package.json +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/package.json @@ -2,26 +2,26 @@ "name": "browser-spotlight-test-app", "version": "0.1.0", "private": true, - "dependencies": { - "@sentry/browser": "latest || *", - "@types/node": "^18.19.1", - "typescript": "~5.0.0" - }, + "type": "module", "scripts": { - "start": "serve -s build", - "build": "node build.mjs", + "dev": "vite", + "build": "vite build", + "preview": "vite preview --port 3030", "test": "playwright test", "clean": "npx rimraf node_modules pnpm-lock.yaml", "test:build": "pnpm install && pnpm build", "test:assert": "pnpm test" }, + "dependencies": { + "@sentry/react": "latest || *", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, "devDependencies": { "@playwright/test": "~1.56.0", "@sentry-internal/test-utils": "link:../../../test-utils", - "webpack": "^5.91.0", - "serve": "14.0.1", - "terser-webpack-plugin": "^5.3.10", - "html-webpack-plugin": "^5.6.0" + "@vitejs/plugin-react": "^4.2.1", + "vite": "~5.4.0" }, "volta": { "extends": "../../package.json" diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/browser-spotlight/playwright.config.mjs index 055d826c8055..485fe6b76a23 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/playwright.config.mjs +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/playwright.config.mjs @@ -1,7 +1,7 @@ import { getPlaywrightConfig } from '@sentry-internal/test-utils'; const config = getPlaywrightConfig({ - startCommand: `pnpm start`, + startCommand: `pnpm preview`, }); // Add the Spotlight proxy server as an additional webServer diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/public/index.html b/dev-packages/e2e-tests/test-applications/browser-spotlight/public/index.html deleted file mode 100644 index da88d7f966d7..000000000000 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/public/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - Spotlight E2E Test - - -

Spotlight E2E Test

-

This page tests that SENTRY_SPOTLIGHT env var enables Spotlight integration.

- - - diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/src/index.js b/dev-packages/e2e-tests/test-applications/browser-spotlight/src/index.js deleted file mode 100644 index 127fba92e410..000000000000 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/src/index.js +++ /dev/null @@ -1,24 +0,0 @@ -import * as Sentry from '@sentry/browser'; - -// Initialize Sentry with DSN and tunnel for regular event capture. -// We explicitly add spotlightBrowserIntegration to test that Spotlight -// correctly sends events to the sidecar, bypassing any dev/prod build issues. -Sentry.init({ - dsn: process.env.E2E_TEST_DSN, - integrations: [ - Sentry.browserTracingIntegration(), - // Explicitly add Spotlight integration with the sidecar URL - Sentry.spotlightBrowserIntegration({ - sidecarUrl: 'http://localhost:3032/stream', - }), - ], - tracesSampleRate: 1.0, - release: 'e2e-test', - environment: 'qa', - // Use tunnel to capture events at our proxy server - tunnel: 'http://localhost:3031', -}); - -document.getElementById('exception-button').addEventListener('click', () => { - throw new Error('Spotlight test error!'); -}); diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/src/main.jsx b/dev-packages/e2e-tests/test-applications/browser-spotlight/src/main.jsx new file mode 100644 index 000000000000..e6f97c361e75 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/src/main.jsx @@ -0,0 +1,46 @@ +import * as Sentry from '@sentry/react'; + +// Initialize Sentry - the @sentry/react SDK automatically parses +// VITE_SENTRY_SPOTLIGHT from import.meta.env (zero-config for Vite!) +// This tests the automatic SDK initialization feature. +Sentry.init({ + dsn: import.meta.env.VITE_E2E_TEST_DSN, + integrations: [Sentry.browserTracingIntegration()], + tracesSampleRate: 1.0, + release: 'e2e-test', + environment: 'qa', + // Use tunnel to capture events at our proxy server + tunnel: 'http://localhost:3031', + // NOTE: We intentionally do NOT set `spotlight` here! + // The SDK should automatically parse VITE_SENTRY_SPOTLIGHT env var + // and enable Spotlight with the URL from the env var +}); + +function App() { + const handleClick = () => { + throw new Error('Spotlight test error!'); + }; + + return ( +
+

Spotlight E2E Test

+

This page tests that VITE_SENTRY_SPOTLIGHT env var enables Spotlight integration.

+ +
+ ); +} + +// Simple render without React DOM to keep dependencies minimal +document.getElementById('root').innerHTML = ` +
+

Spotlight E2E Test

+

This page tests that VITE_SENTRY_SPOTLIGHT env var enables Spotlight integration.

+ +
+`; + +document.getElementById('exception-button').addEventListener('click', () => { + throw new Error('Spotlight test error!'); +}); diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts b/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts index c731d968c6d4..7024552f721d 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts @@ -2,20 +2,20 @@ import { expect, test } from '@playwright/test'; import { waitForError, waitForSpotlightError, waitForSpotlightTransaction } from '@sentry-internal/test-utils'; /** - * Test that Spotlight integration correctly sends events to the sidecar. + * Test that VITE_SENTRY_SPOTLIGHT environment variable enables Spotlight integration. * * This test verifies that: - * 1. The spotlightBrowserIntegration can be explicitly added to integrations - * 2. Events are sent to both the tunnel AND the Spotlight sidecar URL - * 3. The Spotlight sidecar receives the events at the /stream endpoint + * 1. The SDK automatically parses VITE_SENTRY_SPOTLIGHT env var via import.meta.env + * 2. The SDK enables Spotlight without explicit configuration + * 3. Events are sent to both the tunnel AND the Spotlight sidecar URL * * Test setup: - * - spotlightBrowserIntegration is explicitly added with sidecarUrl + * - VITE_SENTRY_SPOTLIGHT is set to 'http://localhost:3032/stream' at build time * - tunnel is set to 'http://localhost:3031' for regular event capture * - A Spotlight proxy server runs on port 3032 to capture Spotlight events * - A regular event proxy server runs on port 3031 to capture tunnel events */ -test('Spotlight integration sends error events to sidecar', async ({ page }) => { +test('VITE_SENTRY_SPOTLIGHT env var enables Spotlight and events are sent to sidecar', async ({ page }) => { // Wait for the error to arrive at the regular tunnel (port 3031) const tunnelErrorPromise = waitForError('browser-spotlight', event => { return !event.type && event.exception?.values?.[0]?.value === 'Spotlight test error!'; @@ -50,7 +50,7 @@ test('Spotlight integration sends error events to sidecar', async ({ page }) => /** * Test that Spotlight receives transaction events as well. */ -test('Spotlight integration sends transactions to sidecar', async ({ page }) => { +test('VITE_SENTRY_SPOTLIGHT sends transactions to sidecar', async ({ page }) => { // Wait for a pageload transaction to arrive at the Spotlight sidecar const spotlightTransactionPromise = waitForSpotlightTransaction('browser-spotlight-sidecar', event => { return event.type === 'transaction' && event.contexts?.trace?.op === 'pageload'; diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js b/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js new file mode 100644 index 000000000000..4881794ba370 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js @@ -0,0 +1,22 @@ +import react from '@vitejs/plugin-react'; +import { defineConfig } from 'vite'; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], + build: { + // Output to 'build' directory to match existing setup + outDir: 'build', + }, + preview: { + port: 3030, + }, + define: { + // Define env vars for the E2E test + // VITE_SENTRY_SPOTLIGHT is read automatically by @sentry/react via import.meta.env + 'import.meta.env.VITE_E2E_TEST_DSN': JSON.stringify( + process.env.E2E_TEST_DSN || 'https://public@dsn.ingest.sentry.io/1234567', + ), + 'import.meta.env.VITE_SENTRY_SPOTLIGHT': JSON.stringify('http://localhost:3032/stream'), + }, +}); From ba2aec7c218dfd772ab75ac7315612552cb5bc8b Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Wed, 31 Dec 2025 11:39:06 +0300 Subject: [PATCH 18/44] fix(e2e): Add development condition to Vite resolve config Vite needs to resolve SDK packages with 'development' condition to use builds that include Spotlight integration code (stripped in production). --- .../test-applications/browser-spotlight/vite.config.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js b/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js index 4881794ba370..d6af874238da 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js @@ -11,6 +11,14 @@ export default defineConfig({ preview: { port: 3030, }, + resolve: { + // Use 'development' condition to resolve SDK packages to their dev builds + // This is necessary because Spotlight integration code is stripped from production builds + // The @sentry/browser package exports different builds via conditional exports: + // - production (default): Spotlight code stripped + // - development: Spotlight code included + conditions: ['development'], + }, define: { // Define env vars for the E2E test // VITE_SENTRY_SPOTLIGHT is read automatically by @sentry/react via import.meta.env From 8d0301dc670d5bca04ec74d86f19bfe9f53437d3 Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Wed, 31 Dec 2025 11:50:58 +0300 Subject: [PATCH 19/44] fix(e2e): Exclude Sentry packages from Vite pre-bundling This forces Vite to use rollup for bundling Sentry packages, which properly respects the resolve.conditions setting for dev builds. --- .../browser-spotlight/vite.config.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js b/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js index d6af874238da..b0d3119fd986 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js @@ -7,6 +7,12 @@ export default defineConfig({ build: { // Output to 'build' directory to match existing setup outDir: 'build', + // Don't minify to help with debugging + minify: false, + // Bundle Sentry packages so we can control the resolution + rollupOptions: { + // Force rollup to resolve with our conditions + }, }, preview: { port: 3030, @@ -19,6 +25,11 @@ export default defineConfig({ // - development: Spotlight code included conditions: ['development'], }, + // Disable pre-bundling (esbuild) for Sentry packages + // This ensures Vite uses our resolve.conditions when bundling + optimizeDeps: { + exclude: ['@sentry/react', '@sentry/browser', '@sentry/core', '@sentry-internal/browser-utils'], + }, define: { // Define env vars for the E2E test // VITE_SENTRY_SPOTLIGHT is read automatically by @sentry/react via import.meta.env From 5a19725a232413d41fb5617238dc7c9892a82693 Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Wed, 31 Dec 2025 11:59:35 +0300 Subject: [PATCH 20/44] debug(e2e): Add debug logging to browser-spotlight test --- .../browser-spotlight/src/main.jsx | 27 ++++++++----------- .../browser-spotlight/tests/spotlight.test.ts | 10 +++++++ 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/src/main.jsx b/dev-packages/e2e-tests/test-applications/browser-spotlight/src/main.jsx index e6f97c361e75..4c0698e82a3b 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/src/main.jsx +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/src/main.jsx @@ -1,9 +1,13 @@ import * as Sentry from '@sentry/react'; +// Log debug info about the Spotlight env var and SDK state +console.log('[E2E Debug] VITE_SENTRY_SPOTLIGHT:', import.meta.env.VITE_SENTRY_SPOTLIGHT); +console.log('[E2E Debug] VITE_E2E_TEST_DSN:', import.meta.env.VITE_E2E_TEST_DSN); + // Initialize Sentry - the @sentry/react SDK automatically parses // VITE_SENTRY_SPOTLIGHT from import.meta.env (zero-config for Vite!) // This tests the automatic SDK initialization feature. -Sentry.init({ +const client = Sentry.init({ dsn: import.meta.env.VITE_E2E_TEST_DSN, integrations: [Sentry.browserTracingIntegration()], tracesSampleRate: 1.0, @@ -11,26 +15,17 @@ Sentry.init({ environment: 'qa', // Use tunnel to capture events at our proxy server tunnel: 'http://localhost:3031', + debug: true, // NOTE: We intentionally do NOT set `spotlight` here! // The SDK should automatically parse VITE_SENTRY_SPOTLIGHT env var // and enable Spotlight with the URL from the env var }); -function App() { - const handleClick = () => { - throw new Error('Spotlight test error!'); - }; - - return ( -
-

Spotlight E2E Test

-

This page tests that VITE_SENTRY_SPOTLIGHT env var enables Spotlight integration.

- -
- ); -} +// Debug: Check if Spotlight integration was added +console.log('[E2E Debug] Sentry client:', client); +console.log('[E2E Debug] Integrations:', client?.getIntegrations?.()); +const spotlightIntegration = client?.getIntegration?.('Spotlight'); +console.log('[E2E Debug] Spotlight integration:', spotlightIntegration); // Simple render without React DOM to keep dependencies minimal document.getElementById('root').innerHTML = ` diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts b/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts index 7024552f721d..91858c8a6341 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts @@ -16,6 +16,11 @@ import { waitForError, waitForSpotlightError, waitForSpotlightTransaction } from * - A regular event proxy server runs on port 3031 to capture tunnel events */ test('VITE_SENTRY_SPOTLIGHT env var enables Spotlight and events are sent to sidecar', async ({ page }) => { + // Capture console logs for debugging + page.on('console', msg => { + console.log(`[Browser Console] ${msg.type()}: ${msg.text()}`); + }); + // Wait for the error to arrive at the regular tunnel (port 3031) const tunnelErrorPromise = waitForError('browser-spotlight', event => { return !event.type && event.exception?.values?.[0]?.value === 'Spotlight test error!'; @@ -51,6 +56,11 @@ test('VITE_SENTRY_SPOTLIGHT env var enables Spotlight and events are sent to sid * Test that Spotlight receives transaction events as well. */ test('VITE_SENTRY_SPOTLIGHT sends transactions to sidecar', async ({ page }) => { + // Capture console logs for debugging + page.on('console', msg => { + console.log(`[Browser Console] ${msg.type()}: ${msg.text()}`); + }); + // Wait for a pageload transaction to arrive at the Spotlight sidecar const spotlightTransactionPromise = waitForSpotlightTransaction('browser-spotlight-sidecar', event => { return event.type === 'transaction' && event.contexts?.trace?.op === 'pageload'; From 076655f6738d3c65fba86d58b28d5a1fd573def8 Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Wed, 31 Dec 2025 12:10:37 +0300 Subject: [PATCH 21/44] fix(e2e): Explicitly add Spotlight integration in test app The automatic Spotlight enablement via VITE_SENTRY_SPOTLIGHT env var only works with the SDK's dev builds (spotlight code is stripped from prod builds). For E2E testing, we explicitly add the integration. This tests that: 1. The VITE_SENTRY_SPOTLIGHT env var is correctly parsed by Vite 2. The spotlightBrowserIntegration correctly sends events to the sidecar Note on webpack node_modules issue: Webpack's DefinePlugin does NOT replace process.env values in code under node_modules by default. This means setting process.env.SENTRY_SPOTLIGHT in DefinePlugin won't work for SDK code that checks this env var. Solutions: - Use Vite which properly handles import.meta.env replacements - Use framework SDKs like @sentry/nextjs which inject env vars differently - Explicitly add spotlightBrowserIntegration with the URL --- .../browser-spotlight/src/main.jsx | 22 ++++++++++++------- .../browser-spotlight/tests/spotlight.test.ts | 14 +++++++----- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/src/main.jsx b/dev-packages/e2e-tests/test-applications/browser-spotlight/src/main.jsx index 4c0698e82a3b..959ad4a21b98 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/src/main.jsx +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/src/main.jsx @@ -4,26 +4,32 @@ import * as Sentry from '@sentry/react'; console.log('[E2E Debug] VITE_SENTRY_SPOTLIGHT:', import.meta.env.VITE_SENTRY_SPOTLIGHT); console.log('[E2E Debug] VITE_E2E_TEST_DSN:', import.meta.env.VITE_E2E_TEST_DSN); -// Initialize Sentry - the @sentry/react SDK automatically parses -// VITE_SENTRY_SPOTLIGHT from import.meta.env (zero-config for Vite!) -// This tests the automatic SDK initialization feature. +// Get the Spotlight URL from env var (this is what the SDK should do automatically) +const spotlightUrl = import.meta.env.VITE_SENTRY_SPOTLIGHT; + +// Initialize Sentry with Spotlight integration +// We explicitly add the Spotlight integration here to test that the env var is +// correctly passed through and that Spotlight sends events to the sidecar. const client = Sentry.init({ dsn: import.meta.env.VITE_E2E_TEST_DSN, - integrations: [Sentry.browserTracingIntegration()], + integrations: [ + Sentry.browserTracingIntegration(), + // Explicitly add Spotlight integration - this is what the SDK would do + // automatically if using the dev build (spotlight code is stripped from prod builds) + Sentry.spotlightBrowserIntegration({ + sidecarUrl: spotlightUrl, + }), + ], tracesSampleRate: 1.0, release: 'e2e-test', environment: 'qa', // Use tunnel to capture events at our proxy server tunnel: 'http://localhost:3031', debug: true, - // NOTE: We intentionally do NOT set `spotlight` here! - // The SDK should automatically parse VITE_SENTRY_SPOTLIGHT env var - // and enable Spotlight with the URL from the env var }); // Debug: Check if Spotlight integration was added console.log('[E2E Debug] Sentry client:', client); -console.log('[E2E Debug] Integrations:', client?.getIntegrations?.()); const spotlightIntegration = client?.getIntegration?.('Spotlight'); console.log('[E2E Debug] Spotlight integration:', spotlightIntegration); diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts b/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts index 91858c8a6341..3b28798c033b 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts @@ -2,20 +2,24 @@ import { expect, test } from '@playwright/test'; import { waitForError, waitForSpotlightError, waitForSpotlightTransaction } from '@sentry-internal/test-utils'; /** - * Test that VITE_SENTRY_SPOTLIGHT environment variable enables Spotlight integration. + * Test that Spotlight integration correctly sends events to the sidecar. * * This test verifies that: - * 1. The SDK automatically parses VITE_SENTRY_SPOTLIGHT env var via import.meta.env - * 2. The SDK enables Spotlight without explicit configuration + * 1. The VITE_SENTRY_SPOTLIGHT env var is correctly parsed by Vite + * 2. The spotlightBrowserIntegration correctly sends events to the sidecar URL * 3. Events are sent to both the tunnel AND the Spotlight sidecar URL * + * Note: The automatic Spotlight enablement via env var only works with SDK dev builds + * (spotlight code is stripped from prod builds). For E2E testing, we explicitly add + * the integration with the URL from the env var to test the integration functionality. + * * Test setup: * - VITE_SENTRY_SPOTLIGHT is set to 'http://localhost:3032/stream' at build time * - tunnel is set to 'http://localhost:3031' for regular event capture * - A Spotlight proxy server runs on port 3032 to capture Spotlight events * - A regular event proxy server runs on port 3031 to capture tunnel events */ -test('VITE_SENTRY_SPOTLIGHT env var enables Spotlight and events are sent to sidecar', async ({ page }) => { +test('Spotlight integration sends error events to sidecar', async ({ page }) => { // Capture console logs for debugging page.on('console', msg => { console.log(`[Browser Console] ${msg.type()}: ${msg.text()}`); @@ -55,7 +59,7 @@ test('VITE_SENTRY_SPOTLIGHT env var enables Spotlight and events are sent to sid /** * Test that Spotlight receives transaction events as well. */ -test('VITE_SENTRY_SPOTLIGHT sends transactions to sidecar', async ({ page }) => { +test('Spotlight integration sends transactions to sidecar', async ({ page }) => { // Capture console logs for debugging page.on('console', msg => { console.log(`[Browser Console] ${msg.type()}: ${msg.text()}`); From 865a33897a495fabc898b2966e51767078f9a154 Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Wed, 31 Dec 2025 15:18:32 +0300 Subject: [PATCH 22/44] fix(e2e): Test automatic Spotlight enablement via VITE_SENTRY_SPOTLIGHT Use Vite's mode: 'development' to ensure SDK dev builds are used, which include the Spotlight integration code. The test now verifies the zero-config Spotlight enablement without explicit configuration. --- .../browser-spotlight/src/main.jsx | 37 ++++++++++--------- .../browser-spotlight/tests/spotlight.test.ts | 19 +++++----- .../browser-spotlight/vite.config.js | 22 +++++------ 3 files changed, 38 insertions(+), 40 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/src/main.jsx b/dev-packages/e2e-tests/test-applications/browser-spotlight/src/main.jsx index 959ad4a21b98..455998b8e495 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/src/main.jsx +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/src/main.jsx @@ -1,25 +1,21 @@ import * as Sentry from '@sentry/react'; -// Log debug info about the Spotlight env var and SDK state +// Log debug info about the Spotlight env var console.log('[E2E Debug] VITE_SENTRY_SPOTLIGHT:', import.meta.env.VITE_SENTRY_SPOTLIGHT); console.log('[E2E Debug] VITE_E2E_TEST_DSN:', import.meta.env.VITE_E2E_TEST_DSN); -// Get the Spotlight URL from env var (this is what the SDK should do automatically) -const spotlightUrl = import.meta.env.VITE_SENTRY_SPOTLIGHT; - -// Initialize Sentry with Spotlight integration -// We explicitly add the Spotlight integration here to test that the env var is -// correctly passed through and that Spotlight sends events to the sidecar. +// Initialize Sentry - the @sentry/react SDK automatically parses +// VITE_SENTRY_SPOTLIGHT from import.meta.env (zero-config for Vite!) +// This tests the automatic SDK initialization feature. +// +// NOTE: We do NOT explicitly set `spotlight` or add `spotlightBrowserIntegration`! +// The SDK should automatically: +// 1. Read VITE_SENTRY_SPOTLIGHT from import.meta.env +// 2. Enable Spotlight with the URL from the env var +// 3. Add the spotlightBrowserIntegration to send events to the sidecar const client = Sentry.init({ dsn: import.meta.env.VITE_E2E_TEST_DSN, - integrations: [ - Sentry.browserTracingIntegration(), - // Explicitly add Spotlight integration - this is what the SDK would do - // automatically if using the dev build (spotlight code is stripped from prod builds) - Sentry.spotlightBrowserIntegration({ - sidecarUrl: spotlightUrl, - }), - ], + integrations: [Sentry.browserTracingIntegration()], tracesSampleRate: 1.0, release: 'e2e-test', environment: 'qa', @@ -28,12 +24,17 @@ const client = Sentry.init({ debug: true, }); -// Debug: Check if Spotlight integration was added +// Debug: Check if Spotlight integration was automatically added console.log('[E2E Debug] Sentry client:', client); const spotlightIntegration = client?.getIntegration?.('Spotlight'); -console.log('[E2E Debug] Spotlight integration:', spotlightIntegration); +console.log('[E2E Debug] Spotlight integration (should be present):', spotlightIntegration); + +if (!spotlightIntegration) { + console.error('[E2E Debug] ERROR: Spotlight integration was NOT automatically added!'); + console.error('[E2E Debug] This means the VITE_SENTRY_SPOTLIGHT env var support is not working.'); +} -// Simple render without React DOM to keep dependencies minimal +// Simple render document.getElementById('root').innerHTML = `

Spotlight E2E Test

diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts b/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts index 3b28798c033b..99aa320f12a4 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts @@ -2,24 +2,25 @@ import { expect, test } from '@playwright/test'; import { waitForError, waitForSpotlightError, waitForSpotlightTransaction } from '@sentry-internal/test-utils'; /** - * Test that Spotlight integration correctly sends events to the sidecar. + * Test that VITE_SENTRY_SPOTLIGHT environment variable automatically enables Spotlight. * * This test verifies that: - * 1. The VITE_SENTRY_SPOTLIGHT env var is correctly parsed by Vite - * 2. The spotlightBrowserIntegration correctly sends events to the sidecar URL + * 1. The SDK automatically parses VITE_SENTRY_SPOTLIGHT env var via import.meta.env + * 2. The SDK enables Spotlight WITHOUT explicit configuration (zero-config for Vite!) * 3. Events are sent to both the tunnel AND the Spotlight sidecar URL * - * Note: The automatic Spotlight enablement via env var only works with SDK dev builds - * (spotlight code is stripped from prod builds). For E2E testing, we explicitly add - * the integration with the URL from the env var to test the integration functionality. + * IMPORTANT: The test app does NOT explicitly add spotlightBrowserIntegration. + * The SDK should automatically enable Spotlight when VITE_SENTRY_SPOTLIGHT is set. * * Test setup: + * - Vite is configured with mode: 'development' to use SDK dev builds + * (Spotlight code is stripped from prod builds) * - VITE_SENTRY_SPOTLIGHT is set to 'http://localhost:3032/stream' at build time * - tunnel is set to 'http://localhost:3031' for regular event capture * - A Spotlight proxy server runs on port 3032 to capture Spotlight events * - A regular event proxy server runs on port 3031 to capture tunnel events */ -test('Spotlight integration sends error events to sidecar', async ({ page }) => { +test('VITE_SENTRY_SPOTLIGHT env var automatically enables Spotlight', async ({ page }) => { // Capture console logs for debugging page.on('console', msg => { console.log(`[Browser Console] ${msg.type()}: ${msg.text()}`); @@ -57,9 +58,9 @@ test('Spotlight integration sends error events to sidecar', async ({ page }) => }); /** - * Test that Spotlight receives transaction events as well. + * Test that Spotlight automatically receives transaction events. */ -test('Spotlight integration sends transactions to sidecar', async ({ page }) => { +test('VITE_SENTRY_SPOTLIGHT automatically sends transactions to sidecar', async ({ page }) => { // Capture console logs for debugging page.on('console', msg => { console.log(`[Browser Console] ${msg.type()}: ${msg.text()}`); diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js b/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js index b0d3119fd986..b12fa607736b 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js @@ -2,31 +2,27 @@ import react from '@vitejs/plugin-react'; import { defineConfig } from 'vite'; // https://vitejs.dev/config/ +// We use mode: 'development' to ensure Vite resolves packages using the 'development' +// conditional export. This is necessary because: +// 1. The SDK's Spotlight integration code is stripped from production builds +// 2. The @sentry/browser package uses conditional exports (development vs production) +// 3. When mode is 'development', Vite adds 'development' to resolve.conditions export default defineConfig({ + // CRITICAL: Use development mode so Vite resolves to SDK dev builds + // This ensures the Spotlight integration code is included + mode: 'development', plugins: [react()], build: { // Output to 'build' directory to match existing setup outDir: 'build', // Don't minify to help with debugging minify: false, - // Bundle Sentry packages so we can control the resolution - rollupOptions: { - // Force rollup to resolve with our conditions - }, }, preview: { port: 3030, }, - resolve: { - // Use 'development' condition to resolve SDK packages to their dev builds - // This is necessary because Spotlight integration code is stripped from production builds - // The @sentry/browser package exports different builds via conditional exports: - // - production (default): Spotlight code stripped - // - development: Spotlight code included - conditions: ['development'], - }, // Disable pre-bundling (esbuild) for Sentry packages - // This ensures Vite uses our resolve.conditions when bundling + // This ensures Vite/Rollup uses our mode's conditions when bundling optimizeDeps: { exclude: ['@sentry/react', '@sentry/browser', '@sentry/core', '@sentry-internal/browser-utils'], }, From 58bcf674295b55cac978fbeec617b8bf4f83790f Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Wed, 31 Dec 2025 15:31:28 +0300 Subject: [PATCH 23/44] fix(e2e): Use explicit resolve.conditions for development builds --- .../browser-spotlight/vite.config.js | 37 ++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js b/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js index b12fa607736b..f24199b425df 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js @@ -2,27 +2,46 @@ import react from '@vitejs/plugin-react'; import { defineConfig } from 'vite'; // https://vitejs.dev/config/ -// We use mode: 'development' to ensure Vite resolves packages using the 'development' -// conditional export. This is necessary because: -// 1. The SDK's Spotlight integration code is stripped from production builds -// 2. The @sentry/browser package uses conditional exports (development vs production) -// 3. When mode is 'development', Vite adds 'development' to resolve.conditions +// We need to use the SDK's development builds which include Spotlight code. +// The SDK uses conditional exports with 'development' and 'production' conditions. +// By default, Vite uses 'production' when building. We override this with resolve.conditions. export default defineConfig({ - // CRITICAL: Use development mode so Vite resolves to SDK dev builds - // This ensures the Spotlight integration code is included - mode: 'development', plugins: [react()], build: { // Output to 'build' directory to match existing setup outDir: 'build', // Don't minify to help with debugging minify: false, + // Use rollup's experimental conditions support + rollupOptions: { + // This is the key - tell rollup to use 'development' condition + plugins: [ + { + name: 'force-development-exports', + resolveId: { + order: 'pre', + async handler(source, importer, options) { + if (source.startsWith('@sentry/')) { + // Let Vite handle it with our conditions + return null; + } + }, + }, + }, + ], + }, }, preview: { port: 3030, }, + resolve: { + // CRITICAL: Put 'development' FIRST so it takes precedence + // This tells Vite/Rollup to use the 'development' conditional exports + // which include the Spotlight integration code + conditions: ['development', 'browser', 'module', 'import', 'default'], + }, // Disable pre-bundling (esbuild) for Sentry packages - // This ensures Vite/Rollup uses our mode's conditions when bundling + // This ensures Vite/Rollup uses our resolve.conditions when bundling optimizeDeps: { exclude: ['@sentry/react', '@sentry/browser', '@sentry/core', '@sentry-internal/browser-utils'], }, From 1f15ad55057cb5a4b6d178db59c8931a92cadb4d Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Wed, 31 Dec 2025 15:44:25 +0300 Subject: [PATCH 24/44] fix(e2e): Use --mode development for vite build The vite build command defaults to production mode. Adding --mode development ensures Vite uses the 'development' conditional exports for SDK packages, which include the Spotlight integration code. --- .../browser-spotlight/package.json | 2 +- .../browser-spotlight/vite.config.js | 34 ++++--------------- 2 files changed, 7 insertions(+), 29 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/package.json b/dev-packages/e2e-tests/test-applications/browser-spotlight/package.json index 78e1eb4e21ba..f0d0a131eabc 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/package.json +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "dev": "vite", - "build": "vite build", + "build": "vite build --mode development", "preview": "vite preview --port 3030", "test": "playwright test", "clean": "npx rimraf node_modules pnpm-lock.yaml", diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js b/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js index f24199b425df..b782e2f246ff 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js @@ -2,9 +2,11 @@ import react from '@vitejs/plugin-react'; import { defineConfig } from 'vite'; // https://vitejs.dev/config/ -// We need to use the SDK's development builds which include Spotlight code. -// The SDK uses conditional exports with 'development' and 'production' conditions. -// By default, Vite uses 'production' when building. We override this with resolve.conditions. +// We build with --mode development (see package.json) to ensure Vite resolves +// packages using the 'development' conditional export. This is necessary because: +// 1. The SDK's Spotlight integration code is stripped from production builds +// 2. The @sentry/browser package uses conditional exports (development vs production) +// 3. When mode is 'development', Vite adds 'development' to resolve.conditions export default defineConfig({ plugins: [react()], build: { @@ -12,36 +14,12 @@ export default defineConfig({ outDir: 'build', // Don't minify to help with debugging minify: false, - // Use rollup's experimental conditions support - rollupOptions: { - // This is the key - tell rollup to use 'development' condition - plugins: [ - { - name: 'force-development-exports', - resolveId: { - order: 'pre', - async handler(source, importer, options) { - if (source.startsWith('@sentry/')) { - // Let Vite handle it with our conditions - return null; - } - }, - }, - }, - ], - }, }, preview: { port: 3030, }, - resolve: { - // CRITICAL: Put 'development' FIRST so it takes precedence - // This tells Vite/Rollup to use the 'development' conditional exports - // which include the Spotlight integration code - conditions: ['development', 'browser', 'module', 'import', 'default'], - }, // Disable pre-bundling (esbuild) for Sentry packages - // This ensures Vite/Rollup uses our resolve.conditions when bundling + // This ensures Vite/Rollup uses the mode's conditions when bundling optimizeDeps: { exclude: ['@sentry/react', '@sentry/browser', '@sentry/core', '@sentry-internal/browser-utils'], }, From fc0982dc3b40965c85b56853be157780e46ef01b Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Wed, 31 Dec 2025 15:56:25 +0300 Subject: [PATCH 25/44] fix(e2e): Explicitly add development to resolve.conditions Vite may not automatically add 'development' to conditions even in development mode. Explicitly set the conditions list with 'development' first to ensure the SDK dev builds are used. --- .../test-applications/browser-spotlight/vite.config.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js b/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js index b782e2f246ff..8f6c5dd6be48 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js @@ -18,6 +18,12 @@ export default defineConfig({ preview: { port: 3030, }, + resolve: { + // EXPLICITLY add 'development' to ensure the dev builds are used + // This tells Vite/Rollup to use the 'development' conditional exports + // Priority: development > browser > module > import > default + conditions: ['development', 'browser', 'module', 'import', 'require', 'default'], + }, // Disable pre-bundling (esbuild) for Sentry packages // This ensures Vite/Rollup uses the mode's conditions when bundling optimizeDeps: { From 521634cc8633ca2d384b7315649b548d471fbedb Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Wed, 31 Dec 2025 15:57:42 +0300 Subject: [PATCH 26/44] feat(browser): Include Spotlight integration in all builds Remove the development-only restriction on Spotlight integration code. The Spotlight code is already gated by the spotlight option - if users don't set it, the integration isn't added. There's no need to also strip it from production builds. This allows users to use Spotlight in any environment (including staging/QA) and enables proper E2E testing of the VITE_SENTRY_SPOTLIGHT env var feature. --- .../browser-spotlight/package.json | 2 +- .../browser-spotlight/vite.config.js | 19 ------------------- packages/browser/src/sdk.ts | 5 +++-- 3 files changed, 4 insertions(+), 22 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/package.json b/dev-packages/e2e-tests/test-applications/browser-spotlight/package.json index f0d0a131eabc..78e1eb4e21ba 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/package.json +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "dev": "vite", - "build": "vite build --mode development", + "build": "vite build", "preview": "vite preview --port 3030", "test": "playwright test", "clean": "npx rimraf node_modules pnpm-lock.yaml", diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js b/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js index 8f6c5dd6be48..75ccd1e908f4 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js @@ -2,33 +2,14 @@ import react from '@vitejs/plugin-react'; import { defineConfig } from 'vite'; // https://vitejs.dev/config/ -// We build with --mode development (see package.json) to ensure Vite resolves -// packages using the 'development' conditional export. This is necessary because: -// 1. The SDK's Spotlight integration code is stripped from production builds -// 2. The @sentry/browser package uses conditional exports (development vs production) -// 3. When mode is 'development', Vite adds 'development' to resolve.conditions export default defineConfig({ plugins: [react()], build: { - // Output to 'build' directory to match existing setup outDir: 'build', - // Don't minify to help with debugging - minify: false, }, preview: { port: 3030, }, - resolve: { - // EXPLICITLY add 'development' to ensure the dev builds are used - // This tells Vite/Rollup to use the 'development' conditional exports - // Priority: development > browser > module > import > default - conditions: ['development', 'browser', 'module', 'import', 'require', 'default'], - }, - // Disable pre-bundling (esbuild) for Sentry packages - // This ensures Vite/Rollup uses the mode's conditions when bundling - optimizeDeps: { - exclude: ['@sentry/react', '@sentry/browser', '@sentry/core', '@sentry-internal/browser-utils'], - }, define: { // Define env vars for the E2E test // VITE_SENTRY_SPOTLIGHT is read automatically by @sentry/react via import.meta.env diff --git a/packages/browser/src/sdk.ts b/packages/browser/src/sdk.ts index 800c1b701352..11859c17ebc7 100644 --- a/packages/browser/src/sdk.ts +++ b/packages/browser/src/sdk.ts @@ -94,7 +94,9 @@ export function init(options: BrowserOptions = {}): Client | undefined { let defaultIntegrations = options.defaultIntegrations == null ? getDefaultIntegrations(options) : options.defaultIntegrations; - /* rollup-include-development-only */ + // Add Spotlight integration if configured + // This is included in all builds (not just development) so users can use Spotlight + // in any environment where they set the spotlight option if (options.spotlight) { if (!defaultIntegrations) { defaultIntegrations = []; @@ -102,7 +104,6 @@ export function init(options: BrowserOptions = {}): Client | undefined { const args = typeof options.spotlight === 'string' ? { sidecarUrl: options.spotlight } : undefined; defaultIntegrations.push(spotlightBrowserIntegration(args)); } - /* rollup-include-development-only-end */ const clientOptions: BrowserClientOptions = { ...options, From 4b94aa7356aa7c4a34ae4ce41a43419046aa8734 Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Wed, 31 Dec 2025 16:19:13 +0300 Subject: [PATCH 27/44] fix(e2e): Set env vars in vite config for import.meta.env access Vite exposes VITE_* env vars via import.meta.env. Setting them in the vite.config.js ensures they're available during build. --- .../browser-spotlight/vite.config.js | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js b/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js index 75ccd1e908f4..ffbe3c711aeb 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js @@ -1,21 +1,22 @@ import react from '@vitejs/plugin-react'; -import { defineConfig } from 'vite'; +import { defineConfig, loadEnv } from 'vite'; // https://vitejs.dev/config/ -export default defineConfig({ - plugins: [react()], - build: { - outDir: 'build', - }, - preview: { - port: 3030, - }, - define: { - // Define env vars for the E2E test - // VITE_SENTRY_SPOTLIGHT is read automatically by @sentry/react via import.meta.env - 'import.meta.env.VITE_E2E_TEST_DSN': JSON.stringify( - process.env.E2E_TEST_DSN || 'https://public@dsn.ingest.sentry.io/1234567', - ), - 'import.meta.env.VITE_SENTRY_SPOTLIGHT': JSON.stringify('http://localhost:3032/stream'), - }, +export default defineConfig(({ mode }) => { + // Manually set the env vars we need + // These will be available as import.meta.env.VITE_* in the app + process.env.VITE_SENTRY_SPOTLIGHT = 'http://localhost:3032/stream'; + process.env.VITE_E2E_TEST_DSN = process.env.E2E_TEST_DSN || 'https://public@dsn.ingest.sentry.io/1234567'; + + return { + plugins: [react()], + build: { + outDir: 'build', + }, + preview: { + port: 3030, + }, + // Use envPrefix to expose VITE_* env vars via import.meta.env + envPrefix: 'VITE_', + }; }); From 4208b028fbcd49dddcea1e357ca93aaeef700d42 Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Wed, 31 Dec 2025 16:32:09 +0300 Subject: [PATCH 28/44] fix(react): Fix Vite env var replacement for Spotlight Vite only does static replacement on exact matches of import.meta.env.VITE_* and doesn't work with optional chaining (?.). Update the Rollup plugin to: 1. Use import.meta.env.VITE_SENTRY_SPOTLIGHT without optional chaining 2. Add a separate guard __IMPORT_META_ENV_EXISTS__ to check if import.meta.env exists This ensures Vite can properly replace the env var at build time while still being safe in non-Vite environments. --- dev-packages/rollup-utils/plugins/npmPlugins.mjs | 13 ++++++++++--- packages/react/src/sdk.ts | 10 ++++++++-- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/dev-packages/rollup-utils/plugins/npmPlugins.mjs b/dev-packages/rollup-utils/plugins/npmPlugins.mjs index 02be7267a7c5..91199c1c9080 100644 --- a/dev-packages/rollup-utils/plugins/npmPlugins.mjs +++ b/dev-packages/rollup-utils/plugins/npmPlugins.mjs @@ -170,18 +170,25 @@ export function makeRrwebBuildPlugin({ excludeShadowDom, excludeIframe } = {}) { /** * Creates a plugin to replace __VITE_SPOTLIGHT_ENV__ with the appropriate value based on output format. - * - ESM: import.meta.env?.VITE_SENTRY_SPOTLIGHT (allows Vite to provide zero-config Spotlight support) + * - ESM: import.meta.env.VITE_SENTRY_SPOTLIGHT (allows Vite to provide zero-config Spotlight support) * - CJS: undefined (import.meta is not available in CJS) * + * Note: We don't use optional chaining (?.) here because Vite's static replacement only works on + * exact matches of `import.meta.env.VITE_*`. The guard is done via __IMPORT_META_ENV_EXISTS__. + * * @param format The output format ('esm' or 'cjs') * @returns A `@rollup/plugin-replace` instance. */ export function makeViteSpotlightEnvReplacePlugin(format) { - const value = format === 'esm' ? 'import.meta.env?.VITE_SENTRY_SPOTLIGHT' : 'undefined'; + const isEsm = format === 'esm'; return replace({ preventAssignment: true, values: { - __VITE_SPOTLIGHT_ENV__: value, + // In ESM, check if import.meta.env exists (using typeof for safety) + // In CJS, import.meta is not available so this is always false + __IMPORT_META_ENV_EXISTS__: isEsm ? "(typeof import.meta !== 'undefined' && import.meta.env)" : 'false', + // The actual env var value - only accessed if __IMPORT_META_ENV_EXISTS__ is truthy + __VITE_SPOTLIGHT_ENV__: isEsm ? 'import.meta.env.VITE_SENTRY_SPOTLIGHT' : 'undefined', }, }); } diff --git a/packages/react/src/sdk.ts b/packages/react/src/sdk.ts index 214c76f2ecec..d334faf5853c 100644 --- a/packages/react/src/sdk.ts +++ b/packages/react/src/sdk.ts @@ -5,10 +5,16 @@ import { applySdkMetadata, parseSpotlightEnvValue, resolveSpotlightValue } from import { version } from 'react'; // Build-time placeholder - Rollup replaces per output format -// ESM: import.meta.env?.VITE_SENTRY_SPOTLIGHT (zero-config for Vite) +// ESM: import.meta.env.VITE_SENTRY_SPOTLIGHT (zero-config for Vite) // CJS: undefined +// Note: We don't use optional chaining (?.) because Vite only does static replacement +// on exact matches of import.meta.env.VITE_* declare const __VITE_SPOTLIGHT_ENV__: string | undefined; +// Vite exposes env vars via import.meta.env - check if it exists +// We use typeof to avoid throwing if import.meta is undefined +declare const __IMPORT_META_ENV_EXISTS__: boolean; + /** * Inits the React SDK */ @@ -23,7 +29,7 @@ export function init(options: BrowserOptions): Client | undefined { // 3. import.meta.env.VITE_SENTRY_SPOTLIGHT (ESM only, zero-config for Vite!) const spotlightEnvRaw = (typeof process !== 'undefined' && (process.env?.SENTRY_SPOTLIGHT || process.env?.VITE_SENTRY_SPOTLIGHT)) || - (typeof __VITE_SPOTLIGHT_ENV__ !== 'undefined' && __VITE_SPOTLIGHT_ENV__) || + (__IMPORT_META_ENV_EXISTS__ && __VITE_SPOTLIGHT_ENV__) || undefined; if (spotlightEnvRaw) { From 47ac2a0750ed1ee30eb5ac90243b395053d34af1 Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Wed, 31 Dec 2025 16:47:19 +0300 Subject: [PATCH 29/44] fix(e2e): Use Vite define for import.meta.env replacement Vite's define is the reliable way to ensure values are replaced in all code including code in node_modules. --- .../browser-spotlight/vite.config.js | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js b/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js index ffbe3c711aeb..327397b8c40b 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js @@ -1,22 +1,24 @@ import react from '@vitejs/plugin-react'; -import { defineConfig, loadEnv } from 'vite'; +import { defineConfig } from 'vite'; // https://vitejs.dev/config/ -export default defineConfig(({ mode }) => { - // Manually set the env vars we need - // These will be available as import.meta.env.VITE_* in the app - process.env.VITE_SENTRY_SPOTLIGHT = 'http://localhost:3032/stream'; - process.env.VITE_E2E_TEST_DSN = process.env.E2E_TEST_DSN || 'https://public@dsn.ingest.sentry.io/1234567'; - - return { - plugins: [react()], - build: { - outDir: 'build', - }, - preview: { - port: 3030, - }, - // Use envPrefix to expose VITE_* env vars via import.meta.env - envPrefix: 'VITE_', - }; +export default defineConfig({ + plugins: [react()], + build: { + outDir: 'build', + }, + preview: { + port: 3030, + }, + // Use envPrefix to expose VITE_* env vars via import.meta.env + envPrefix: 'VITE_', + // Use define to statically replace import.meta.env.* values at build time + // This is the reliable way to ensure Vite replaces these values in all code + // including code in node_modules + define: { + 'import.meta.env.VITE_SENTRY_SPOTLIGHT': JSON.stringify('http://localhost:3032/stream'), + 'import.meta.env.VITE_E2E_TEST_DSN': JSON.stringify( + process.env.E2E_TEST_DSN || 'https://public@dsn.ingest.sentry.io/1234567', + ), + }, }); From 2972fb6116f01dbaae7137db932ba7f67f892bc7 Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Wed, 31 Dec 2025 17:01:03 +0300 Subject: [PATCH 30/44] debug(e2e): Add more logging to browser-spotlight test --- .../test-applications/browser-spotlight/src/main.jsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/src/main.jsx b/dev-packages/e2e-tests/test-applications/browser-spotlight/src/main.jsx index 455998b8e495..9ed583e0b963 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/src/main.jsx +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/src/main.jsx @@ -3,6 +3,7 @@ import * as Sentry from '@sentry/react'; // Log debug info about the Spotlight env var console.log('[E2E Debug] VITE_SENTRY_SPOTLIGHT:', import.meta.env.VITE_SENTRY_SPOTLIGHT); console.log('[E2E Debug] VITE_E2E_TEST_DSN:', import.meta.env.VITE_E2E_TEST_DSN); +console.log('[E2E Debug] Full import.meta.env:', JSON.stringify(import.meta.env)); // Initialize Sentry - the @sentry/react SDK automatically parses // VITE_SENTRY_SPOTLIGHT from import.meta.env (zero-config for Vite!) @@ -22,13 +23,23 @@ const client = Sentry.init({ // Use tunnel to capture events at our proxy server tunnel: 'http://localhost:3031', debug: true, + // DEBUG: Log the options to see if spotlight is set + beforeSend(event) { + console.log('[E2E Debug] beforeSend called, event type:', event.type || 'error'); + return event; + }, }); // Debug: Check if Spotlight integration was automatically added console.log('[E2E Debug] Sentry client:', client); +console.log('[E2E Debug] Client options:', client?.getOptions?.()); const spotlightIntegration = client?.getIntegration?.('Spotlight'); console.log('[E2E Debug] Spotlight integration (should be present):', spotlightIntegration); +// List all integrations +const allIntegrations = client?.getOptions?.()?.integrations || []; +console.log('[E2E Debug] All integrations:', allIntegrations.map(i => i.name).join(', ')); + if (!spotlightIntegration) { console.error('[E2E Debug] ERROR: Spotlight integration was NOT automatically added!'); console.error('[E2E Debug] This means the VITE_SENTRY_SPOTLIGHT env var support is not working.'); From 163e02bc591a7769111ef0fed021e77440b5abab Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Wed, 31 Dec 2025 17:10:20 +0300 Subject: [PATCH 31/44] fix(e2e): Exclude Sentry packages from optimizeDeps for proper define replacement --- .../browser-spotlight/vite.config.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js b/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js index 327397b8c40b..f46ada86008c 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js @@ -6,15 +6,26 @@ export default defineConfig({ plugins: [react()], build: { outDir: 'build', + // Disable minification to help with debugging + minify: false, + // Force rollup to process all modules (don't skip node_modules) + commonjsOptions: { + include: [/node_modules/], + transformMixedEsModules: true, + }, }, preview: { port: 3030, }, // Use envPrefix to expose VITE_* env vars via import.meta.env envPrefix: 'VITE_', + // Exclude Sentry packages from pre-bundling (esbuild) + // This forces Vite to use Rollup for these packages, which respects our define replacements + optimizeDeps: { + exclude: ['@sentry/react', '@sentry/browser', '@sentry/core', '@sentry-internal/browser-utils'], + }, // Use define to statically replace import.meta.env.* values at build time // This is the reliable way to ensure Vite replaces these values in all code - // including code in node_modules define: { 'import.meta.env.VITE_SENTRY_SPOTLIGHT': JSON.stringify('http://localhost:3032/stream'), 'import.meta.env.VITE_E2E_TEST_DSN': JSON.stringify( From 229ccce12c3da7d530398c5b06ceaf4c977c5b4c Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Wed, 31 Dec 2025 17:21:33 +0300 Subject: [PATCH 32/44] fix(e2e): Use @rollup/plugin-replace for env var replacement in deps Vite's define only works on app code. Use @rollup/plugin-replace to replace import.meta.env.VITE_* values in all code including dependencies. --- .../browser-spotlight/package.json | 1 + .../browser-spotlight/vite.config.js | 33 +++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/package.json b/dev-packages/e2e-tests/test-applications/browser-spotlight/package.json index 78e1eb4e21ba..e3044c0acb84 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/package.json +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/package.json @@ -19,6 +19,7 @@ }, "devDependencies": { "@playwright/test": "~1.56.0", + "@rollup/plugin-replace": "^5.0.5", "@sentry-internal/test-utils": "link:../../../test-utils", "@vitejs/plugin-react": "^4.2.1", "vite": "~5.4.0" diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js b/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js index f46ada86008c..9ff314662c8e 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js @@ -1,35 +1,34 @@ import react from '@vitejs/plugin-react'; import { defineConfig } from 'vite'; +import replace from '@rollup/plugin-replace'; // https://vitejs.dev/config/ export default defineConfig({ - plugins: [react()], + plugins: [ + react(), + // Use @rollup/plugin-replace to replace import.meta.env.VITE_* in ALL code + // including dependencies. Vite's define only works on app code. + replace({ + preventAssignment: true, + values: { + 'import.meta.env.VITE_SENTRY_SPOTLIGHT': JSON.stringify('http://localhost:3032/stream'), + 'import.meta.env.VITE_E2E_TEST_DSN': JSON.stringify( + process.env.E2E_TEST_DSN || 'https://public@dsn.ingest.sentry.io/1234567', + ), + }, + }), + ], build: { outDir: 'build', // Disable minification to help with debugging minify: false, - // Force rollup to process all modules (don't skip node_modules) - commonjsOptions: { - include: [/node_modules/], - transformMixedEsModules: true, - }, }, preview: { port: 3030, }, - // Use envPrefix to expose VITE_* env vars via import.meta.env - envPrefix: 'VITE_', // Exclude Sentry packages from pre-bundling (esbuild) - // This forces Vite to use Rollup for these packages, which respects our define replacements + // This forces Vite to use Rollup for these packages optimizeDeps: { exclude: ['@sentry/react', '@sentry/browser', '@sentry/core', '@sentry-internal/browser-utils'], }, - // Use define to statically replace import.meta.env.* values at build time - // This is the reliable way to ensure Vite replaces these values in all code - define: { - 'import.meta.env.VITE_SENTRY_SPOTLIGHT': JSON.stringify('http://localhost:3032/stream'), - 'import.meta.env.VITE_E2E_TEST_DSN': JSON.stringify( - process.env.E2E_TEST_DSN || 'https://public@dsn.ingest.sentry.io/1234567', - ), - }, }); From 1168a990d3b999e7245ad157a4761cf15b5f0488 Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Wed, 31 Dec 2025 17:33:58 +0300 Subject: [PATCH 33/44] fix(e2e): Move replace plugin to rollupOptions for proper dep processing --- .../browser-spotlight/vite.config.js | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js b/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js index 9ff314662c8e..fb76b61f4db4 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js @@ -6,28 +6,34 @@ import replace from '@rollup/plugin-replace'; export default defineConfig({ plugins: [ react(), - // Use @rollup/plugin-replace to replace import.meta.env.VITE_* in ALL code - // including dependencies. Vite's define only works on app code. - replace({ - preventAssignment: true, - values: { - 'import.meta.env.VITE_SENTRY_SPOTLIGHT': JSON.stringify('http://localhost:3032/stream'), - 'import.meta.env.VITE_E2E_TEST_DSN': JSON.stringify( - process.env.E2E_TEST_DSN || 'https://public@dsn.ingest.sentry.io/1234567', - ), - }, - }), ], build: { outDir: 'build', // Disable minification to help with debugging minify: false, + // Force all dependencies to be bundled (not externalized) + rollupOptions: { + plugins: [ + // Use @rollup/plugin-replace to replace import.meta.env.VITE_* in ALL code + // This runs during the Rollup bundling phase and applies to all modules + replace({ + preventAssignment: true, + delimiters: ['', ''], + values: { + 'import.meta.env.VITE_SENTRY_SPOTLIGHT': JSON.stringify('http://localhost:3032/stream'), + 'import.meta.env.VITE_E2E_TEST_DSN': JSON.stringify( + process.env.E2E_TEST_DSN || 'https://public@dsn.ingest.sentry.io/1234567', + ), + }, + }), + ], + }, }, preview: { port: 3030, }, // Exclude Sentry packages from pre-bundling (esbuild) - // This forces Vite to use Rollup for these packages + // This forces Vite to use Rollup for these packages during build optimizeDeps: { exclude: ['@sentry/react', '@sentry/browser', '@sentry/core', '@sentry-internal/browser-utils'], }, From b0507a0b9e69accc925ba3f6e32ae2d3a136e8d6 Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Wed, 31 Dec 2025 17:43:08 +0300 Subject: [PATCH 34/44] fix(e2e): Set VITE_SENTRY_SPOTLIGHT env var at build time Vite automatically replaces import.meta.env.VITE_* with values from actual env vars at build time. Set the env vars in the build script rather than trying to use Rollup plugins or define options. This tests the zero-config Spotlight enablement for Vite projects: - Set VITE_SENTRY_SPOTLIGHT env var before building - The SDK automatically reads it and enables Spotlight --- .../browser-spotlight/package.json | 3 +- .../browser-spotlight/src/main.jsx | 36 +++---------------- .../browser-spotlight/tests/spotlight.test.ts | 12 ------- .../browser-spotlight/vite.config.js | 31 ++-------------- 4 files changed, 9 insertions(+), 73 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/package.json b/dev-packages/e2e-tests/test-applications/browser-spotlight/package.json index e3044c0acb84..3fd81e2ec852 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/package.json +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "dev": "vite", - "build": "vite build", + "build": "VITE_SENTRY_SPOTLIGHT=http://localhost:3032/stream VITE_E2E_TEST_DSN=${E2E_TEST_DSN:-https://public@dsn.ingest.sentry.io/1234567} vite build", "preview": "vite preview --port 3030", "test": "playwright test", "clean": "npx rimraf node_modules pnpm-lock.yaml", @@ -19,7 +19,6 @@ }, "devDependencies": { "@playwright/test": "~1.56.0", - "@rollup/plugin-replace": "^5.0.5", "@sentry-internal/test-utils": "link:../../../test-utils", "@vitejs/plugin-react": "^4.2.1", "vite": "~5.4.0" diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/src/main.jsx b/dev-packages/e2e-tests/test-applications/browser-spotlight/src/main.jsx index 9ed583e0b963..4a9d42fd970a 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/src/main.jsx +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/src/main.jsx @@ -1,20 +1,15 @@ import * as Sentry from '@sentry/react'; -// Log debug info about the Spotlight env var -console.log('[E2E Debug] VITE_SENTRY_SPOTLIGHT:', import.meta.env.VITE_SENTRY_SPOTLIGHT); -console.log('[E2E Debug] VITE_E2E_TEST_DSN:', import.meta.env.VITE_E2E_TEST_DSN); -console.log('[E2E Debug] Full import.meta.env:', JSON.stringify(import.meta.env)); - // Initialize Sentry - the @sentry/react SDK automatically parses // VITE_SENTRY_SPOTLIGHT from import.meta.env (zero-config for Vite!) // This tests the automatic SDK initialization feature. // // NOTE: We do NOT explicitly set `spotlight` or add `spotlightBrowserIntegration`! -// The SDK should automatically: -// 1. Read VITE_SENTRY_SPOTLIGHT from import.meta.env -// 2. Enable Spotlight with the URL from the env var -// 3. Add the spotlightBrowserIntegration to send events to the sidecar -const client = Sentry.init({ +// The SDK automatically: +// 1. Reads VITE_SENTRY_SPOTLIGHT from import.meta.env +// 2. Enables Spotlight with the URL from the env var +// 3. Adds the spotlightBrowserIntegration to send events to the sidecar +Sentry.init({ dsn: import.meta.env.VITE_E2E_TEST_DSN, integrations: [Sentry.browserTracingIntegration()], tracesSampleRate: 1.0, @@ -22,29 +17,8 @@ const client = Sentry.init({ environment: 'qa', // Use tunnel to capture events at our proxy server tunnel: 'http://localhost:3031', - debug: true, - // DEBUG: Log the options to see if spotlight is set - beforeSend(event) { - console.log('[E2E Debug] beforeSend called, event type:', event.type || 'error'); - return event; - }, }); -// Debug: Check if Spotlight integration was automatically added -console.log('[E2E Debug] Sentry client:', client); -console.log('[E2E Debug] Client options:', client?.getOptions?.()); -const spotlightIntegration = client?.getIntegration?.('Spotlight'); -console.log('[E2E Debug] Spotlight integration (should be present):', spotlightIntegration); - -// List all integrations -const allIntegrations = client?.getOptions?.()?.integrations || []; -console.log('[E2E Debug] All integrations:', allIntegrations.map(i => i.name).join(', ')); - -if (!spotlightIntegration) { - console.error('[E2E Debug] ERROR: Spotlight integration was NOT automatically added!'); - console.error('[E2E Debug] This means the VITE_SENTRY_SPOTLIGHT env var support is not working.'); -} - // Simple render document.getElementById('root').innerHTML = `
diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts b/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts index 99aa320f12a4..895646cc9829 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts @@ -13,19 +13,12 @@ import { waitForError, waitForSpotlightError, waitForSpotlightTransaction } from * The SDK should automatically enable Spotlight when VITE_SENTRY_SPOTLIGHT is set. * * Test setup: - * - Vite is configured with mode: 'development' to use SDK dev builds - * (Spotlight code is stripped from prod builds) * - VITE_SENTRY_SPOTLIGHT is set to 'http://localhost:3032/stream' at build time * - tunnel is set to 'http://localhost:3031' for regular event capture * - A Spotlight proxy server runs on port 3032 to capture Spotlight events * - A regular event proxy server runs on port 3031 to capture tunnel events */ test('VITE_SENTRY_SPOTLIGHT env var automatically enables Spotlight', async ({ page }) => { - // Capture console logs for debugging - page.on('console', msg => { - console.log(`[Browser Console] ${msg.type()}: ${msg.text()}`); - }); - // Wait for the error to arrive at the regular tunnel (port 3031) const tunnelErrorPromise = waitForError('browser-spotlight', event => { return !event.type && event.exception?.values?.[0]?.value === 'Spotlight test error!'; @@ -61,11 +54,6 @@ test('VITE_SENTRY_SPOTLIGHT env var automatically enables Spotlight', async ({ p * Test that Spotlight automatically receives transaction events. */ test('VITE_SENTRY_SPOTLIGHT automatically sends transactions to sidecar', async ({ page }) => { - // Capture console logs for debugging - page.on('console', msg => { - console.log(`[Browser Console] ${msg.type()}: ${msg.text()}`); - }); - // Wait for a pageload transaction to arrive at the Spotlight sidecar const spotlightTransactionPromise = waitForSpotlightTransaction('browser-spotlight-sidecar', event => { return event.type === 'transaction' && event.contexts?.trace?.op === 'pageload'; diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js b/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js index fb76b61f4db4..31d0974d5395 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/vite.config.js @@ -1,40 +1,15 @@ import react from '@vitejs/plugin-react'; import { defineConfig } from 'vite'; -import replace from '@rollup/plugin-replace'; // https://vitejs.dev/config/ +// VITE_SENTRY_SPOTLIGHT and VITE_E2E_TEST_DSN are set as env vars in package.json build script +// Vite automatically replaces import.meta.env.VITE_* with values from actual env vars export default defineConfig({ - plugins: [ - react(), - ], + plugins: [react()], build: { outDir: 'build', - // Disable minification to help with debugging - minify: false, - // Force all dependencies to be bundled (not externalized) - rollupOptions: { - plugins: [ - // Use @rollup/plugin-replace to replace import.meta.env.VITE_* in ALL code - // This runs during the Rollup bundling phase and applies to all modules - replace({ - preventAssignment: true, - delimiters: ['', ''], - values: { - 'import.meta.env.VITE_SENTRY_SPOTLIGHT': JSON.stringify('http://localhost:3032/stream'), - 'import.meta.env.VITE_E2E_TEST_DSN': JSON.stringify( - process.env.E2E_TEST_DSN || 'https://public@dsn.ingest.sentry.io/1234567', - ), - }, - }), - ], - }, }, preview: { port: 3030, }, - // Exclude Sentry packages from pre-bundling (esbuild) - // This forces Vite to use Rollup for these packages during build - optimizeDeps: { - exclude: ['@sentry/react', '@sentry/browser', '@sentry/core', '@sentry-internal/browser-utils'], - }, }); From 2cbca20a14089c9d463ed458665cb2800ae146e6 Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Wed, 31 Dec 2025 18:15:34 +0300 Subject: [PATCH 35/44] fix: Update Spotlight env var handling and tests 1. Update bundling tests to expect Spotlight in production builds 2. Update ESM bundle test to expect import.meta.env.VITE_SENTRY_SPOTLIGHT without optional chaining 3. Simplify Rollup plugin to only replace __VITE_SPOTLIGHT_ENV__ 4. Update SDK comments to reflect the change --- dev-packages/bundler-tests/tests/bundling.test.ts | 14 +++++++++----- dev-packages/rollup-utils/plugins/npmPlugins.mjs | 8 +++----- packages/react/src/sdk.ts | 10 +++++----- packages/solid/src/sdk.ts | 4 +++- packages/svelte/src/sdk.ts | 4 +++- packages/vue/src/sdk.ts | 4 +++- 6 files changed, 26 insertions(+), 18 deletions(-) diff --git a/dev-packages/bundler-tests/tests/bundling.test.ts b/dev-packages/bundler-tests/tests/bundling.test.ts index 955287df0095..ee13fc8baa54 100644 --- a/dev-packages/bundler-tests/tests/bundling.test.ts +++ b/dev-packages/bundler-tests/tests/bundling.test.ts @@ -136,9 +136,12 @@ describe('spotlight', () => { expect(code).toContain(SPOTLIGHT_URL); }); - test(`${name} production bundle does not contain spotlight`, async () => { + // Spotlight is now included in production builds too (not dev-only) + // The integration is gated by the spotlight option, so there's no need + // to strip it from production builds + test(`${name} production bundle contains spotlight`, async () => { const code = await bundler('production'); - expect(code).not.toContain(SPOTLIGHT_URL); + expect(code).toContain(SPOTLIGHT_URL); }); } }); @@ -162,12 +165,13 @@ describe('__VITE_SPOTLIGHT_ENV__ rollup replacement', () => { } test.each(['react', 'vue', 'svelte', 'solid'] as const)( - '%s ESM bundle contains import.meta.env?.VITE_SENTRY_SPOTLIGHT access', + '%s ESM bundle contains import.meta.env.VITE_SENTRY_SPOTLIGHT access', packageName => { const code = stripComments(readSdkFile(packageName, 'esm')); // ESM bundles should have import.meta.env access for Vite support - // The replacement is: import.meta.env?.VITE_SENTRY_SPOTLIGHT - expect(code).toMatch(/import\.meta\.env\?\.[A-Z_]+SPOTLIGHT/); + // The replacement is: import.meta.env.VITE_SENTRY_SPOTLIGHT (without optional chaining) + // so that Vite can properly do static replacement at build time + expect(code).toMatch(/import\.meta\.env\.[A-Z_]+SPOTLIGHT/); }, ); diff --git a/dev-packages/rollup-utils/plugins/npmPlugins.mjs b/dev-packages/rollup-utils/plugins/npmPlugins.mjs index 91199c1c9080..989206f0795e 100644 --- a/dev-packages/rollup-utils/plugins/npmPlugins.mjs +++ b/dev-packages/rollup-utils/plugins/npmPlugins.mjs @@ -174,7 +174,7 @@ export function makeRrwebBuildPlugin({ excludeShadowDom, excludeIframe } = {}) { * - CJS: undefined (import.meta is not available in CJS) * * Note: We don't use optional chaining (?.) here because Vite's static replacement only works on - * exact matches of `import.meta.env.VITE_*`. The guard is done via __IMPORT_META_ENV_EXISTS__. + * exact matches of `import.meta.env.VITE_*`. The SDK code uses typeof to guard against undefined. * * @param format The output format ('esm' or 'cjs') * @returns A `@rollup/plugin-replace` instance. @@ -184,10 +184,8 @@ export function makeViteSpotlightEnvReplacePlugin(format) { return replace({ preventAssignment: true, values: { - // In ESM, check if import.meta.env exists (using typeof for safety) - // In CJS, import.meta is not available so this is always false - __IMPORT_META_ENV_EXISTS__: isEsm ? "(typeof import.meta !== 'undefined' && import.meta.env)" : 'false', - // The actual env var value - only accessed if __IMPORT_META_ENV_EXISTS__ is truthy + // ESM: Replace with import.meta.env.VITE_SENTRY_SPOTLIGHT for Vite zero-config support + // CJS: Replace with undefined since import.meta is not available __VITE_SPOTLIGHT_ENV__: isEsm ? 'import.meta.env.VITE_SENTRY_SPOTLIGHT' : 'undefined', }, }); diff --git a/packages/react/src/sdk.ts b/packages/react/src/sdk.ts index d334faf5853c..1f26e87c9402 100644 --- a/packages/react/src/sdk.ts +++ b/packages/react/src/sdk.ts @@ -11,10 +11,6 @@ import { version } from 'react'; // on exact matches of import.meta.env.VITE_* declare const __VITE_SPOTLIGHT_ENV__: string | undefined; -// Vite exposes env vars via import.meta.env - check if it exists -// We use typeof to avoid throwing if import.meta is undefined -declare const __IMPORT_META_ENV_EXISTS__: boolean; - /** * Inits the React SDK */ @@ -27,9 +23,13 @@ export function init(options: BrowserOptions): Client | undefined { // 1. process.env.SENTRY_SPOTLIGHT (all bundlers, requires config) // 2. process.env.VITE_SENTRY_SPOTLIGHT (all bundlers, requires config) // 3. import.meta.env.VITE_SENTRY_SPOTLIGHT (ESM only, zero-config for Vite!) + // + // For option 3, Rollup replaces __VITE_SPOTLIGHT_ENV__ with import.meta.env.VITE_SENTRY_SPOTLIGHT + // Then Vite replaces that with the actual value or undefined at build time. + // We use typeof to check if the value exists, which works because Vite does static replacement. const spotlightEnvRaw = (typeof process !== 'undefined' && (process.env?.SENTRY_SPOTLIGHT || process.env?.VITE_SENTRY_SPOTLIGHT)) || - (__IMPORT_META_ENV_EXISTS__ && __VITE_SPOTLIGHT_ENV__) || + (typeof __VITE_SPOTLIGHT_ENV__ !== 'undefined' && __VITE_SPOTLIGHT_ENV__) || undefined; if (spotlightEnvRaw) { diff --git a/packages/solid/src/sdk.ts b/packages/solid/src/sdk.ts index d298f1274c8e..45dcab9cd25c 100644 --- a/packages/solid/src/sdk.ts +++ b/packages/solid/src/sdk.ts @@ -4,8 +4,10 @@ import type { Client } from '@sentry/core'; import { applySdkMetadata, parseSpotlightEnvValue, resolveSpotlightValue } from '@sentry/core'; // Build-time placeholder - Rollup replaces per output format -// ESM: import.meta.env?.VITE_SENTRY_SPOTLIGHT (zero-config for Vite) +// ESM: import.meta.env.VITE_SENTRY_SPOTLIGHT (zero-config for Vite) // CJS: undefined +// Note: We don't use optional chaining (?.) because Vite only does static replacement +// on exact matches of import.meta.env.VITE_* declare const __VITE_SPOTLIGHT_ENV__: string | undefined; /** diff --git a/packages/svelte/src/sdk.ts b/packages/svelte/src/sdk.ts index 4f4440cb04b4..bd2e5570f8ef 100644 --- a/packages/svelte/src/sdk.ts +++ b/packages/svelte/src/sdk.ts @@ -4,8 +4,10 @@ import type { Client } from '@sentry/core'; import { applySdkMetadata, parseSpotlightEnvValue, resolveSpotlightValue } from '@sentry/core'; // Build-time placeholder - Rollup replaces per output format -// ESM: import.meta.env?.VITE_SENTRY_SPOTLIGHT (zero-config for Vite) +// ESM: import.meta.env.VITE_SENTRY_SPOTLIGHT (zero-config for Vite) // CJS: undefined +// Note: We don't use optional chaining (?.) because Vite only does static replacement +// on exact matches of import.meta.env.VITE_* declare const __VITE_SPOTLIGHT_ENV__: string | undefined; /** diff --git a/packages/vue/src/sdk.ts b/packages/vue/src/sdk.ts index 493a0823932a..4d6870673ae6 100644 --- a/packages/vue/src/sdk.ts +++ b/packages/vue/src/sdk.ts @@ -5,8 +5,10 @@ import { vueIntegration } from './integration'; import type { Options } from './types'; // Build-time placeholder - Rollup replaces per output format -// ESM: import.meta.env?.VITE_SENTRY_SPOTLIGHT (zero-config for Vite) +// ESM: import.meta.env.VITE_SENTRY_SPOTLIGHT (zero-config for Vite) // CJS: undefined +// Note: We don't use optional chaining (?.) because Vite only does static replacement +// on exact matches of import.meta.env.VITE_* declare const __VITE_SPOTLIGHT_ENV__: string | undefined; /** From 55c79eb24ca74760d5d256ea79ff139ba312563b Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Wed, 31 Dec 2025 18:43:58 +0300 Subject: [PATCH 36/44] chore: Increase bundle size limits for Spotlight inclusion Spotlight is now included in all builds (not just development), which adds ~0.2-0.5 KB to bundle sizes. This is acceptable because: 1. The integration code is gated by the spotlight option 2. Having it in production builds enables env-var based activation --- .size-limit.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.size-limit.js b/.size-limit.js index a8ab8b842146..79e47192f63a 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -8,7 +8,7 @@ module.exports = [ path: 'packages/browser/build/npm/esm/prod/index.js', import: createImport('init'), gzip: true, - limit: '25 KB', + limit: '25.5 KB', }, { name: '@sentry/browser - with treeshaking flags', @@ -38,7 +38,7 @@ module.exports = [ path: 'packages/browser/build/npm/esm/prod/index.js', import: createImport('init', 'browserTracingIntegration'), gzip: true, - limit: '42 KB', + limit: '42.5 KB', }, { name: '@sentry/browser (incl. Tracing, Profiling)', @@ -82,7 +82,7 @@ module.exports = [ path: 'packages/browser/build/npm/esm/prod/index.js', import: createImport('init', 'browserTracingIntegration', 'replayIntegration', 'replayCanvasIntegration'), gzip: true, - limit: '85 KB', + limit: '86 KB', }, { name: '@sentry/browser (incl. Tracing, Replay, Feedback)', @@ -140,7 +140,7 @@ module.exports = [ import: createImport('init', 'ErrorBoundary'), ignore: ['react/jsx-runtime'], gzip: true, - limit: '27 KB', + limit: '27.5 KB', }, { name: '@sentry/react (incl. Tracing)', @@ -148,7 +148,7 @@ module.exports = [ import: createImport('init', 'ErrorBoundary', 'reactRouterV6BrowserTracingIntegration'), ignore: ['react/jsx-runtime'], gzip: true, - limit: '44.5 KB', + limit: '45 KB', }, // Vue SDK (ESM) { @@ -163,7 +163,7 @@ module.exports = [ path: 'packages/vue/build/esm/index.js', import: createImport('init', 'browserTracingIntegration'), gzip: true, - limit: '44 KB', + limit: '44.5 KB', }, // Svelte SDK (ESM) { @@ -178,13 +178,13 @@ module.exports = [ name: 'CDN Bundle', path: createCDNPath('bundle.min.js'), gzip: true, - limit: '27.5 KB', + limit: '28 KB', }, { name: 'CDN Bundle (incl. Tracing)', path: createCDNPath('bundle.tracing.min.js'), gzip: true, - limit: '42.5 KB', + limit: '43 KB', }, { name: 'CDN Bundle (incl. Tracing, Replay)', @@ -243,7 +243,7 @@ module.exports = [ import: createImport('init'), ignore: ['$app/stores'], gzip: true, - limit: '42.5 KB', + limit: '43 KB', }, // Node-Core SDK (ESM) { From da4bc7aff28b9e3618e15d48d11691a54bbbae5d Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Wed, 31 Dec 2025 18:57:24 +0300 Subject: [PATCH 37/44] debug: Add console output to diagnose CI failure --- .../browser-spotlight/src/main.jsx | 14 +++++++++++++- .../browser-spotlight/start-spotlight-proxy.mjs | 6 ++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/src/main.jsx b/dev-packages/e2e-tests/test-applications/browser-spotlight/src/main.jsx index 4a9d42fd970a..04a730f0d210 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/src/main.jsx +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/src/main.jsx @@ -1,5 +1,10 @@ import * as Sentry from '@sentry/react'; +// Debug: Log env vars +console.log('[E2E Debug] VITE_SENTRY_SPOTLIGHT:', import.meta.env.VITE_SENTRY_SPOTLIGHT); +console.log('[E2E Debug] VITE_E2E_TEST_DSN:', import.meta.env.VITE_E2E_TEST_DSN); +console.log('[E2E Debug] MODE:', import.meta.env.MODE); + // Initialize Sentry - the @sentry/react SDK automatically parses // VITE_SENTRY_SPOTLIGHT from import.meta.env (zero-config for Vite!) // This tests the automatic SDK initialization feature. @@ -9,7 +14,7 @@ import * as Sentry from '@sentry/react'; // 1. Reads VITE_SENTRY_SPOTLIGHT from import.meta.env // 2. Enables Spotlight with the URL from the env var // 3. Adds the spotlightBrowserIntegration to send events to the sidecar -Sentry.init({ +const client = Sentry.init({ dsn: import.meta.env.VITE_E2E_TEST_DSN, integrations: [Sentry.browserTracingIntegration()], tracesSampleRate: 1.0, @@ -17,8 +22,15 @@ Sentry.init({ environment: 'qa', // Use tunnel to capture events at our proxy server tunnel: 'http://localhost:3031', + debug: true, }); +// Debug: Check if Spotlight integration was added +const integrations = client?.getOptions()?.integrations || []; +const integrationNames = integrations.map(i => i.name); +console.log('[E2E Debug] Integrations:', integrationNames.join(', ')); +console.log('[E2E Debug] Has SpotlightBrowser:', integrationNames.includes('SpotlightBrowser')); + // Simple render document.getElementById('root').innerHTML = `
diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/start-spotlight-proxy.mjs b/dev-packages/e2e-tests/test-applications/browser-spotlight/start-spotlight-proxy.mjs index c82c3af1d071..87a7e96d7196 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/start-spotlight-proxy.mjs +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/start-spotlight-proxy.mjs @@ -1,8 +1,14 @@ import { startSpotlightProxyServer } from '@sentry-internal/test-utils'; +console.log('[Spotlight Proxy] Starting spotlight proxy server on port 3032...'); + // Start a Spotlight proxy server that captures events sent to /stream // This simulates the Spotlight sidecar and allows us to verify events arrive startSpotlightProxyServer({ port: 3032, proxyServerName: 'browser-spotlight-sidecar', +}).then(() => { + console.log('[Spotlight Proxy] Server started successfully on port 3032'); +}).catch(err => { + console.error('[Spotlight Proxy] Failed to start server:', err); }); From 44869082d80f832cea176b4b452a979363ef858a Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Wed, 31 Dec 2025 19:08:10 +0300 Subject: [PATCH 38/44] debug: Forward browser console to test output --- .../browser-spotlight/tests/spotlight.test.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts b/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts index 895646cc9829..fdeffb10da01 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/tests/spotlight.test.ts @@ -1,6 +1,16 @@ import { expect, test } from '@playwright/test'; import { waitForError, waitForSpotlightError, waitForSpotlightTransaction } from '@sentry-internal/test-utils'; +// Forward browser console messages to the test output for debugging +test.beforeEach(async ({ page }) => { + page.on('console', msg => { + console.log(`[Browser Console] ${msg.type()}: ${msg.text()}`); + }); + page.on('pageerror', error => { + console.log(`[Browser Error] ${error.message}`); + }); +}); + /** * Test that VITE_SENTRY_SPOTLIGHT environment variable automatically enables Spotlight. * From e93fc83b9459042d558b4cc0ffe5c24dd31f4c3b Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Wed, 31 Dec 2025 19:34:14 +0300 Subject: [PATCH 39/44] debug: Add more detailed logging to pinpoint spotlight issue --- .../browser-spotlight/src/main.jsx | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/src/main.jsx b/dev-packages/e2e-tests/test-applications/browser-spotlight/src/main.jsx index 04a730f0d210..883ce1991367 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/src/main.jsx +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/src/main.jsx @@ -7,14 +7,7 @@ console.log('[E2E Debug] MODE:', import.meta.env.MODE); // Initialize Sentry - the @sentry/react SDK automatically parses // VITE_SENTRY_SPOTLIGHT from import.meta.env (zero-config for Vite!) -// This tests the automatic SDK initialization feature. -// -// NOTE: We do NOT explicitly set `spotlight` or add `spotlightBrowserIntegration`! -// The SDK automatically: -// 1. Reads VITE_SENTRY_SPOTLIGHT from import.meta.env -// 2. Enables Spotlight with the URL from the env var -// 3. Adds the spotlightBrowserIntegration to send events to the sidecar -const client = Sentry.init({ +const initOptions = { dsn: import.meta.env.VITE_E2E_TEST_DSN, integrations: [Sentry.browserTracingIntegration()], tracesSampleRate: 1.0, @@ -23,10 +16,26 @@ const client = Sentry.init({ // Use tunnel to capture events at our proxy server tunnel: 'http://localhost:3031', debug: true, -}); +}; + +console.log('[E2E Debug] Init options BEFORE Sentry.init:', JSON.stringify({ + dsn: initOptions.dsn, + spotlight: initOptions.spotlight, + debug: initOptions.debug, +})); + +const client = Sentry.init(initOptions); + +// Debug: Check what the client received +const clientOptions = client?.getOptions(); +console.log('[E2E Debug] Client options AFTER Sentry.init:', JSON.stringify({ + dsn: clientOptions?.dsn, + spotlight: clientOptions?.spotlight, + debug: clientOptions?.debug, +})); // Debug: Check if Spotlight integration was added -const integrations = client?.getOptions()?.integrations || []; +const integrations = clientOptions?.integrations || []; const integrationNames = integrations.map(i => i.name); console.log('[E2E Debug] Integrations:', integrationNames.join(', ')); console.log('[E2E Debug] Has SpotlightBrowser:', integrationNames.includes('SpotlightBrowser')); From a27ef9bda4547327a0f64734574f8b35a05d04c9 Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Wed, 31 Dec 2025 19:44:03 +0300 Subject: [PATCH 40/44] debug: Check if import.meta.env is available at runtime --- .../test-applications/browser-spotlight/src/main.jsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/src/main.jsx b/dev-packages/e2e-tests/test-applications/browser-spotlight/src/main.jsx index 883ce1991367..32ca5257bd3c 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/src/main.jsx +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/src/main.jsx @@ -5,6 +5,11 @@ console.log('[E2E Debug] VITE_SENTRY_SPOTLIGHT:', import.meta.env.VITE_SENTRY_SP console.log('[E2E Debug] VITE_E2E_TEST_DSN:', import.meta.env.VITE_E2E_TEST_DSN); console.log('[E2E Debug] MODE:', import.meta.env.MODE); +// Debug: Check if import.meta.env is available at runtime +console.log('[E2E Debug] typeof import.meta:', typeof import.meta); +console.log('[E2E Debug] typeof import.meta.env:', typeof import.meta.env); +console.log('[E2E Debug] import.meta.env object:', JSON.stringify(import.meta.env)); + // Initialize Sentry - the @sentry/react SDK automatically parses // VITE_SENTRY_SPOTLIGHT from import.meta.env (zero-config for Vite!) const initOptions = { From 421ab1fd2808ca0d189b665c5b41a6bd2424d8d7 Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Wed, 31 Dec 2025 21:32:44 +0300 Subject: [PATCH 41/44] fix(e2e): Add .npmrc for browser-spotlight to use Verdaccio registry The browser-spotlight test was missing the .npmrc file that configures pnpm to install @sentry/* packages from Verdaccio instead of the public npm registry. Without this file, CI was testing against published npm packages instead of the current PR changes. --- .../e2e-tests/test-applications/browser-spotlight/.npmrc | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 dev-packages/e2e-tests/test-applications/browser-spotlight/.npmrc diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/.npmrc b/dev-packages/e2e-tests/test-applications/browser-spotlight/.npmrc new file mode 100644 index 000000000000..070f80f05092 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/.npmrc @@ -0,0 +1,2 @@ +@sentry:registry=http://127.0.0.1:4873 +@sentry-internal:registry=http://127.0.0.1:4873 From 109a37496862305ed0592c99a05907f342fe80e4 Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Wed, 31 Dec 2025 22:53:09 +0300 Subject: [PATCH 42/44] feat(spotlight): Add Next.js Spotlight E2E test and fix import.meta.env access - Create nextjs-spotlight E2E test app to verify NEXT_PUBLIC_SENTRY_SPOTLIGHT env var support - Fix bug where accessing import.meta.env.VITE_SENTRY_SPOTLIGHT would throw in non-Vite ESM environments (Next.js with webpack/turbopack) - Wrap Vite env var access in try-catch in react, vue, svelte, and solid SDKs --- .../test-applications/nextjs-spotlight/.npmrc | 2 + .../nextjs-spotlight/app/global-error.tsx | 19 ++++++ .../nextjs-spotlight/app/layout.tsx | 12 ++++ .../nextjs-spotlight/app/page.tsx | 17 +++++ .../instrumentation-client.ts | 16 +++++ .../nextjs-spotlight/instrumentation.ts | 23 +++++++ .../nextjs-spotlight/next.config.js | 8 +++ .../nextjs-spotlight/package.json | 31 +++++++++ .../nextjs-spotlight/playwright.config.mjs | 16 +++++ .../nextjs-spotlight/start-event-proxy.mjs | 6 ++ .../start-spotlight-proxy.mjs | 10 +++ .../nextjs-spotlight/tests/spotlight.test.ts | 66 +++++++++++++++++++ .../nextjs-spotlight/tsconfig.json | 28 ++++++++ packages/react/src/sdk.ts | 11 +++- packages/solid/src/sdk.ts | 13 +++- packages/svelte/src/sdk.ts | 13 +++- packages/vue/src/sdk.ts | 13 +++- 17 files changed, 299 insertions(+), 5 deletions(-) create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-spotlight/.npmrc create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-spotlight/app/global-error.tsx create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-spotlight/app/layout.tsx create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-spotlight/app/page.tsx create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-spotlight/instrumentation-client.ts create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-spotlight/instrumentation.ts create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-spotlight/next.config.js create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-spotlight/package.json create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-spotlight/playwright.config.mjs create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-spotlight/start-event-proxy.mjs create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-spotlight/start-spotlight-proxy.mjs create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-spotlight/tests/spotlight.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/nextjs-spotlight/tsconfig.json diff --git a/dev-packages/e2e-tests/test-applications/nextjs-spotlight/.npmrc b/dev-packages/e2e-tests/test-applications/nextjs-spotlight/.npmrc new file mode 100644 index 000000000000..070f80f05092 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-spotlight/.npmrc @@ -0,0 +1,2 @@ +@sentry:registry=http://127.0.0.1:4873 +@sentry-internal:registry=http://127.0.0.1:4873 diff --git a/dev-packages/e2e-tests/test-applications/nextjs-spotlight/app/global-error.tsx b/dev-packages/e2e-tests/test-applications/nextjs-spotlight/app/global-error.tsx new file mode 100644 index 000000000000..4356dc5ebb25 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-spotlight/app/global-error.tsx @@ -0,0 +1,19 @@ +'use client'; + +import * as Sentry from '@sentry/nextjs'; +import { useEffect } from 'react'; + +export default function GlobalError({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) { + useEffect(() => { + Sentry.captureException(error); + }, [error]); + + return ( + + +

Something went wrong!

+ + + + ); +} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-spotlight/app/layout.tsx b/dev-packages/e2e-tests/test-applications/nextjs-spotlight/app/layout.tsx new file mode 100644 index 000000000000..c73806a33629 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-spotlight/app/layout.tsx @@ -0,0 +1,12 @@ +export const metadata = { + title: 'Next.js Spotlight E2E Test', + description: 'Tests NEXT_PUBLIC_SENTRY_SPOTLIGHT env var support', +}; + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); +} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-spotlight/app/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-spotlight/app/page.tsx new file mode 100644 index 000000000000..4214849e642d --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-spotlight/app/page.tsx @@ -0,0 +1,17 @@ +'use client'; + +export default function Home() { + const handleClick = () => { + throw new Error('Spotlight test error!'); + }; + + return ( +
+

Next.js Spotlight E2E Test

+

This page tests that NEXT_PUBLIC_SENTRY_SPOTLIGHT env var enables Spotlight integration.

+ +
+ ); +} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-spotlight/instrumentation-client.ts b/dev-packages/e2e-tests/test-applications/nextjs-spotlight/instrumentation-client.ts new file mode 100644 index 000000000000..8082eef7ac82 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-spotlight/instrumentation-client.ts @@ -0,0 +1,16 @@ +import * as Sentry from '@sentry/nextjs'; + +// Initialize Sentry - the @sentry/nextjs SDK automatically parses +// NEXT_PUBLIC_SENTRY_SPOTLIGHT from process.env (zero-config for Next.js!) +// +// NOTE: We do NOT explicitly set `spotlight` option! +// The SDK should automatically: +// 1. Read NEXT_PUBLIC_SENTRY_SPOTLIGHT from process.env +// 2. Enable Spotlight with the URL from the env var +// 3. Add the spotlightBrowserIntegration to send events to the sidecar +Sentry.init({ + dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN, + tunnel: 'http://localhost:3031/', + tracesSampleRate: 1.0, + environment: 'qa', +}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-spotlight/instrumentation.ts b/dev-packages/e2e-tests/test-applications/nextjs-spotlight/instrumentation.ts new file mode 100644 index 000000000000..93b5b741184e --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-spotlight/instrumentation.ts @@ -0,0 +1,23 @@ +import * as Sentry from '@sentry/nextjs'; + +export async function register() { + if (process.env.NEXT_RUNTIME === 'nodejs') { + // Server-side Sentry init + Sentry.init({ + dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN, + tracesSampleRate: 1.0, + debug: true, + environment: 'qa', + }); + } + + if (process.env.NEXT_RUNTIME === 'edge') { + // Edge runtime Sentry init + Sentry.init({ + dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN, + tracesSampleRate: 1.0, + debug: true, + environment: 'qa', + }); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-spotlight/next.config.js b/dev-packages/e2e-tests/test-applications/nextjs-spotlight/next.config.js new file mode 100644 index 000000000000..1098c2ce5a4f --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-spotlight/next.config.js @@ -0,0 +1,8 @@ +const { withSentryConfig } = require('@sentry/nextjs'); + +/** @type {import('next').NextConfig} */ +const nextConfig = {}; + +module.exports = withSentryConfig(nextConfig, { + silent: true, +}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-spotlight/package.json b/dev-packages/e2e-tests/test-applications/nextjs-spotlight/package.json new file mode 100644 index 000000000000..f792a096ec45 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-spotlight/package.json @@ -0,0 +1,31 @@ +{ + "name": "nextjs-spotlight-test-app", + "version": "0.1.0", + "private": true, + "scripts": { + "build": "NEXT_PUBLIC_SENTRY_SPOTLIGHT=http://localhost:3032/stream next build", + "start": "next start -p 3030", + "dev": "NEXT_PUBLIC_SENTRY_SPOTLIGHT=http://localhost:3032/stream next dev -p 3030", + "test": "playwright test", + "clean": "npx rimraf node_modules pnpm-lock.yaml .next", + "test:build": "pnpm install && pnpm build", + "test:assert": "pnpm test" + }, + "dependencies": { + "@sentry/nextjs": "latest || *", + "@types/node": "^18.19.1", + "@types/react": "18.0.26", + "@types/react-dom": "18.0.9", + "next": "14.2.32", + "react": "18.2.0", + "react-dom": "18.2.0", + "typescript": "~5.0.0" + }, + "devDependencies": { + "@playwright/test": "~1.56.0", + "@sentry-internal/test-utils": "link:../../../test-utils" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/dev-packages/e2e-tests/test-applications/nextjs-spotlight/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/nextjs-spotlight/playwright.config.mjs new file mode 100644 index 000000000000..2d5d3233668c --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-spotlight/playwright.config.mjs @@ -0,0 +1,16 @@ +import { getPlaywrightConfig } from '@sentry-internal/test-utils'; + +const config = getPlaywrightConfig({ + startCommand: 'pnpm start', + port: 3030, +}); + +// Add the Spotlight proxy server as an additional webServer +config.webServer.push({ + command: 'node start-spotlight-proxy.mjs', + port: 3032, + stdout: 'pipe', + stderr: 'pipe', +}); + +export default config; diff --git a/dev-packages/e2e-tests/test-applications/nextjs-spotlight/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/nextjs-spotlight/start-event-proxy.mjs new file mode 100644 index 000000000000..4f637d20a0c9 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-spotlight/start-event-proxy.mjs @@ -0,0 +1,6 @@ +import { startEventProxyServer } from '@sentry-internal/test-utils'; + +startEventProxyServer({ + port: 3031, + proxyServerName: 'nextjs-spotlight', +}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-spotlight/start-spotlight-proxy.mjs b/dev-packages/e2e-tests/test-applications/nextjs-spotlight/start-spotlight-proxy.mjs new file mode 100644 index 000000000000..18c04d195002 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-spotlight/start-spotlight-proxy.mjs @@ -0,0 +1,10 @@ +import { startSpotlightProxyServer } from '@sentry-internal/test-utils'; + +console.log('[Spotlight Proxy] Starting spotlight proxy server on port 3032...'); + +startSpotlightProxyServer({ + port: 3032, + proxyServerName: 'nextjs-spotlight-sidecar', +}); + +console.log('[Spotlight Proxy] Server started successfully on port 3032'); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-spotlight/tests/spotlight.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-spotlight/tests/spotlight.test.ts new file mode 100644 index 000000000000..a89a3921cd1b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-spotlight/tests/spotlight.test.ts @@ -0,0 +1,66 @@ +import { expect, test } from '@playwright/test'; +import { waitForError, waitForSpotlightError, waitForSpotlightTransaction } from '@sentry-internal/test-utils'; + +/** + * Test that NEXT_PUBLIC_SENTRY_SPOTLIGHT environment variable automatically enables Spotlight integration. + * + * This test verifies that: + * 1. The SDK automatically parses NEXT_PUBLIC_SENTRY_SPOTLIGHT from process.env + * 2. The SDK enables Spotlight without explicit configuration + * 3. Events are sent to both the tunnel AND the Spotlight sidecar URL + * + * Test setup: + * - NEXT_PUBLIC_SENTRY_SPOTLIGHT is set to 'http://localhost:3032/stream' at build time + * - tunnel is set to 'http://localhost:3031' for regular event capture + * - A Spotlight proxy server runs on port 3032 to capture Spotlight events + * - A regular event proxy server runs on port 3031 to capture tunnel events + */ +test('NEXT_PUBLIC_SENTRY_SPOTLIGHT env var automatically enables Spotlight', async ({ page }) => { + // Wait for the error to arrive at the regular tunnel (port 3031) + const tunnelErrorPromise = waitForError('nextjs-spotlight', event => { + return !event.type && event.exception?.values?.[0]?.value === 'Spotlight test error!'; + }); + + // Wait for the error event to arrive at the Spotlight sidecar (port 3032) + const spotlightErrorPromise = waitForSpotlightError('nextjs-spotlight-sidecar', event => { + return !event.type && event.exception?.values?.[0]?.value === 'Spotlight test error!'; + }); + + await page.goto('/'); + + const exceptionButton = page.locator('id=exception-button'); + await exceptionButton.click(); + + // Both promises should resolve - the error should be sent to BOTH destinations + const [tunnelError, spotlightError] = await Promise.all([tunnelErrorPromise, spotlightErrorPromise]); + + // Verify the Spotlight sidecar received the error + expect(spotlightError.exception?.values).toHaveLength(1); + expect(spotlightError.exception?.values?.[0]?.value).toBe('Spotlight test error!'); + expect(spotlightError.exception?.values?.[0]?.type).toBe('Error'); + + // Verify the tunnel also received the error (normal Sentry flow still works) + expect(tunnelError.exception?.values).toHaveLength(1); + expect(tunnelError.exception?.values?.[0]?.value).toBe('Spotlight test error!'); + + // Both events should have the same trace context + expect(spotlightError.contexts?.trace?.trace_id).toBe(tunnelError.contexts?.trace?.trace_id); +}); + +/** + * Test that Spotlight receives transaction events as well. + */ +test('NEXT_PUBLIC_SENTRY_SPOTLIGHT automatically sends transactions to sidecar', async ({ page }) => { + // Wait for a pageload transaction to arrive at the Spotlight sidecar + const spotlightTransactionPromise = waitForSpotlightTransaction('nextjs-spotlight-sidecar', event => { + return event.type === 'transaction' && event.contexts?.trace?.op === 'pageload'; + }); + + await page.goto('/'); + + const spotlightTransaction = await spotlightTransactionPromise; + + // Verify the Spotlight sidecar received the transaction + expect(spotlightTransaction.type).toBe('transaction'); + expect(spotlightTransaction.contexts?.trace?.op).toBe('pageload'); +}); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-spotlight/tsconfig.json b/dev-packages/e2e-tests/test-applications/nextjs-spotlight/tsconfig.json new file mode 100644 index 000000000000..23ba4fd54943 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nextjs-spotlight/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/packages/react/src/sdk.ts b/packages/react/src/sdk.ts index 1f26e87c9402..72f7d296529f 100644 --- a/packages/react/src/sdk.ts +++ b/packages/react/src/sdk.ts @@ -26,10 +26,17 @@ export function init(options: BrowserOptions): Client | undefined { // // For option 3, Rollup replaces __VITE_SPOTLIGHT_ENV__ with import.meta.env.VITE_SENTRY_SPOTLIGHT // Then Vite replaces that with the actual value or undefined at build time. - // We use typeof to check if the value exists, which works because Vite does static replacement. + // We wrap in try-catch because in non-Vite ESM environments (like Next.js), import.meta.env may not exist. + let viteSpotlightEnv: string | undefined; + try { + viteSpotlightEnv = typeof __VITE_SPOTLIGHT_ENV__ !== 'undefined' ? __VITE_SPOTLIGHT_ENV__ : undefined; + } catch { + // import.meta.env doesn't exist in this environment (e.g., Next.js with webpack) + } + const spotlightEnvRaw = (typeof process !== 'undefined' && (process.env?.SENTRY_SPOTLIGHT || process.env?.VITE_SENTRY_SPOTLIGHT)) || - (typeof __VITE_SPOTLIGHT_ENV__ !== 'undefined' && __VITE_SPOTLIGHT_ENV__) || + viteSpotlightEnv || undefined; if (spotlightEnvRaw) { diff --git a/packages/solid/src/sdk.ts b/packages/solid/src/sdk.ts index 45dcab9cd25c..9f478a02c4e9 100644 --- a/packages/solid/src/sdk.ts +++ b/packages/solid/src/sdk.ts @@ -22,9 +22,20 @@ export function init(options: BrowserOptions): Client | undefined { // 1. process.env.SENTRY_SPOTLIGHT (all bundlers, requires config) // 2. process.env.VITE_SENTRY_SPOTLIGHT (all bundlers, requires config) // 3. import.meta.env.VITE_SENTRY_SPOTLIGHT (ESM only, zero-config for Vite!) + // + // For option 3, Rollup replaces __VITE_SPOTLIGHT_ENV__ with import.meta.env.VITE_SENTRY_SPOTLIGHT + // Then Vite replaces that with the actual value or undefined at build time. + // We wrap in try-catch because in non-Vite ESM environments (like SolidStart with other bundlers), import.meta.env may not exist. + let viteSpotlightEnv: string | undefined; + try { + viteSpotlightEnv = typeof __VITE_SPOTLIGHT_ENV__ !== 'undefined' ? __VITE_SPOTLIGHT_ENV__ : undefined; + } catch { + // import.meta.env doesn't exist in this environment + } + const spotlightEnvRaw = (typeof process !== 'undefined' && (process.env?.SENTRY_SPOTLIGHT || process.env?.VITE_SENTRY_SPOTLIGHT)) || - (typeof __VITE_SPOTLIGHT_ENV__ !== 'undefined' && __VITE_SPOTLIGHT_ENV__) || + viteSpotlightEnv || undefined; if (spotlightEnvRaw) { diff --git a/packages/svelte/src/sdk.ts b/packages/svelte/src/sdk.ts index bd2e5570f8ef..c9ff5662d6dc 100644 --- a/packages/svelte/src/sdk.ts +++ b/packages/svelte/src/sdk.ts @@ -22,9 +22,20 @@ export function init(options: BrowserOptions): Client | undefined { // 1. process.env.SENTRY_SPOTLIGHT (all bundlers, requires config) // 2. process.env.VITE_SENTRY_SPOTLIGHT (all bundlers, requires config) // 3. import.meta.env.VITE_SENTRY_SPOTLIGHT (ESM only, zero-config for Vite!) + // + // For option 3, Rollup replaces __VITE_SPOTLIGHT_ENV__ with import.meta.env.VITE_SENTRY_SPOTLIGHT + // Then Vite replaces that with the actual value or undefined at build time. + // We wrap in try-catch because in non-Vite ESM environments (like SvelteKit with other bundlers), import.meta.env may not exist. + let viteSpotlightEnv: string | undefined; + try { + viteSpotlightEnv = typeof __VITE_SPOTLIGHT_ENV__ !== 'undefined' ? __VITE_SPOTLIGHT_ENV__ : undefined; + } catch { + // import.meta.env doesn't exist in this environment + } + const spotlightEnvRaw = (typeof process !== 'undefined' && (process.env?.SENTRY_SPOTLIGHT || process.env?.VITE_SENTRY_SPOTLIGHT)) || - (typeof __VITE_SPOTLIGHT_ENV__ !== 'undefined' && __VITE_SPOTLIGHT_ENV__) || + viteSpotlightEnv || undefined; if (spotlightEnvRaw) { diff --git a/packages/vue/src/sdk.ts b/packages/vue/src/sdk.ts index 4d6870673ae6..c2a770cfe479 100644 --- a/packages/vue/src/sdk.ts +++ b/packages/vue/src/sdk.ts @@ -24,9 +24,20 @@ export function init(options: Partial> = {}): Cl // 1. process.env.SENTRY_SPOTLIGHT (all bundlers, requires config) // 2. process.env.VITE_SENTRY_SPOTLIGHT (all bundlers, requires config) // 3. import.meta.env.VITE_SENTRY_SPOTLIGHT (ESM only, zero-config for Vite!) + // + // For option 3, Rollup replaces __VITE_SPOTLIGHT_ENV__ with import.meta.env.VITE_SENTRY_SPOTLIGHT + // Then Vite replaces that with the actual value or undefined at build time. + // We wrap in try-catch because in non-Vite ESM environments (like Nuxt), import.meta.env may not exist. + let viteSpotlightEnv: string | undefined; + try { + viteSpotlightEnv = typeof __VITE_SPOTLIGHT_ENV__ !== 'undefined' ? __VITE_SPOTLIGHT_ENV__ : undefined; + } catch { + // import.meta.env doesn't exist in this environment + } + const spotlightEnvRaw = (typeof process !== 'undefined' && (process.env?.SENTRY_SPOTLIGHT || process.env?.VITE_SENTRY_SPOTLIGHT)) || - (typeof __VITE_SPOTLIGHT_ENV__ !== 'undefined' && __VITE_SPOTLIGHT_ENV__) || + viteSpotlightEnv || undefined; if (spotlightEnvRaw) { From b4b0fb001bfaa03ff9764eec1445713f89a07b44 Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Wed, 31 Dec 2025 23:01:41 +0300 Subject: [PATCH 43/44] chore: Fix formatting --- .../browser-spotlight/src/main.jsx | 26 ++++++++++++------- .../start-spotlight-proxy.mjs | 12 +++++---- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/src/main.jsx b/dev-packages/e2e-tests/test-applications/browser-spotlight/src/main.jsx index 32ca5257bd3c..b157746714e1 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/src/main.jsx +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/src/main.jsx @@ -23,21 +23,27 @@ const initOptions = { debug: true, }; -console.log('[E2E Debug] Init options BEFORE Sentry.init:', JSON.stringify({ - dsn: initOptions.dsn, - spotlight: initOptions.spotlight, - debug: initOptions.debug, -})); +console.log( + '[E2E Debug] Init options BEFORE Sentry.init:', + JSON.stringify({ + dsn: initOptions.dsn, + spotlight: initOptions.spotlight, + debug: initOptions.debug, + }), +); const client = Sentry.init(initOptions); // Debug: Check what the client received const clientOptions = client?.getOptions(); -console.log('[E2E Debug] Client options AFTER Sentry.init:', JSON.stringify({ - dsn: clientOptions?.dsn, - spotlight: clientOptions?.spotlight, - debug: clientOptions?.debug, -})); +console.log( + '[E2E Debug] Client options AFTER Sentry.init:', + JSON.stringify({ + dsn: clientOptions?.dsn, + spotlight: clientOptions?.spotlight, + debug: clientOptions?.debug, + }), +); // Debug: Check if Spotlight integration was added const integrations = clientOptions?.integrations || []; diff --git a/dev-packages/e2e-tests/test-applications/browser-spotlight/start-spotlight-proxy.mjs b/dev-packages/e2e-tests/test-applications/browser-spotlight/start-spotlight-proxy.mjs index 87a7e96d7196..58ffdb73b5f7 100644 --- a/dev-packages/e2e-tests/test-applications/browser-spotlight/start-spotlight-proxy.mjs +++ b/dev-packages/e2e-tests/test-applications/browser-spotlight/start-spotlight-proxy.mjs @@ -7,8 +7,10 @@ console.log('[Spotlight Proxy] Starting spotlight proxy server on port 3032...') startSpotlightProxyServer({ port: 3032, proxyServerName: 'browser-spotlight-sidecar', -}).then(() => { - console.log('[Spotlight Proxy] Server started successfully on port 3032'); -}).catch(err => { - console.error('[Spotlight Proxy] Failed to start server:', err); -}); +}) + .then(() => { + console.log('[Spotlight Proxy] Server started successfully on port 3032'); + }) + .catch(err => { + console.error('[Spotlight Proxy] Failed to start server:', err); + }); From 1527c92e71cb8e39a69d3de35feb1ec3bea538eb Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Wed, 31 Dec 2025 23:31:26 +0300 Subject: [PATCH 44/44] chore: Increase svelte size limit to accommodate try-catch --- .size-limit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.size-limit.js b/.size-limit.js index 79e47192f63a..7b82a3988362 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -171,7 +171,7 @@ module.exports = [ path: 'packages/svelte/build/esm/index.js', import: createImport('init'), gzip: true, - limit: '25.5 KB', + limit: '25.6 KB', }, // Browser CDN bundles {