From 759addaa85a9c9c7ee287a74d37cdb4e06a707f3 Mon Sep 17 00:00:00 2001 From: Aditya Mathur <57684218+MathurAditya724@users.noreply.github.com> Date: Fri, 24 Jul 2026 13:28:32 +0530 Subject: [PATCH 1/2] feat(v10/cloudflare): Add Spotlight integration for local dev event forwarding Backport of: #22490 --- .size-limit.js | 4 +- packages/cloudflare/package.json | 56 ++--- packages/cloudflare/rollup.npm.config.mjs | 1 + packages/cloudflare/src/client.ts | 12 + packages/cloudflare/src/index.ts | 1 + .../cloudflare/src/integrations/spotlight.ts | 89 +++++++ packages/cloudflare/src/options.ts | 38 +++ packages/cloudflare/src/sdk.ts | 11 + .../test/integrations/spotlight.test.ts | 234 ++++++++++++++++++ packages/cloudflare/test/options.test.ts | 50 ++++ 10 files changed, 461 insertions(+), 35 deletions(-) create mode 100644 packages/cloudflare/src/integrations/spotlight.ts create mode 100644 packages/cloudflare/test/integrations/spotlight.test.ts diff --git a/.size-limit.js b/.size-limit.js index 4ff4c7d05f7f..89285dcf1360 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -455,7 +455,7 @@ module.exports = [ // Cloudflare SDK (ESM) - compressed, minified to match `wrangler deploy --dry-run --minify` output { name: '@sentry/cloudflare (withSentry) - minified', - path: 'packages/cloudflare/build/esm/index.js', + path: 'packages/cloudflare/build/esm/prod/index.js', import: createImport('withSentry', 'instrumentDurableObjectWithSentry', 'instrumentWorkflowWithSentry'), ignore: [...builtinModules, ...nodePrefixedBuiltinModules], gzip: false, @@ -475,7 +475,7 @@ module.exports = [ // Cloudflare SDK (ESM) - uncompressed, unminified to match `wrangler deploy --dry-run` output { name: '@sentry/cloudflare (withSentry)', - path: 'packages/cloudflare/build/esm/index.js', + path: 'packages/cloudflare/build/esm/prod/index.js', import: createImport('withSentry', 'instrumentDurableObjectWithSentry', 'instrumentWorkflowWithSentry'), ignore: [...builtinModules, ...nodePrefixedBuiltinModules], gzip: false, diff --git a/packages/cloudflare/package.json b/packages/cloudflare/package.json index cbf07ea70524..fba943865a23 100644 --- a/packages/cloudflare/package.json +++ b/packages/cloudflare/package.json @@ -12,50 +12,40 @@ "files": [ "/build" ], - "main": "build/cjs/index.js", - "module": "build/esm/index.js", + "main": "build/cjs/prod/index.js", + "module": "build/esm/prod/index.js", "types": "build/types/index.d.ts", "exports": { "./package.json": "./package.json", ".": { - "import": { - "types": "./build/types/index.d.ts", - "default": "./build/esm/index.js" + "types": "./build/types/index.d.ts", + "development": { + "import": "./build/esm/dev/index.js", + "require": "./build/cjs/dev/index.js" }, - "require": { - "types": "./build/types/index.d.ts", - "default": "./build/cjs/index.js" + "production": { + "import": "./build/esm/prod/index.js", + "require": "./build/cjs/prod/index.js" + }, + "default": { + "import": "./build/esm/prod/index.js", + "require": "./build/cjs/prod/index.js" } }, "./request": { - "import": { - "types": "./build/types/request.d.ts", - "default": "./build/esm/request.js" - }, - "require": { - "types": "./build/types/request.d.ts", - "default": "./build/cjs/request.js" - } + "types": "./build/types/request.d.ts", + "import": "./build/esm/prod/request.js", + "require": "./build/cjs/prod/request.js" }, "./nodejs_compat": { - "import": { - "types": "./build/types/nodejs_compat/index.d.ts", - "default": "./build/esm/nodejs_compat/index.js" - }, - "require": { - "types": "./build/types/nodejs_compat/index.d.ts", - "default": "./build/cjs/nodejs_compat/index.js" - } + "types": "./build/types/nodejs_compat/index.d.ts", + "import": "./build/esm/prod/nodejs_compat/index.js", + "require": "./build/cjs/prod/nodejs_compat/index.js" }, "./vite": { - "import": { - "types": "./build/types/vite/index.d.ts", - "default": "./build/esm/vite/index.js" - }, - "require": { - "types": "./build/types/vite/index.d.ts", - "default": "./build/cjs/vite/index.js" - } + "types": "./build/types/vite/index.d.ts", + "import": "./build/esm/prod/vite/index.js", + "require": "./build/cjs/prod/vite/index.js" } }, "typesVersions": { @@ -106,7 +96,7 @@ "clean": "rimraf build coverage sentry-cloudflare-*.tgz", "lint:fix": "OXLINT_TSGOLINT_DANGEROUSLY_SUPPRESS_PROGRAM_DIAGNOSTICS=true oxlint . --fix --type-aware", "lint": "OXLINT_TSGOLINT_DANGEROUSLY_SUPPRESS_PROGRAM_DIAGNOSTICS=true oxlint . --type-aware", - "lint:es-compatibility": "es-check es2022 ./build/cjs/*.js && es-check es2022 ./build/esm/*.js --module", + "lint:es-compatibility": "es-check es2022 ./build/cjs/prod/*.js && es-check es2022 ./build/esm/prod/*.js --module", "test": "yarn test:unit", "test:unit": "vitest run", "test:watch": "vitest --watch", diff --git a/packages/cloudflare/rollup.npm.config.mjs b/packages/cloudflare/rollup.npm.config.mjs index 9b674514ca4f..839ccaa4c8db 100644 --- a/packages/cloudflare/rollup.npm.config.mjs +++ b/packages/cloudflare/rollup.npm.config.mjs @@ -4,4 +4,5 @@ export default makeNPMConfigVariants( makeBaseNPMConfig({ entrypoints: ['src/index.ts', 'src/nodejs_compat/index.ts', 'src/vite/index.ts'], }), + { splitDevProd: true }, ); diff --git a/packages/cloudflare/src/client.ts b/packages/cloudflare/src/client.ts index aa5ae4f50a08..84d0cbf52522 100644 --- a/packages/cloudflare/src/client.ts +++ b/packages/cloudflare/src/client.ts @@ -294,6 +294,18 @@ interface BaseCloudflareOptions { * @default false */ instrumentPrototypeMethods?: boolean | string[]; + + /** + * If you use Spotlight by Sentry during development, use + * this option to forward captured Sentry events to Spotlight. + * + * Either set it to true, or provide a specific Spotlight Sidecar URL. + * + * More details: https://spotlightjs.com/ + * + * IMPORTANT: Only set this option to `true` while developing, not in production! + */ + spotlight?: boolean | string; } /** diff --git a/packages/cloudflare/src/index.ts b/packages/cloudflare/src/index.ts index 4a718f10f5ce..1e1896d6bc95 100644 --- a/packages/cloudflare/src/index.ts +++ b/packages/cloudflare/src/index.ts @@ -130,6 +130,7 @@ export { getDefaultIntegrations } from './sdk'; export { httpServerIntegration } from './integrations/httpServer'; export { fetchIntegration } from './integrations/fetch'; +export { spotlightIntegration } from './integrations/spotlight'; export { vercelAIIntegration } from './integrations/tracing/vercelai'; // eslint-disable-next-line typescript/no-deprecated export { honoIntegration } from './integrations/hono'; diff --git a/packages/cloudflare/src/integrations/spotlight.ts b/packages/cloudflare/src/integrations/spotlight.ts new file mode 100644 index 000000000000..ead0e8f552c3 --- /dev/null +++ b/packages/cloudflare/src/integrations/spotlight.ts @@ -0,0 +1,89 @@ +import type { Client, Envelope, IntegrationFn } from '@sentry/core'; +import { debug, defineIntegration, serializeEnvelope, suppressTracing } from '@sentry/core'; +import { DEBUG_BUILD } from '../debug-build'; + +type SpotlightConnectionOptions = { + /** + * Set this if the Spotlight Sidecar is not running on localhost:8969. + * By default, the URL is set to http://localhost:8969/stream + */ + sidecarUrl?: string; +}; + +export const INTEGRATION_NAME = 'Spotlight' as const; + +const _spotlightIntegration = ((options: Partial = {}) => { + const sidecarUrl = options.sidecarUrl || 'http://localhost:8969/stream'; + + return { + name: INTEGRATION_NAME, + setup(client) { + DEBUG_BUILD && debug.log('[Spotlight] Using Sidecar URL', sidecarUrl); + setupSidecarForwarding(client, sidecarUrl); + }, + }; +}) satisfies IntegrationFn; + +/** + * Use this integration to send errors and transactions to Spotlight. + * + * Learn more about spotlight at https://spotlightjs.com + * + * Important: This integration is intended for local development only. + * Each forwarded envelope counts as a Worker subrequest (50 free / 1000 paid + * per invocation), so it should not be enabled in production. + */ +export const spotlightIntegration = defineIntegration(_spotlightIntegration); + +function setupSidecarForwarding(client: Client, sidecarUrl: string): void { + const parsedUrl = parseSidecarUrl(sidecarUrl); + if (!parsedUrl) { + return; + } + + let failCount = 0; + + client.on('beforeEnvelope', (envelope: Envelope) => { + if (failCount > 3) { + DEBUG_BUILD && debug.warn('[Spotlight] Disabled Sentry -> Spotlight forwarding due to too many failed requests'); + return; + } + + const body = serializeEnvelope(envelope); + + suppressTracing(() => { + fetch(parsedUrl.href, { + method: 'POST', + body, + headers: { + 'Content-Type': 'application/x-sentry-envelope', + }, + }).then( + res => { + // Consume the response body to satisfy Cloudflare Workers' requirement + // that all fetch response bodies are read or cancelled. + res.text().catch(() => { + // no-op + }); + + if (res.status >= 200 && res.status < 400) { + failCount = 0; + } + }, + () => { + failCount++; + DEBUG_BUILD && debug.warn('[Spotlight] Failed to send envelope to Spotlight Sidecar'); + }, + ); + }); + }); +} + +function parseSidecarUrl(url: string): URL | undefined { + try { + return new URL(url); + } catch { + DEBUG_BUILD && debug.warn(`[Spotlight] Invalid sidecar URL: ${url}`); + return undefined; + } +} diff --git a/packages/cloudflare/src/options.ts b/packages/cloudflare/src/options.ts index 7506dd34468e..947d6d921892 100644 --- a/packages/cloudflare/src/options.ts +++ b/packages/cloudflare/src/options.ts @@ -54,6 +54,14 @@ export function getFinalOptions(userOptions: CloudflareOptions = {}, env: unknow const tracesSampleRate = userOptions.tracesSampleRate ?? parseFloat(getEnvVar(env, 'SENTRY_TRACES_SAMPLE_RATE') ?? ''); + // Spotlight precedence (mirrors node-core's getSpotlightConfig): + // - false or explicit string from options: use as-is + // - true: enable, but prefer a custom URL from the env var if set + // - undefined: defer entirely to the env var (bool or URL) + /*! rollup-include-development-only */ + const spotlight = getSpotlightFromEnv(userOptions.spotlight, getEnvVar(env, 'SENTRY_SPOTLIGHT')); + /*! rollup-include-development-only-end */ + return { release, ...userOptions, @@ -62,5 +70,35 @@ export function getFinalOptions(userOptions: CloudflareOptions = {}, env: unknow tracesSampleRate: isFinite(tracesSampleRate) ? tracesSampleRate : undefined, debug: userOptions.debug ?? envToBool(getEnvVar(env, 'SENTRY_DEBUG')), tunnel: userOptions.tunnel ?? getEnvVar(env, 'SENTRY_TUNNEL'), + /*! rollup-include-development-only */ + spotlight, + /*! rollup-include-development-only-end */ }; } + +/** + * Resolve the spotlight option from a user-supplied value and an env binding string. + * Mirrors node-core's `getSpotlightConfig` precedence: + * - `false` or explicit string from options → use as-is + * - `true` → enable, but prefer a custom URL from the env var if set + * - `undefined` → defer entirely to the env var (bool or URL) + */ +function getSpotlightFromEnv( + optionsSpotlight: boolean | string | undefined, + envVar: string | undefined, +): boolean | string | undefined { + if (optionsSpotlight === false) { + return false; + } + if (typeof optionsSpotlight === 'string') { + return optionsSpotlight; + } + + // optionsSpotlight is true or undefined + const envBool = envToBool(envVar, { strict: true }); + const envUrl = envBool === null && envVar ? envVar : undefined; + + return optionsSpotlight === true + ? (envUrl ?? true) // true: use env URL if present, otherwise true + : (envBool ?? envUrl); // undefined: use env var (bool or URL) +} diff --git a/packages/cloudflare/src/sdk.ts b/packages/cloudflare/src/sdk.ts index 2bbd704e6004..04f654e6ef2d 100644 --- a/packages/cloudflare/src/sdk.ts +++ b/packages/cloudflare/src/sdk.ts @@ -18,6 +18,7 @@ import { makeFlushLock } from './flush'; import { httpServerIntegration } from './integrations/httpServer'; import { fetchIntegration } from './integrations/fetch'; import { honoIntegration } from './integrations/hono'; +import { INTEGRATION_NAME as SPOTLIGHT_INTEGRATION_NAME, spotlightIntegration } from './integrations/spotlight'; import { setupOpenTelemetryTracer } from './opentelemetry/tracer'; import { makeCloudflareTransport } from './transport'; import { defaultStackParser } from './vendor/stacktrace'; @@ -91,6 +92,16 @@ export function init(options: CloudflareOptions): CloudflareClient | undefined { flushLock, }; + /*! rollup-include-development-only */ + if (options.spotlight && !clientOptions.integrations.some(({ name }) => name === SPOTLIGHT_INTEGRATION_NAME)) { + clientOptions.integrations.push( + spotlightIntegration({ + sidecarUrl: typeof options.spotlight === 'string' ? options.spotlight : undefined, + }), + ); + } + /*! rollup-include-development-only-end */ + /** * The Cloudflare SDK is not OpenTelemetry native, however, we set up some OpenTelemetry compatibility * via a custom trace provider. diff --git a/packages/cloudflare/test/integrations/spotlight.test.ts b/packages/cloudflare/test/integrations/spotlight.test.ts new file mode 100644 index 000000000000..e485715b6e6a --- /dev/null +++ b/packages/cloudflare/test/integrations/spotlight.test.ts @@ -0,0 +1,234 @@ +import type { Envelope, EventEnvelope } from '@sentry/core'; +import { createEnvelope, debug } from '@sentry/core'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { CloudflareClient } from '../../src/client'; +import { INTEGRATION_NAME, spotlightIntegration } from '../../src/integrations/spotlight'; +import { createStackParser } from '@sentry/core'; + +function createTestClient(): CloudflareClient { + return new CloudflareClient({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + tracesSampleRate: 1, + integrations: [], + transport: () => ({ + send: () => Promise.resolve({}), + flush: () => Promise.resolve(true), + }), + stackParser: createStackParser(), + }); +} + +function createTestEnvelope(): EventEnvelope { + return createEnvelope({ event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', sent_at: '123' }, [ + [{ type: 'event' }, { event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2' }], + ]); +} + +describe('Spotlight (Cloudflare)', () => { + const debugWarnSpy = vi.spyOn(debug, 'warn'); + let fetchSpy: ReturnType; + + beforeEach(() => { + vi.clearAllMocks(); + fetchSpy = vi.fn().mockResolvedValue({ + status: 200, + text: () => Promise.resolve(''), + }); + vi.stubGlobal('fetch', fetchSpy); + }); + + afterEach(() => { + vi.unstubAllGlobals(); + }); + + it('has integration name "Spotlight"', () => { + const integration = spotlightIntegration(); + expect(integration.name).toEqual(INTEGRATION_NAME); + expect(integration.name).toEqual('Spotlight'); + }); + + it('registers a callback on the beforeEnvelope hook', () => { + const client = createTestClient(); + const onSpy = vi.spyOn(client, 'on'); + + const integration = spotlightIntegration(); + integration.setup!(client); + + expect(onSpy).toHaveBeenCalledWith('beforeEnvelope', expect.any(Function)); + }); + + it('sends an envelope POST request to the default sidecar URL', () => { + let callback: (envelope: Envelope) => void = () => {}; + const client = createTestClient(); + vi.spyOn(client, 'on').mockImplementationOnce((_hook: string, cb: (envelope: Envelope) => void) => { + callback = cb; + return () => {}; + }); + + const integration = spotlightIntegration(); + integration.setup!(client); + + callback(createTestEnvelope()); + + expect(fetchSpy).toHaveBeenCalledWith( + 'http://localhost:8969/stream', + expect.objectContaining({ + method: 'POST', + headers: { 'Content-Type': 'application/x-sentry-envelope' }, + }), + ); + }); + + it('sends an envelope POST request to a custom sidecar URL', () => { + let callback: (envelope: Envelope) => void = () => {}; + const client = createTestClient(); + vi.spyOn(client, 'on').mockImplementationOnce((_hook: string, cb: (envelope: Envelope) => void) => { + callback = cb; + return () => {}; + }); + + const integration = spotlightIntegration({ sidecarUrl: 'http://mylocalhost:8888/abcd' }); + integration.setup!(client); + + callback(createTestEnvelope()); + + expect(fetchSpy).toHaveBeenCalledWith( + 'http://mylocalhost:8888/abcd', + expect.objectContaining({ + method: 'POST', + headers: { 'Content-Type': 'application/x-sentry-envelope' }, + }), + ); + }); + + it('serializes the envelope body', () => { + let callback: (envelope: Envelope) => void = () => {}; + const client = createTestClient(); + vi.spyOn(client, 'on').mockImplementationOnce((_hook: string, cb: (envelope: Envelope) => void) => { + callback = cb; + return () => {}; + }); + + const integration = spotlightIntegration(); + integration.setup!(client); + + callback(createTestEnvelope()); + + const body = fetchSpy.mock.calls[0]![1].body as string; + expect(body).toContain('aa3ff046696b4bc6b609ce6d28fde9e2'); + expect(typeof body).toBe('string'); + }); + + it('stops forwarding after more than 3 failed requests', async () => { + let callback: (envelope: Envelope) => void = () => {}; + const client = createTestClient(); + vi.spyOn(client, 'on').mockImplementationOnce((_hook: string, cb: (envelope: Envelope) => void) => { + callback = cb; + return () => {}; + }); + + fetchSpy.mockRejectedValue(new Error('connection refused')); + + const integration = spotlightIntegration(); + integration.setup!(client); + + const envelope = createTestEnvelope(); + + // 4 failed requests should trigger the disable + for (let i = 0; i < 4; i++) { + callback(envelope); + await vi.waitFor(() => expect(fetchSpy).toHaveBeenCalledTimes(i + 1)); + } + + fetchSpy.mockClear(); + callback(envelope); + + // Wait a tick to ensure any async handling is done + await new Promise(resolve => setTimeout(resolve, 10)); + + // The 5th call should not reach fetch + expect(fetchSpy).not.toHaveBeenCalled(); + }); + + it('resets fail count on successful request', async () => { + let callback: (envelope: Envelope) => void = () => {}; + const client = createTestClient(); + vi.spyOn(client, 'on').mockImplementationOnce((_hook: string, cb: (envelope: Envelope) => void) => { + callback = cb; + return () => {}; + }); + + // Fail 3 times, then succeed + fetchSpy + .mockRejectedValueOnce(new Error('fail')) + .mockRejectedValueOnce(new Error('fail')) + .mockRejectedValueOnce(new Error('fail')) + .mockResolvedValueOnce({ status: 200, text: () => Promise.resolve('') }); + + const integration = spotlightIntegration(); + integration.setup!(client); + + const envelope = createTestEnvelope(); + + for (let i = 0; i < 4; i++) { + callback(envelope); + await vi.waitFor(() => expect(fetchSpy).toHaveBeenCalledTimes(i + 1)); + } + + // After the success, fail count should be reset, so the next call should go through + fetchSpy.mockResolvedValueOnce({ status: 200, text: () => Promise.resolve('') }); + callback(envelope); + + await vi.waitFor(() => expect(fetchSpy).toHaveBeenCalledTimes(5)); + }); + + it('warns on invalid sidecar URL', () => { + const client = createTestClient(); + + const integration = spotlightIntegration({ sidecarUrl: 'not-a-valid-url' }); + integration.setup!(client); + + expect(debugWarnSpy).toHaveBeenCalledWith(expect.stringContaining('Invalid sidecar URL: not-a-valid-url')); + }); + + it('does not call fetch for invalid sidecar URL', () => { + let callback: (envelope: Envelope) => void = () => {}; + const client = createTestClient(); + vi.spyOn(client, 'on').mockImplementationOnce((_hook: string, cb: (envelope: Envelope) => void) => { + callback = cb; + return () => {}; + }); + + const integration = spotlightIntegration({ sidecarUrl: 'not-a-valid-url' }); + integration.setup!(client); + + // If the URL is invalid, the beforeEnvelope hook is never registered + // so callback is never replaced — it's still the no-op default + callback(createTestEnvelope()); + + expect(fetchSpy).not.toHaveBeenCalled(); + }); + + it('does not increment fail count on 4xx/5xx responses', async () => { + let callback: (envelope: Envelope) => void = () => {}; + const client = createTestClient(); + vi.spyOn(client, 'on').mockImplementationOnce((_hook: string, cb: (envelope: Envelope) => void) => { + callback = cb; + return () => {}; + }); + + fetchSpy.mockResolvedValue({ status: 500, text: () => Promise.resolve('') }); + + const integration = spotlightIntegration(); + integration.setup!(client); + + const envelope = createTestEnvelope(); + + // 5 calls with 500 status — fail count is NOT incremented for HTTP errors, + // only for network rejections, so fetch should still be called each time + for (let i = 0; i < 5; i++) { + callback(envelope); + await vi.waitFor(() => expect(fetchSpy).toHaveBeenCalledTimes(i + 1)); + } + }); +}); diff --git a/packages/cloudflare/test/options.test.ts b/packages/cloudflare/test/options.test.ts index 9dc21606b445..e999df4b7970 100644 --- a/packages/cloudflare/test/options.test.ts +++ b/packages/cloudflare/test/options.test.ts @@ -189,4 +189,54 @@ describe('getFinalOptions', () => { expect(result).toEqual(expect.objectContaining({ dsn: 'test-dsn', release: undefined })); }); }); + + describe('SENTRY_SPOTLIGHT', () => { + it('reads SENTRY_SPOTLIGHT boolean "true" from env', () => { + const result = getFinalOptions({}, { SENTRY_SPOTLIGHT: 'true' }); + expect(result.spotlight).toBe(true); + }); + + it('reads SENTRY_SPOTLIGHT boolean "false" from env', () => { + const result = getFinalOptions({}, { SENTRY_SPOTLIGHT: 'false' }); + expect(result.spotlight).toBe(false); + }); + + it('reads SENTRY_SPOTLIGHT URL string from env', () => { + const result = getFinalOptions({}, { SENTRY_SPOTLIGHT: 'http://localhost:9999/stream' }); + expect(result.spotlight).toBe('http://localhost:9999/stream'); + }); + + it('user option takes precedence over env', () => { + const result = getFinalOptions({ spotlight: false }, { SENTRY_SPOTLIGHT: 'true' }); + expect(result.spotlight).toBe(false); + }); + + it('user option string takes precedence over env', () => { + const result = getFinalOptions( + { spotlight: 'http://custom:1234/stream' }, + { SENTRY_SPOTLIGHT: 'http://other:5678/stream' }, + ); + expect(result.spotlight).toBe('http://custom:1234/stream'); + }); + + it('returns undefined when SENTRY_SPOTLIGHT is not set', () => { + const result = getFinalOptions({}, { SENTRY_DSN: 'test-dsn' }); + expect(result.spotlight).toBeUndefined(); + }); + + it('spotlight: true prefers env URL over boolean true', () => { + const result = getFinalOptions({ spotlight: true }, { SENTRY_SPOTLIGHT: 'http://custom:1234/stream' }); + expect(result.spotlight).toBe('http://custom:1234/stream'); + }); + + it('spotlight: true stays true when env is boolean "true"', () => { + const result = getFinalOptions({ spotlight: true }, { SENTRY_SPOTLIGHT: 'true' }); + expect(result.spotlight).toBe(true); + }); + + it('spotlight: true stays true when env is not set', () => { + const result = getFinalOptions({ spotlight: true }, {}); + expect(result.spotlight).toBe(true); + }); + }); }); From 0e163d19b63b4ba748f5be552b2bdd7b654e51b5 Mon Sep 17 00:00:00 2001 From: JPeer264 Date: Wed, 29 Jul 2026 08:35:06 +0200 Subject: [PATCH 2/2] Revert "feat(cloudflare): Add dev/prod bundle in favor of spotlight" This reverts commit dff8f1b52467c03f40bc058be25f2ccd67ec27e1. --- .size-limit.js | 6 +-- packages/cloudflare/package.json | 56 +++++++++++++---------- packages/cloudflare/rollup.npm.config.mjs | 1 - packages/cloudflare/src/options.ts | 4 -- packages/cloudflare/src/sdk.ts | 2 - 5 files changed, 36 insertions(+), 33 deletions(-) diff --git a/.size-limit.js b/.size-limit.js index 89285dcf1360..7a455389c1be 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -455,7 +455,7 @@ module.exports = [ // Cloudflare SDK (ESM) - compressed, minified to match `wrangler deploy --dry-run --minify` output { name: '@sentry/cloudflare (withSentry) - minified', - path: 'packages/cloudflare/build/esm/prod/index.js', + path: 'packages/cloudflare/build/esm/index.js', import: createImport('withSentry', 'instrumentDurableObjectWithSentry', 'instrumentWorkflowWithSentry'), ignore: [...builtinModules, ...nodePrefixedBuiltinModules], gzip: false, @@ -475,12 +475,12 @@ module.exports = [ // Cloudflare SDK (ESM) - uncompressed, unminified to match `wrangler deploy --dry-run` output { name: '@sentry/cloudflare (withSentry)', - path: 'packages/cloudflare/build/esm/prod/index.js', + path: 'packages/cloudflare/build/esm/index.js', import: createImport('withSentry', 'instrumentDurableObjectWithSentry', 'instrumentWorkflowWithSentry'), ignore: [...builtinModules, ...nodePrefixedBuiltinModules], gzip: false, brotli: false, - limit: '480 KiB', + limit: '490 KiB', disablePlugins: ['@size-limit/webpack'], webpack: false, modifyEsbuildConfig: function (config) { diff --git a/packages/cloudflare/package.json b/packages/cloudflare/package.json index fba943865a23..cbf07ea70524 100644 --- a/packages/cloudflare/package.json +++ b/packages/cloudflare/package.json @@ -12,40 +12,50 @@ "files": [ "/build" ], - "main": "build/cjs/prod/index.js", - "module": "build/esm/prod/index.js", + "main": "build/cjs/index.js", + "module": "build/esm/index.js", "types": "build/types/index.d.ts", "exports": { "./package.json": "./package.json", ".": { - "types": "./build/types/index.d.ts", - "development": { - "import": "./build/esm/dev/index.js", - "require": "./build/cjs/dev/index.js" + "import": { + "types": "./build/types/index.d.ts", + "default": "./build/esm/index.js" }, - "production": { - "import": "./build/esm/prod/index.js", - "require": "./build/cjs/prod/index.js" - }, - "default": { - "import": "./build/esm/prod/index.js", - "require": "./build/cjs/prod/index.js" + "require": { + "types": "./build/types/index.d.ts", + "default": "./build/cjs/index.js" } }, "./request": { - "types": "./build/types/request.d.ts", - "import": "./build/esm/prod/request.js", - "require": "./build/cjs/prod/request.js" + "import": { + "types": "./build/types/request.d.ts", + "default": "./build/esm/request.js" + }, + "require": { + "types": "./build/types/request.d.ts", + "default": "./build/cjs/request.js" + } }, "./nodejs_compat": { - "types": "./build/types/nodejs_compat/index.d.ts", - "import": "./build/esm/prod/nodejs_compat/index.js", - "require": "./build/cjs/prod/nodejs_compat/index.js" + "import": { + "types": "./build/types/nodejs_compat/index.d.ts", + "default": "./build/esm/nodejs_compat/index.js" + }, + "require": { + "types": "./build/types/nodejs_compat/index.d.ts", + "default": "./build/cjs/nodejs_compat/index.js" + } }, "./vite": { - "types": "./build/types/vite/index.d.ts", - "import": "./build/esm/prod/vite/index.js", - "require": "./build/cjs/prod/vite/index.js" + "import": { + "types": "./build/types/vite/index.d.ts", + "default": "./build/esm/vite/index.js" + }, + "require": { + "types": "./build/types/vite/index.d.ts", + "default": "./build/cjs/vite/index.js" + } } }, "typesVersions": { @@ -96,7 +106,7 @@ "clean": "rimraf build coverage sentry-cloudflare-*.tgz", "lint:fix": "OXLINT_TSGOLINT_DANGEROUSLY_SUPPRESS_PROGRAM_DIAGNOSTICS=true oxlint . --fix --type-aware", "lint": "OXLINT_TSGOLINT_DANGEROUSLY_SUPPRESS_PROGRAM_DIAGNOSTICS=true oxlint . --type-aware", - "lint:es-compatibility": "es-check es2022 ./build/cjs/prod/*.js && es-check es2022 ./build/esm/prod/*.js --module", + "lint:es-compatibility": "es-check es2022 ./build/cjs/*.js && es-check es2022 ./build/esm/*.js --module", "test": "yarn test:unit", "test:unit": "vitest run", "test:watch": "vitest --watch", diff --git a/packages/cloudflare/rollup.npm.config.mjs b/packages/cloudflare/rollup.npm.config.mjs index 839ccaa4c8db..9b674514ca4f 100644 --- a/packages/cloudflare/rollup.npm.config.mjs +++ b/packages/cloudflare/rollup.npm.config.mjs @@ -4,5 +4,4 @@ export default makeNPMConfigVariants( makeBaseNPMConfig({ entrypoints: ['src/index.ts', 'src/nodejs_compat/index.ts', 'src/vite/index.ts'], }), - { splitDevProd: true }, ); diff --git a/packages/cloudflare/src/options.ts b/packages/cloudflare/src/options.ts index 947d6d921892..28e5f911d8b0 100644 --- a/packages/cloudflare/src/options.ts +++ b/packages/cloudflare/src/options.ts @@ -58,9 +58,7 @@ export function getFinalOptions(userOptions: CloudflareOptions = {}, env: unknow // - false or explicit string from options: use as-is // - true: enable, but prefer a custom URL from the env var if set // - undefined: defer entirely to the env var (bool or URL) - /*! rollup-include-development-only */ const spotlight = getSpotlightFromEnv(userOptions.spotlight, getEnvVar(env, 'SENTRY_SPOTLIGHT')); - /*! rollup-include-development-only-end */ return { release, @@ -70,9 +68,7 @@ export function getFinalOptions(userOptions: CloudflareOptions = {}, env: unknow tracesSampleRate: isFinite(tracesSampleRate) ? tracesSampleRate : undefined, debug: userOptions.debug ?? envToBool(getEnvVar(env, 'SENTRY_DEBUG')), tunnel: userOptions.tunnel ?? getEnvVar(env, 'SENTRY_TUNNEL'), - /*! rollup-include-development-only */ spotlight, - /*! rollup-include-development-only-end */ }; } diff --git a/packages/cloudflare/src/sdk.ts b/packages/cloudflare/src/sdk.ts index 04f654e6ef2d..7cb21103987c 100644 --- a/packages/cloudflare/src/sdk.ts +++ b/packages/cloudflare/src/sdk.ts @@ -92,7 +92,6 @@ export function init(options: CloudflareOptions): CloudflareClient | undefined { flushLock, }; - /*! rollup-include-development-only */ if (options.spotlight && !clientOptions.integrations.some(({ name }) => name === SPOTLIGHT_INTEGRATION_NAME)) { clientOptions.integrations.push( spotlightIntegration({ @@ -100,7 +99,6 @@ export function init(options: CloudflareOptions): CloudflareClient | undefined { }), ); } - /*! rollup-include-development-only-end */ /** * The Cloudflare SDK is not OpenTelemetry native, however, we set up some OpenTelemetry compatibility