From 704a781e2af0d9f62c0462627dda862e874ce257 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Wed, 7 Jan 2026 14:19:06 +0100 Subject: [PATCH 01/31] Add placeholder vite config wrapper --- packages/tanstackstart-react/package.json | 3 + .../tanstackstart-react/src/config/index.ts | 3 +- .../tanstackstart-react/src/config/types.ts | 65 +++++++++++++++++++ .../src/config/wrapConfigWithSentry.ts | 19 ++++++ packages/tanstackstart-react/tsconfig.json | 6 +- 5 files changed, 90 insertions(+), 6 deletions(-) create mode 100644 packages/tanstackstart-react/src/config/types.ts create mode 100644 packages/tanstackstart-react/src/config/wrapConfigWithSentry.ts diff --git a/packages/tanstackstart-react/package.json b/packages/tanstackstart-react/package.json index a688622dbdaa..d0635b68cd0a 100644 --- a/packages/tanstackstart-react/package.json +++ b/packages/tanstackstart-react/package.json @@ -57,6 +57,9 @@ "@sentry/node": "10.32.1", "@sentry/react": "10.32.1" }, + "devDependencies": { + "vite": "^5.4.11" + }, "scripts": { "build": "run-p build:transpile build:types", "build:dev": "yarn build", diff --git a/packages/tanstackstart-react/src/config/index.ts b/packages/tanstackstart-react/src/config/index.ts index cb0ff5c3b541..01b192c3c744 100644 --- a/packages/tanstackstart-react/src/config/index.ts +++ b/packages/tanstackstart-react/src/config/index.ts @@ -1 +1,2 @@ -export {}; +export * from './types'; +export { wrapConfigWithSentry } from './wrapConfigWithSentry'; diff --git a/packages/tanstackstart-react/src/config/types.ts b/packages/tanstackstart-react/src/config/types.ts new file mode 100644 index 000000000000..af3ecdf9cafc --- /dev/null +++ b/packages/tanstackstart-react/src/config/types.ts @@ -0,0 +1,65 @@ +type SourceMapsOptions = { + /** + * If this flag is `true`, and an auth token is detected, the Sentry SDK will + * automatically generate and upload source maps to Sentry during a production build. + * + * @default true + */ + enabled?: boolean; + + /** + * If this flag is `true`, the Sentry plugin will collect some telemetry data and send it to Sentry. + * It will not collect any sensitive or user-specific data. + * + * @default true + */ + telemetry?: boolean; + + /** + * A glob or an array of globs that specifies the build artifacts that should be deleted after the artifact + * upload to Sentry has been completed. + * + * @default [] - By default no files are deleted. + * + * The globbing patterns follow the implementation of the glob package. (https://www.npmjs.com/package/glob) + */ + filesToDeleteAfterUpload?: string | Array; +}; + +/** + * Build options for the Sentry plugin. These options are used during build-time by the Sentry SDK. + */ +export type SentryTanstackStartReactPluginOptions = { + /** + * The auth token to use when uploading source maps to Sentry. + * + * Instead of specifying this option, you can also set the `SENTRY_AUTH_TOKEN` environment variable. + * + * To create an auth token, follow this guide: + * @see https://docs.sentry.io/product/accounts/auth-tokens/#organization-auth-tokens + */ + authToken?: string; + + /** + * The organization slug of your Sentry organization. + * Instead of specifying this option, you can also set the `SENTRY_ORG` environment variable. + */ + org?: string; + + /** + * The project slug of your Sentry project. + * Instead of specifying this option, you can also set the `SENTRY_PROJECT` environment variable. + */ + project?: string; + + /** + * Options for the Sentry Vite plugin to customize the source maps upload process. + */ + sourceMapsUploadOptions?: SourceMapsOptions; + + /** + * Enable debug functionality of the SDK during build-time. + * Enabling this will give you, for example logs about source maps. + */ + debug?: boolean; +}; diff --git a/packages/tanstackstart-react/src/config/wrapConfigWithSentry.ts b/packages/tanstackstart-react/src/config/wrapConfigWithSentry.ts new file mode 100644 index 000000000000..fd7be4976b30 --- /dev/null +++ b/packages/tanstackstart-react/src/config/wrapConfigWithSentry.ts @@ -0,0 +1,19 @@ +import type { UserConfig } from 'vite'; +import type { SentryTanstackStartReactPluginOptions } from './types'; + +/** + * Wraps a Vite configuration with Sentry build-time enhancements such as + * automatic source maps upload. + * + * @param config - A Vite configuration object + * @param sentryPluginOptions - Options to configure the Sentry Vite plugin + * @returns The modified Vite config + */ +export function wrapConfigWithSentry( + config: UserConfig, + _sentryPluginOptions: SentryTanstackStartReactPluginOptions = {}, +): UserConfig { + // TODO: Add Sentry Vite plugins for source map upload + console.log('wrapConfigWithSentry', config, _sentryPluginOptions); + return config; +} diff --git a/packages/tanstackstart-react/tsconfig.json b/packages/tanstackstart-react/tsconfig.json index 220ba3fa2b86..9399ef75ead6 100644 --- a/packages/tanstackstart-react/tsconfig.json +++ b/packages/tanstackstart-react/tsconfig.json @@ -1,9 +1,5 @@ { "extends": "../../tsconfig.json", "include": ["src/**/*"], - "compilerOptions": { - "lib": ["es2020"], - "module": "Node16", - "moduleResolution": "Node16" - } + "compilerOptions": {} } From 6cc5784364568e91b57a400592c819deee859dd3 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Wed, 7 Jan 2026 14:29:32 +0100 Subject: [PATCH 02/31] align usage with solidstart --- .../src/config/wrapConfigWithSentry.ts | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/packages/tanstackstart-react/src/config/wrapConfigWithSentry.ts b/packages/tanstackstart-react/src/config/wrapConfigWithSentry.ts index fd7be4976b30..ca78c4eb9a03 100644 --- a/packages/tanstackstart-react/src/config/wrapConfigWithSentry.ts +++ b/packages/tanstackstart-react/src/config/wrapConfigWithSentry.ts @@ -2,15 +2,36 @@ import type { UserConfig } from 'vite'; import type { SentryTanstackStartReactPluginOptions } from './types'; /** - * Wraps a Vite configuration with Sentry build-time enhancements such as + * Wraps a Vite configuration object with Sentry build-time enhancements such as * automatic source maps upload. * + * @example + * ```typescript + * // vite.config.ts + * import { defineConfig } from 'vite'; + * import { wrapConfigWithSentry } from '@sentry/tanstackstart-react'; + * + * export default defineConfig( + * wrapConfigWithSentry( + * { + * // Your Vite/TanStack Start config + * plugins: [...] + * }, + * { + * // Sentry build-time options + * org: 'your-org', + * project: 'your-project', + * }, + * ), + * ); + * ``` + * * @param config - A Vite configuration object * @param sentryPluginOptions - Options to configure the Sentry Vite plugin - * @returns The modified Vite config + * @returns The modified Vite config to be passed to `defineConfig` */ export function wrapConfigWithSentry( - config: UserConfig, + config: UserConfig = {}, _sentryPluginOptions: SentryTanstackStartReactPluginOptions = {}, ): UserConfig { // TODO: Add Sentry Vite plugins for source map upload From b4a0d839e7b816cbf8f1ce994108ad33b70f9d5c Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Wed, 7 Jan 2026 14:57:12 +0100 Subject: [PATCH 03/31] Add placeholder to add plugins --- .../src/config/wrapConfigWithSentry.ts | 13 +++++++++---- .../src/vite/addSentryPlugins.ts | 13 +++++++++++++ packages/tanstackstart-react/src/vite/index.ts | 1 + 3 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 packages/tanstackstart-react/src/vite/addSentryPlugins.ts create mode 100644 packages/tanstackstart-react/src/vite/index.ts diff --git a/packages/tanstackstart-react/src/config/wrapConfigWithSentry.ts b/packages/tanstackstart-react/src/config/wrapConfigWithSentry.ts index ca78c4eb9a03..2949ae2c591d 100644 --- a/packages/tanstackstart-react/src/config/wrapConfigWithSentry.ts +++ b/packages/tanstackstart-react/src/config/wrapConfigWithSentry.ts @@ -1,5 +1,6 @@ import type { UserConfig } from 'vite'; import type { SentryTanstackStartReactPluginOptions } from './types'; +import { addSentryPlugins } from '../vite'; /** * Wraps a Vite configuration object with Sentry build-time enhancements such as @@ -32,9 +33,13 @@ import type { SentryTanstackStartReactPluginOptions } from './types'; */ export function wrapConfigWithSentry( config: UserConfig = {}, - _sentryPluginOptions: SentryTanstackStartReactPluginOptions = {}, + sentryPluginOptions: SentryTanstackStartReactPluginOptions = {}, ): UserConfig { - // TODO: Add Sentry Vite plugins for source map upload - console.log('wrapConfigWithSentry', config, _sentryPluginOptions); - return config; + const userPlugins = Array.isArray(config.plugins) ? [...config.plugins] : []; + const plugins = addSentryPlugins(userPlugins, sentryPluginOptions); + + return { + ...config, + plugins, + }; } diff --git a/packages/tanstackstart-react/src/vite/addSentryPlugins.ts b/packages/tanstackstart-react/src/vite/addSentryPlugins.ts new file mode 100644 index 000000000000..4d6721d6413e --- /dev/null +++ b/packages/tanstackstart-react/src/vite/addSentryPlugins.ts @@ -0,0 +1,13 @@ +import type { PluginOption } from 'vite'; +import type { SentryTanstackStartReactPluginOptions } from '../config/types'; + +/** + * Adds Sentry plugins to the given array of Vite plugins. + */ +export function addSentryPlugins( + plugins: PluginOption[], + _options: SentryTanstackStartReactPluginOptions, +): PluginOption[] { + // TODO: Add Sentry Vite plugins for source map upload + return plugins; +} diff --git a/packages/tanstackstart-react/src/vite/index.ts b/packages/tanstackstart-react/src/vite/index.ts new file mode 100644 index 000000000000..e66ccf733bd2 --- /dev/null +++ b/packages/tanstackstart-react/src/vite/index.ts @@ -0,0 +1 @@ +export { addSentryPlugins } from './addSentryPlugins'; From b6ddbffa7ebc66ffa1feee0f2946c601c550eca8 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Wed, 7 Jan 2026 15:18:51 +0100 Subject: [PATCH 04/31] Add sentry vite plugin and enable source maps plugins automatically --- packages/solidstart/src/vite/sourceMaps.ts | 2 +- packages/tanstackstart-react/package.json | 3 +- .../tanstackstart-react/src/config/types.ts | 46 ++++++ .../src/config/wrapConfigWithSentry.ts | 4 +- .../src/vite/addSentryPlugins.ts | 26 +++- .../src/vite/sourceMaps.ts | 136 ++++++++++++++++++ 6 files changed, 209 insertions(+), 8 deletions(-) create mode 100644 packages/tanstackstart-react/src/vite/sourceMaps.ts diff --git a/packages/solidstart/src/vite/sourceMaps.ts b/packages/solidstart/src/vite/sourceMaps.ts index 0cd44e6a61c7..8d10e6a16112 100644 --- a/packages/solidstart/src/vite/sourceMaps.ts +++ b/packages/solidstart/src/vite/sourceMaps.ts @@ -76,7 +76,7 @@ export function makeEnableSourceMapsVitePlugin(options: SentrySolidStartPluginOp ]; } -/** There are 3 ways to set up source map generation (https://github.com/getsentry/sentry-j avascript/issues/13993) +/** There are 3 ways to set up source map generation (https://github.com/getsentry/sentry-javascript/issues/13993) * * 1. User explicitly disabled source maps * - keep this setting (emit a warning that errors won't be unminified in Sentry) diff --git a/packages/tanstackstart-react/package.json b/packages/tanstackstart-react/package.json index d0635b68cd0a..0b2acc3760ab 100644 --- a/packages/tanstackstart-react/package.json +++ b/packages/tanstackstart-react/package.json @@ -55,7 +55,8 @@ "@sentry-internal/browser-utils": "10.32.1", "@sentry/core": "10.32.1", "@sentry/node": "10.32.1", - "@sentry/react": "10.32.1" + "@sentry/react": "10.32.1", + "@sentry/vite-plugin": "^4.6.1" }, "devDependencies": { "vite": "^5.4.11" diff --git a/packages/tanstackstart-react/src/config/types.ts b/packages/tanstackstart-react/src/config/types.ts index af3ecdf9cafc..196ca29721ae 100644 --- a/packages/tanstackstart-react/src/config/types.ts +++ b/packages/tanstackstart-react/src/config/types.ts @@ -1,3 +1,44 @@ +type BundleSizeOptimizationOptions = { + /** + * If set to `true`, the plugin will attempt to tree-shake (remove) any debugging code within the Sentry SDK. + * Note that the success of this depends on tree shaking being enabled in your build tooling. + * + * Setting this option to `true` will disable features like the SDK's `debug` option. + */ + excludeDebugStatements?: boolean; + + /** + * If set to true, the plugin will try to tree-shake tracing statements out. + * Note that the success of this depends on tree shaking generally being enabled in your build. + * Attention: DO NOT enable this when you're using any performance monitoring-related SDK features (e.g. Sentry.startSpan()). + */ + excludeTracing?: boolean; + + /** + * If set to `true`, the plugin will attempt to tree-shake (remove) code related to the Sentry SDK's Session Replay Shadow DOM recording functionality. + * Note that the success of this depends on tree shaking being enabled in your build tooling. + * + * This option is safe to be used when you do not want to capture any Shadow DOM activity via Sentry Session Replay. + */ + excludeReplayShadowDom?: boolean; + + /** + * If set to `true`, the plugin will attempt to tree-shake (remove) code related to the Sentry SDK's Session Replay `iframe` recording functionality. + * Note that the success of this depends on tree shaking being enabled in your build tooling. + * + * You can safely do this when you do not want to capture any `iframe` activity via Sentry Session Replay. + */ + excludeReplayIframe?: boolean; + + /** + * If set to `true`, the plugin will attempt to tree-shake (remove) code related to the Sentry SDK's Session Replay's Compression Web Worker. + * Note that the success of this depends on tree shaking being enabled in your build tooling. + * + * **Notice:** You should only do use this option if you manually host a compression worker and configure it in your Sentry Session Replay integration config via the `workerUrl` option. + */ + excludeReplayWorker?: boolean; +}; + type SourceMapsOptions = { /** * If this flag is `true`, and an auth token is detected, the Sentry SDK will @@ -57,6 +98,11 @@ export type SentryTanstackStartReactPluginOptions = { */ sourceMapsUploadOptions?: SourceMapsOptions; + /** + * Options for the Sentry Vite plugin to customize bundle size optimizations. + */ + bundleSizeOptimizations?: BundleSizeOptimizationOptions; + /** * Enable debug functionality of the SDK during build-time. * Enabling this will give you, for example logs about source maps. diff --git a/packages/tanstackstart-react/src/config/wrapConfigWithSentry.ts b/packages/tanstackstart-react/src/config/wrapConfigWithSentry.ts index 2949ae2c591d..97f9be50afc6 100644 --- a/packages/tanstackstart-react/src/config/wrapConfigWithSentry.ts +++ b/packages/tanstackstart-react/src/config/wrapConfigWithSentry.ts @@ -1,6 +1,6 @@ import type { UserConfig } from 'vite'; -import type { SentryTanstackStartReactPluginOptions } from './types'; import { addSentryPlugins } from '../vite'; +import type { SentryTanstackStartReactPluginOptions } from './types'; /** * Wraps a Vite configuration object with Sentry build-time enhancements such as @@ -36,7 +36,7 @@ export function wrapConfigWithSentry( sentryPluginOptions: SentryTanstackStartReactPluginOptions = {}, ): UserConfig { const userPlugins = Array.isArray(config.plugins) ? [...config.plugins] : []; - const plugins = addSentryPlugins(userPlugins, sentryPluginOptions); + const plugins = addSentryPlugins(userPlugins, sentryPluginOptions, config); return { ...config, diff --git a/packages/tanstackstart-react/src/vite/addSentryPlugins.ts b/packages/tanstackstart-react/src/vite/addSentryPlugins.ts index 4d6721d6413e..56b8b8c8a2ad 100644 --- a/packages/tanstackstart-react/src/vite/addSentryPlugins.ts +++ b/packages/tanstackstart-react/src/vite/addSentryPlugins.ts @@ -1,13 +1,31 @@ -import type { PluginOption } from 'vite'; +import type { PluginOption, UserConfig } from 'vite'; import type { SentryTanstackStartReactPluginOptions } from '../config/types'; +import { makeAddSentryVitePlugin, makeEnableSourceMapsVitePlugin } from './sourceMaps'; /** * Adds Sentry plugins to the given array of Vite plugins. */ export function addSentryPlugins( plugins: PluginOption[], - _options: SentryTanstackStartReactPluginOptions, + options: SentryTanstackStartReactPluginOptions, + viteConfig: UserConfig, ): PluginOption[] { - // TODO: Add Sentry Vite plugins for source map upload - return plugins; + const sentryPlugins: PluginOption[] = []; + + // Only add source map plugins in production builds + if (process.env.NODE_ENV !== 'development') { + // Check if source maps upload is enabled + // Default to enabled + const sourceMapsEnabled = options.sourceMapsUploadOptions?.enabled ?? true; + + if (sourceMapsEnabled) { + const sourceMapsPlugins = makeAddSentryVitePlugin(options, viteConfig); + const enableSourceMapsPlugin = makeEnableSourceMapsVitePlugin(options); + + sentryPlugins.push(...sourceMapsPlugins, ...enableSourceMapsPlugin); + } + } + + // Prepend Sentry plugins so they run first + return [...sentryPlugins, ...plugins]; } diff --git a/packages/tanstackstart-react/src/vite/sourceMaps.ts b/packages/tanstackstart-react/src/vite/sourceMaps.ts new file mode 100644 index 000000000000..1d012df64e33 --- /dev/null +++ b/packages/tanstackstart-react/src/vite/sourceMaps.ts @@ -0,0 +1,136 @@ +import { sentryVitePlugin } from '@sentry/vite-plugin'; +import type { Plugin, UserConfig } from 'vite'; +import type { SentryTanstackStartReactPluginOptions } from '../config/types'; + +/** + * A Sentry plugin for adding the @sentry/vite-plugin to automatically upload source maps to Sentry. + */ +export function makeAddSentryVitePlugin( + options: SentryTanstackStartReactPluginOptions, + viteConfig: UserConfig, +): Plugin[] { + const { authToken, bundleSizeOptimizations, debug, org, project, sourceMapsUploadOptions } = options; + + let updatedFilesToDeleteAfterUpload: string[] | undefined = undefined; + + if ( + typeof sourceMapsUploadOptions?.filesToDeleteAfterUpload === 'undefined' && + // Only if source maps were previously not set, we update the "filesToDeleteAfterUpload" (as we override the setting with "hidden") + typeof viteConfig.build?.sourcemap === 'undefined' + ) { + // For .output, .vercel, .netlify etc. + updatedFilesToDeleteAfterUpload = ['.*/**/*.map']; + + if (debug) { + // eslint-disable-next-line no-console + console.log( + `[Sentry] Automatically setting \`sourceMapsUploadOptions.filesToDeleteAfterUpload: ${JSON.stringify( + updatedFilesToDeleteAfterUpload, + )}\` to delete generated source maps after they were uploaded to Sentry.`, + ); + } + } + + return [ + ...sentryVitePlugin({ + authToken: authToken ?? process.env.SENTRY_AUTH_TOKEN, + bundleSizeOptimizations: bundleSizeOptimizations ?? undefined, + debug: debug ?? false, + org: org ?? process.env.SENTRY_ORG, + project: project ?? process.env.SENTRY_PROJECT, + sourcemaps: { + filesToDeleteAfterUpload: sourceMapsUploadOptions?.filesToDeleteAfterUpload ?? updatedFilesToDeleteAfterUpload, + }, + telemetry: sourceMapsUploadOptions?.telemetry ?? true, + _metaOptions: { + telemetry: { + metaFramework: 'tanstackstart-react', + }, + }, + }), + ]; +} + +/** + * A Sentry plugin for TanStack Start React to enable "hidden" source maps if they are unset. + */ +export function makeEnableSourceMapsVitePlugin(options: SentryTanstackStartReactPluginOptions): Plugin[] { + return [ + { + name: 'sentry-tanstackstart-react-source-maps', + apply: 'build', + enforce: 'post', + config(viteConfig) { + return { + ...viteConfig, + build: { + ...viteConfig.build, + sourcemap: getUpdatedSourceMapSettings(viteConfig, options), + }, + }; + }, + }, + ]; +} + +/** There are 3 ways to set up source map generation (https://github.com/getsentry/sentry-javascript/issues/13993) + * + * 1. User explicitly disabled source maps + * - keep this setting (emit a warning that errors won't be unminified in Sentry) + * - We won't upload anything + * + * 2. Users enabled source map generation (true, 'hidden', 'inline'). + * - keep this setting (don't do anything - like deletion - besides uploading) + * + * 3. Users didn't set source maps generation + * - we enable 'hidden' source maps generation + * - configure `filesToDeleteAfterUpload` to delete all .map files (we emit a log about this) + * + * --> only exported for testing + */ +export function getUpdatedSourceMapSettings( + viteConfig: UserConfig, + sentryPluginOptions?: SentryTanstackStartReactPluginOptions, +): boolean | 'inline' | 'hidden' { + viteConfig.build = viteConfig.build || {}; + + const viteSourceMap = viteConfig.build.sourcemap; + let updatedSourceMapSetting = viteSourceMap; + + const settingKey = 'vite.build.sourcemap'; + const debug = sentryPluginOptions?.debug; + + if (viteSourceMap === false) { + updatedSourceMapSetting = viteSourceMap; + + if (debug) { + // eslint-disable-next-line no-console + console.warn( + `[Sentry] Source map generation is currently disabled in your TanStack Start configuration (\`${settingKey}: false\`). Sentry won't override this setting. Without source maps, code snippets on the Sentry Issues page will remain minified.`, + ); + } else { + // eslint-disable-next-line no-console + console.warn('[Sentry] Source map generation is disabled in your TanStack Start configuration.'); + } + } else if (viteSourceMap && ['hidden', 'inline', true].includes(viteSourceMap)) { + updatedSourceMapSetting = viteSourceMap; + + if (debug) { + // eslint-disable-next-line no-console + console.log( + `[Sentry] We discovered \`${settingKey}\` is set to \`${viteSourceMap.toString()}\`. Sentry will keep this source map setting.`, + ); + } + } else { + updatedSourceMapSetting = 'hidden'; + + if (debug) { + // eslint-disable-next-line no-console + console.log( + `[Sentry] Enabled source map generation in the build options with \`${settingKey}: 'hidden'\`. The source maps will be deleted after they were uploaded to Sentry.`, + ); + } + } + + return updatedSourceMapSetting; +} From 8c6f0fa6c6e0cd6d9147786f1e91d234397265f0 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Wed, 7 Jan 2026 15:35:37 +0100 Subject: [PATCH 05/31] ? --- packages/tanstackstart-react/src/vite/sourceMaps.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tanstackstart-react/src/vite/sourceMaps.ts b/packages/tanstackstart-react/src/vite/sourceMaps.ts index 1d012df64e33..1283bb4f6a14 100644 --- a/packages/tanstackstart-react/src/vite/sourceMaps.ts +++ b/packages/tanstackstart-react/src/vite/sourceMaps.ts @@ -94,7 +94,7 @@ export function getUpdatedSourceMapSettings( ): boolean | 'inline' | 'hidden' { viteConfig.build = viteConfig.build || {}; - const viteSourceMap = viteConfig.build.sourcemap; + const viteSourceMap = viteConfig.build?.sourcemap; let updatedSourceMapSetting = viteSourceMap; const settingKey = 'vite.build.sourcemap'; From 207a96adcad2a89e747d8193d2fc4b8c61196e8e Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Wed, 7 Jan 2026 15:53:03 +0100 Subject: [PATCH 06/31] add unit tests --- .../test/vite/addSentryPlugins.test.ts | 87 +++++++ .../test/vite/sourceMaps.test.ts | 227 ++++++++++++++++++ 2 files changed, 314 insertions(+) create mode 100644 packages/tanstackstart-react/test/vite/addSentryPlugins.test.ts create mode 100644 packages/tanstackstart-react/test/vite/sourceMaps.test.ts diff --git a/packages/tanstackstart-react/test/vite/addSentryPlugins.test.ts b/packages/tanstackstart-react/test/vite/addSentryPlugins.test.ts new file mode 100644 index 000000000000..fbf9b441984f --- /dev/null +++ b/packages/tanstackstart-react/test/vite/addSentryPlugins.test.ts @@ -0,0 +1,87 @@ +import type { Plugin } from 'vite'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { addSentryPlugins } from '../../src/vite/addSentryPlugins'; + +const mockSourceMapsPlugin: Plugin = { + name: 'sentry-vite-debug-id-upload-plugin', + writeBundle: vi.fn(), +}; + +const mockEnableSourceMapsPlugin: Plugin = { + name: 'sentry-tanstackstart-react-source-maps', + apply: 'build', + enforce: 'post', + config: vi.fn(), +}; + +vi.mock('../../src/vite/sourceMaps', () => ({ + makeAddSentryVitePlugin: vi.fn(() => [mockSourceMapsPlugin]), + makeEnableSourceMapsVitePlugin: vi.fn(() => [mockEnableSourceMapsPlugin]), +})); + +describe('addSentryPlugins()', () => { + beforeEach(() => { + vi.clearAllMocks(); + process.env.NODE_ENV = 'production'; + }); + + afterEach(() => { + process.env.NODE_ENV = 'production'; + }); + + it('prepends Sentry plugins to the original plugins array', () => { + const userPlugin: Plugin = { name: 'user-plugin' }; + const result = addSentryPlugins([userPlugin], {}, {}); + + expect(result).toHaveLength(3); + expect(result[0]).toBe(mockSourceMapsPlugin); + expect(result[1]).toBe(mockEnableSourceMapsPlugin); + expect(result[2]).toBe(userPlugin); + }); + + it('does not add plugins in development mode', () => { + process.env.NODE_ENV = 'development'; + + const userPlugin: Plugin = { name: 'user-plugin' }; + const result = addSentryPlugins([userPlugin], {}, {}); + + expect(result).toHaveLength(1); + expect(result[0]).toBe(userPlugin); + }); + + it('does not add plugins when sourceMapsUploadOptions.enabled is false', () => { + const userPlugin: Plugin = { name: 'user-plugin' }; + const result = addSentryPlugins([userPlugin], { sourceMapsUploadOptions: { enabled: false } }, {}); + + expect(result).toHaveLength(1); + expect(result[0]).toBe(userPlugin); + }); + + it('adds plugins when sourceMapsUploadOptions.enabled is true', () => { + const userPlugin: Plugin = { name: 'user-plugin' }; + const result = addSentryPlugins([userPlugin], { sourceMapsUploadOptions: { enabled: true } }, {}); + + expect(result).toHaveLength(3); + expect(result[0]).toBe(mockSourceMapsPlugin); + expect(result[1]).toBe(mockEnableSourceMapsPlugin); + expect(result[2]).toBe(userPlugin); + }); + + it('adds plugins by default when sourceMapsUploadOptions is not specified', () => { + const userPlugin: Plugin = { name: 'user-plugin' }; + const result = addSentryPlugins([userPlugin], {}, {}); + + expect(result).toHaveLength(3); + expect(result[0]).toBe(mockSourceMapsPlugin); + expect(result[1]).toBe(mockEnableSourceMapsPlugin); + expect(result[2]).toBe(userPlugin); + }); + + it('returns only Sentry plugins when no user plugins are provided', () => { + const result = addSentryPlugins([], {}, {}); + + expect(result).toHaveLength(2); + expect(result[0]).toBe(mockSourceMapsPlugin); + expect(result[1]).toBe(mockEnableSourceMapsPlugin); + }); +}); diff --git a/packages/tanstackstart-react/test/vite/sourceMaps.test.ts b/packages/tanstackstart-react/test/vite/sourceMaps.test.ts new file mode 100644 index 000000000000..2bba5cda0aea --- /dev/null +++ b/packages/tanstackstart-react/test/vite/sourceMaps.test.ts @@ -0,0 +1,227 @@ +import type { SentryVitePluginOptions } from '@sentry/vite-plugin'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { + getUpdatedSourceMapSettings, + makeAddSentryVitePlugin, + makeEnableSourceMapsVitePlugin, +} from '../../src/vite/sourceMaps'; + +const mockedSentryVitePlugin = { + name: 'sentry-vite-debug-id-upload-plugin', + writeBundle: vi.fn(), +}; + +const sentryVitePluginSpy = vi.fn((_options: SentryVitePluginOptions) => [mockedSentryVitePlugin]); + +vi.mock('@sentry/vite-plugin', () => ({ + sentryVitePlugin: (options: SentryVitePluginOptions) => sentryVitePluginSpy(options), +})); + +beforeEach(() => { + vi.clearAllMocks(); +}); + +describe('makeEnableSourceMapsVitePlugin()', () => { + it('returns a plugin to enable source maps', () => { + const sourceMapsConfigPlugins = makeEnableSourceMapsVitePlugin({}); + const enableSourceMapPlugin = sourceMapsConfigPlugins[0]; + + expect(enableSourceMapPlugin?.name).toEqual('sentry-tanstackstart-react-source-maps'); + expect(enableSourceMapPlugin?.apply).toEqual('build'); + expect(enableSourceMapPlugin?.enforce).toEqual('post'); + expect(enableSourceMapPlugin?.config).toEqual(expect.any(Function)); + + expect(sourceMapsConfigPlugins).toHaveLength(1); + }); +}); + +describe('makeAddSentryVitePlugin()', () => { + it('passes user-specified vite plugin options to vite plugin', () => { + makeAddSentryVitePlugin( + { + org: 'my-org', + authToken: 'my-token', + sourceMapsUploadOptions: { + filesToDeleteAfterUpload: ['baz/*.js'], + }, + bundleSizeOptimizations: { + excludeTracing: true, + }, + }, + {}, + ); + + expect(sentryVitePluginSpy).toHaveBeenCalledWith( + expect.objectContaining({ + org: 'my-org', + authToken: 'my-token', + sourcemaps: { + filesToDeleteAfterUpload: ['baz/*.js'], + }, + bundleSizeOptimizations: { + excludeTracing: true, + }, + }), + ); + }); + + it('should update `filesToDeleteAfterUpload` if source map generation was previously not defined', () => { + makeAddSentryVitePlugin( + { + org: 'my-org', + authToken: 'my-token', + bundleSizeOptimizations: { + excludeTracing: true, + }, + }, + {}, + ); + + expect(sentryVitePluginSpy).toHaveBeenCalledWith( + expect.objectContaining({ + sourcemaps: expect.objectContaining({ + filesToDeleteAfterUpload: ['.*/**/*.map'], + }), + }), + ); + }); + + it('should not update `filesToDeleteAfterUpload` if source map generation was previously enabled', () => { + makeAddSentryVitePlugin( + { + org: 'my-org', + authToken: 'my-token', + bundleSizeOptimizations: { + excludeTracing: true, + }, + }, + { build: { sourcemap: true } }, + ); + + expect(sentryVitePluginSpy).toHaveBeenCalledWith( + expect.objectContaining({ + sourcemaps: expect.objectContaining({ + filesToDeleteAfterUpload: undefined, + }), + }), + ); + }); + + it('should not update `filesToDeleteAfterUpload` if source map generation was previously disabled', () => { + makeAddSentryVitePlugin( + { + org: 'my-org', + authToken: 'my-token', + bundleSizeOptimizations: { + excludeTracing: true, + }, + }, + { build: { sourcemap: false } }, + ); + + expect(sentryVitePluginSpy).toHaveBeenCalledWith( + expect.objectContaining({ + sourcemaps: expect.objectContaining({ + filesToDeleteAfterUpload: undefined, + }), + }), + ); + }); + + it('sets the correct metaFramework in telemetry options', () => { + makeAddSentryVitePlugin( + { + org: 'my-org', + authToken: 'my-token', + }, + {}, + ); + + expect(sentryVitePluginSpy).toHaveBeenCalledWith( + expect.objectContaining({ + _metaOptions: { + telemetry: { + metaFramework: 'tanstackstart-react', + }, + }, + }), + ); + }); +}); + +describe('getUpdatedSourceMapSettings', () => { + beforeEach(() => { + vi.clearAllMocks(); + vi.spyOn(console, 'warn').mockImplementation(() => {}); + vi.spyOn(console, 'log').mockImplementation(() => {}); + }); + + describe('when sourcemap is false', () => { + it('should keep sourcemap as false and show short warning when debug is disabled', () => { + const result = getUpdatedSourceMapSettings({ build: { sourcemap: false } }); + + expect(result).toBe(false); + // eslint-disable-next-line no-console + expect(console.warn).toHaveBeenCalledWith( + '[Sentry] Source map generation is disabled in your TanStack Start configuration.', + ); + }); + + it('should keep sourcemap as false and show long warning when debug is enabled', () => { + const result = getUpdatedSourceMapSettings({ build: { sourcemap: false } }, { debug: true }); + + expect(result).toBe(false); + // eslint-disable-next-line no-console + expect(console.warn).toHaveBeenCalledWith( + expect.stringContaining( + '[Sentry] Source map generation is currently disabled in your TanStack Start configuration', + ), + ); + }); + }); + + describe('when sourcemap is explicitly set to valid values', () => { + it.each([ + ['hidden', 'hidden'], + ['inline', 'inline'], + [true, true], + ] as ('inline' | 'hidden' | boolean)[][])('should keep sourcemap as %s when set to %s', (input, expected) => { + const result = getUpdatedSourceMapSettings({ build: { sourcemap: input } }, { debug: true }); + + expect(result).toBe(expected); + // eslint-disable-next-line no-console + expect(console.log).toHaveBeenCalledWith( + expect.stringContaining(`[Sentry] We discovered \`vite.build.sourcemap\` is set to \`${input.toString()}\``), + ); + }); + }); + + describe('when sourcemap is undefined or invalid', () => { + it.each([[undefined], ['invalid'], ['something'], [null]])( + 'should set sourcemap to hidden when value is %s', + input => { + const result = getUpdatedSourceMapSettings({ build: { sourcemap: input as any } }, { debug: true }); + + expect(result).toBe('hidden'); + // eslint-disable-next-line no-console + expect(console.log).toHaveBeenCalledWith( + expect.stringContaining( + "[Sentry] Enabled source map generation in the build options with `vite.build.sourcemap: 'hidden'`", + ), + ); + }, + ); + + it('should set sourcemap to hidden when build config is empty', () => { + const result = getUpdatedSourceMapSettings({}, { debug: true }); + + expect(result).toBe('hidden'); + // eslint-disable-next-line no-console + expect(console.log).toHaveBeenCalledWith( + expect.stringContaining( + "[Sentry] Enabled source map generation in the build options with `vite.build.sourcemap: 'hidden'`", + ), + ); + }); + }); +}); From 53e9099a2947aece39dc368e1f39018fb63e825c Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Thu, 8 Jan 2026 09:04:43 +0100 Subject: [PATCH 07/31] add vite wrapper to e2e tests --- .../tanstackstart-react/vite.config.ts | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react/vite.config.ts b/dev-packages/e2e-tests/test-applications/tanstackstart-react/vite.config.ts index 4df9fbb14208..af8ef89927ed 100644 --- a/dev-packages/e2e-tests/test-applications/tanstackstart-react/vite.config.ts +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react/vite.config.ts @@ -3,16 +3,27 @@ import tsConfigPaths from 'vite-tsconfig-paths'; import { tanstackStart } from '@tanstack/react-start/plugin/vite'; import viteReact from '@vitejs/plugin-react-swc'; import { nitro } from 'nitro/vite'; +import { wrapConfigWithSentry } from '@sentry/tanstackstart-react'; -export default defineConfig({ - server: { - port: 3000, - }, - plugins: [ - tsConfigPaths(), - tanstackStart(), - nitro(), - // react's vite plugin must come after start's vite plugin - viteReact(), - ], -}); +export default defineConfig( + wrapConfigWithSentry( + { + server: { + port: 3000, + }, + plugins: [ + tsConfigPaths(), + tanstackStart(), + nitro(), + // react's vite plugin must come after start's vite plugin + viteReact(), + ], + }, + { + org: process.env.E2E_TEST_SENTRY_ORG_SLUG, + project: process.env.E2E_TEST_SENTRY_PROJECT, + authToken: process.env.E2E_TEST_AUTH_TOKEN, + debug: true, + }, + ), +); From b9c51e9404d9d7ea61f32a48a5cb10200ec7103d Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Thu, 8 Jan 2026 09:12:04 +0100 Subject: [PATCH 08/31] simplify unit tests --- .../test/vite/sourceMaps.test.ts | 79 ++++--------------- 1 file changed, 17 insertions(+), 62 deletions(-) diff --git a/packages/tanstackstart-react/test/vite/sourceMaps.test.ts b/packages/tanstackstart-react/test/vite/sourceMaps.test.ts index 2bba5cda0aea..a7871668129a 100644 --- a/packages/tanstackstart-react/test/vite/sourceMaps.test.ts +++ b/packages/tanstackstart-react/test/vite/sourceMaps.test.ts @@ -156,72 +156,27 @@ describe('getUpdatedSourceMapSettings', () => { vi.spyOn(console, 'log').mockImplementation(() => {}); }); - describe('when sourcemap is false', () => { - it('should keep sourcemap as false and show short warning when debug is disabled', () => { - const result = getUpdatedSourceMapSettings({ build: { sourcemap: false } }); - - expect(result).toBe(false); - // eslint-disable-next-line no-console - expect(console.warn).toHaveBeenCalledWith( - '[Sentry] Source map generation is disabled in your TanStack Start configuration.', - ); - }); - - it('should keep sourcemap as false and show long warning when debug is enabled', () => { - const result = getUpdatedSourceMapSettings({ build: { sourcemap: false } }, { debug: true }); - - expect(result).toBe(false); - // eslint-disable-next-line no-console - expect(console.warn).toHaveBeenCalledWith( - expect.stringContaining( - '[Sentry] Source map generation is currently disabled in your TanStack Start configuration', - ), - ); - }); + it('should keep sourcemap as false and emit warning when explicitly disabled', () => { + const result = getUpdatedSourceMapSettings({ build: { sourcemap: false } }); + + expect(result).toBe(false); + // eslint-disable-next-line no-console + expect(console.warn).toHaveBeenCalled(); }); - describe('when sourcemap is explicitly set to valid values', () => { - it.each([ - ['hidden', 'hidden'], - ['inline', 'inline'], - [true, true], - ] as ('inline' | 'hidden' | boolean)[][])('should keep sourcemap as %s when set to %s', (input, expected) => { - const result = getUpdatedSourceMapSettings({ build: { sourcemap: input } }, { debug: true }); - - expect(result).toBe(expected); - // eslint-disable-next-line no-console - expect(console.log).toHaveBeenCalledWith( - expect.stringContaining(`[Sentry] We discovered \`vite.build.sourcemap\` is set to \`${input.toString()}\``), - ); - }); + it.each([ + ['hidden', 'hidden'], + ['inline', 'inline'], + [true, true], + ] as const)('should keep sourcemap as %s when explicitly set', (input, expected) => { + const result = getUpdatedSourceMapSettings({ build: { sourcemap: input } }); + + expect(result).toBe(expected); }); - describe('when sourcemap is undefined or invalid', () => { - it.each([[undefined], ['invalid'], ['something'], [null]])( - 'should set sourcemap to hidden when value is %s', - input => { - const result = getUpdatedSourceMapSettings({ build: { sourcemap: input as any } }, { debug: true }); - - expect(result).toBe('hidden'); - // eslint-disable-next-line no-console - expect(console.log).toHaveBeenCalledWith( - expect.stringContaining( - "[Sentry] Enabled source map generation in the build options with `vite.build.sourcemap: 'hidden'`", - ), - ); - }, - ); + it('should set sourcemap to hidden when not configured', () => { + const result = getUpdatedSourceMapSettings({}); - it('should set sourcemap to hidden when build config is empty', () => { - const result = getUpdatedSourceMapSettings({}, { debug: true }); - - expect(result).toBe('hidden'); - // eslint-disable-next-line no-console - expect(console.log).toHaveBeenCalledWith( - expect.stringContaining( - "[Sentry] Enabled source map generation in the build options with `vite.build.sourcemap: 'hidden'`", - ), - ); - }); + expect(result).toBe('hidden'); }); }); From 6eb1c8ba91996b65379212eefe70565fa2dcf10d Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Tue, 13 Jan 2026 17:32:27 +0100 Subject: [PATCH 09/31] use buildTimeOptionsBase instead of defining my own type --- .../tanstackstart-react/src/config/index.ts | 1 - .../tanstackstart-react/src/config/types.ts | 111 ------------------ .../src/config/wrapConfigWithSentry.ts | 4 +- .../src/vite/addSentryPlugins.ts | 11 +- .../src/vite/sourceMaps.ts | 21 ++-- .../test/vite/addSentryPlugins.test.ts | 18 ++- .../test/vite/sourceMaps.test.ts | 2 +- 7 files changed, 30 insertions(+), 138 deletions(-) delete mode 100644 packages/tanstackstart-react/src/config/types.ts diff --git a/packages/tanstackstart-react/src/config/index.ts b/packages/tanstackstart-react/src/config/index.ts index 01b192c3c744..41c03e21fda2 100644 --- a/packages/tanstackstart-react/src/config/index.ts +++ b/packages/tanstackstart-react/src/config/index.ts @@ -1,2 +1 @@ -export * from './types'; export { wrapConfigWithSentry } from './wrapConfigWithSentry'; diff --git a/packages/tanstackstart-react/src/config/types.ts b/packages/tanstackstart-react/src/config/types.ts deleted file mode 100644 index 196ca29721ae..000000000000 --- a/packages/tanstackstart-react/src/config/types.ts +++ /dev/null @@ -1,111 +0,0 @@ -type BundleSizeOptimizationOptions = { - /** - * If set to `true`, the plugin will attempt to tree-shake (remove) any debugging code within the Sentry SDK. - * Note that the success of this depends on tree shaking being enabled in your build tooling. - * - * Setting this option to `true` will disable features like the SDK's `debug` option. - */ - excludeDebugStatements?: boolean; - - /** - * If set to true, the plugin will try to tree-shake tracing statements out. - * Note that the success of this depends on tree shaking generally being enabled in your build. - * Attention: DO NOT enable this when you're using any performance monitoring-related SDK features (e.g. Sentry.startSpan()). - */ - excludeTracing?: boolean; - - /** - * If set to `true`, the plugin will attempt to tree-shake (remove) code related to the Sentry SDK's Session Replay Shadow DOM recording functionality. - * Note that the success of this depends on tree shaking being enabled in your build tooling. - * - * This option is safe to be used when you do not want to capture any Shadow DOM activity via Sentry Session Replay. - */ - excludeReplayShadowDom?: boolean; - - /** - * If set to `true`, the plugin will attempt to tree-shake (remove) code related to the Sentry SDK's Session Replay `iframe` recording functionality. - * Note that the success of this depends on tree shaking being enabled in your build tooling. - * - * You can safely do this when you do not want to capture any `iframe` activity via Sentry Session Replay. - */ - excludeReplayIframe?: boolean; - - /** - * If set to `true`, the plugin will attempt to tree-shake (remove) code related to the Sentry SDK's Session Replay's Compression Web Worker. - * Note that the success of this depends on tree shaking being enabled in your build tooling. - * - * **Notice:** You should only do use this option if you manually host a compression worker and configure it in your Sentry Session Replay integration config via the `workerUrl` option. - */ - excludeReplayWorker?: boolean; -}; - -type SourceMapsOptions = { - /** - * If this flag is `true`, and an auth token is detected, the Sentry SDK will - * automatically generate and upload source maps to Sentry during a production build. - * - * @default true - */ - enabled?: boolean; - - /** - * If this flag is `true`, the Sentry plugin will collect some telemetry data and send it to Sentry. - * It will not collect any sensitive or user-specific data. - * - * @default true - */ - telemetry?: boolean; - - /** - * A glob or an array of globs that specifies the build artifacts that should be deleted after the artifact - * upload to Sentry has been completed. - * - * @default [] - By default no files are deleted. - * - * The globbing patterns follow the implementation of the glob package. (https://www.npmjs.com/package/glob) - */ - filesToDeleteAfterUpload?: string | Array; -}; - -/** - * Build options for the Sentry plugin. These options are used during build-time by the Sentry SDK. - */ -export type SentryTanstackStartReactPluginOptions = { - /** - * The auth token to use when uploading source maps to Sentry. - * - * Instead of specifying this option, you can also set the `SENTRY_AUTH_TOKEN` environment variable. - * - * To create an auth token, follow this guide: - * @see https://docs.sentry.io/product/accounts/auth-tokens/#organization-auth-tokens - */ - authToken?: string; - - /** - * The organization slug of your Sentry organization. - * Instead of specifying this option, you can also set the `SENTRY_ORG` environment variable. - */ - org?: string; - - /** - * The project slug of your Sentry project. - * Instead of specifying this option, you can also set the `SENTRY_PROJECT` environment variable. - */ - project?: string; - - /** - * Options for the Sentry Vite plugin to customize the source maps upload process. - */ - sourceMapsUploadOptions?: SourceMapsOptions; - - /** - * Options for the Sentry Vite plugin to customize bundle size optimizations. - */ - bundleSizeOptimizations?: BundleSizeOptimizationOptions; - - /** - * Enable debug functionality of the SDK during build-time. - * Enabling this will give you, for example logs about source maps. - */ - debug?: boolean; -}; diff --git a/packages/tanstackstart-react/src/config/wrapConfigWithSentry.ts b/packages/tanstackstart-react/src/config/wrapConfigWithSentry.ts index 97f9be50afc6..1184e5d0f06f 100644 --- a/packages/tanstackstart-react/src/config/wrapConfigWithSentry.ts +++ b/packages/tanstackstart-react/src/config/wrapConfigWithSentry.ts @@ -1,6 +1,6 @@ +import type { BuildTimeOptionsBase } from '@sentry/core'; import type { UserConfig } from 'vite'; import { addSentryPlugins } from '../vite'; -import type { SentryTanstackStartReactPluginOptions } from './types'; /** * Wraps a Vite configuration object with Sentry build-time enhancements such as @@ -33,7 +33,7 @@ import type { SentryTanstackStartReactPluginOptions } from './types'; */ export function wrapConfigWithSentry( config: UserConfig = {}, - sentryPluginOptions: SentryTanstackStartReactPluginOptions = {}, + sentryPluginOptions: BuildTimeOptionsBase = {}, ): UserConfig { const userPlugins = Array.isArray(config.plugins) ? [...config.plugins] : []; const plugins = addSentryPlugins(userPlugins, sentryPluginOptions, config); diff --git a/packages/tanstackstart-react/src/vite/addSentryPlugins.ts b/packages/tanstackstart-react/src/vite/addSentryPlugins.ts index 56b8b8c8a2ad..0c425145c76a 100644 --- a/packages/tanstackstart-react/src/vite/addSentryPlugins.ts +++ b/packages/tanstackstart-react/src/vite/addSentryPlugins.ts @@ -1,5 +1,5 @@ +import type { BuildTimeOptionsBase } from '@sentry/core'; import type { PluginOption, UserConfig } from 'vite'; -import type { SentryTanstackStartReactPluginOptions } from '../config/types'; import { makeAddSentryVitePlugin, makeEnableSourceMapsVitePlugin } from './sourceMaps'; /** @@ -7,18 +7,17 @@ import { makeAddSentryVitePlugin, makeEnableSourceMapsVitePlugin } from './sourc */ export function addSentryPlugins( plugins: PluginOption[], - options: SentryTanstackStartReactPluginOptions, + options: BuildTimeOptionsBase, viteConfig: UserConfig, ): PluginOption[] { const sentryPlugins: PluginOption[] = []; // Only add source map plugins in production builds if (process.env.NODE_ENV !== 'development') { - // Check if source maps upload is enabled - // Default to enabled - const sourceMapsEnabled = options.sourceMapsUploadOptions?.enabled ?? true; + // Check if source maps upload is enabled, default is enabled + const sourceMapsDisabled = options.sourcemaps?.disable === true || options.sourcemaps?.disable === 'disable-upload'; - if (sourceMapsEnabled) { + if (!sourceMapsDisabled) { const sourceMapsPlugins = makeAddSentryVitePlugin(options, viteConfig); const enableSourceMapsPlugin = makeEnableSourceMapsVitePlugin(options); diff --git a/packages/tanstackstart-react/src/vite/sourceMaps.ts b/packages/tanstackstart-react/src/vite/sourceMaps.ts index 1283bb4f6a14..6d268549a3cd 100644 --- a/packages/tanstackstart-react/src/vite/sourceMaps.ts +++ b/packages/tanstackstart-react/src/vite/sourceMaps.ts @@ -1,20 +1,17 @@ +import type { BuildTimeOptionsBase } from '@sentry/core'; import { sentryVitePlugin } from '@sentry/vite-plugin'; import type { Plugin, UserConfig } from 'vite'; -import type { SentryTanstackStartReactPluginOptions } from '../config/types'; /** * A Sentry plugin for adding the @sentry/vite-plugin to automatically upload source maps to Sentry. */ -export function makeAddSentryVitePlugin( - options: SentryTanstackStartReactPluginOptions, - viteConfig: UserConfig, -): Plugin[] { - const { authToken, bundleSizeOptimizations, debug, org, project, sourceMapsUploadOptions } = options; +export function makeAddSentryVitePlugin(options: BuildTimeOptionsBase, viteConfig: UserConfig): Plugin[] { + const { authToken, bundleSizeOptimizations, debug, org, project, sourcemaps, telemetry } = options; let updatedFilesToDeleteAfterUpload: string[] | undefined = undefined; if ( - typeof sourceMapsUploadOptions?.filesToDeleteAfterUpload === 'undefined' && + typeof sourcemaps?.filesToDeleteAfterUpload === 'undefined' && // Only if source maps were previously not set, we update the "filesToDeleteAfterUpload" (as we override the setting with "hidden") typeof viteConfig.build?.sourcemap === 'undefined' ) { @@ -24,7 +21,7 @@ export function makeAddSentryVitePlugin( if (debug) { // eslint-disable-next-line no-console console.log( - `[Sentry] Automatically setting \`sourceMapsUploadOptions.filesToDeleteAfterUpload: ${JSON.stringify( + `[Sentry] Automatically setting \`sourcemaps.filesToDeleteAfterUpload: ${JSON.stringify( updatedFilesToDeleteAfterUpload, )}\` to delete generated source maps after they were uploaded to Sentry.`, ); @@ -39,9 +36,9 @@ export function makeAddSentryVitePlugin( org: org ?? process.env.SENTRY_ORG, project: project ?? process.env.SENTRY_PROJECT, sourcemaps: { - filesToDeleteAfterUpload: sourceMapsUploadOptions?.filesToDeleteAfterUpload ?? updatedFilesToDeleteAfterUpload, + filesToDeleteAfterUpload: sourcemaps?.filesToDeleteAfterUpload ?? updatedFilesToDeleteAfterUpload, }, - telemetry: sourceMapsUploadOptions?.telemetry ?? true, + telemetry: telemetry ?? true, _metaOptions: { telemetry: { metaFramework: 'tanstackstart-react', @@ -54,7 +51,7 @@ export function makeAddSentryVitePlugin( /** * A Sentry plugin for TanStack Start React to enable "hidden" source maps if they are unset. */ -export function makeEnableSourceMapsVitePlugin(options: SentryTanstackStartReactPluginOptions): Plugin[] { +export function makeEnableSourceMapsVitePlugin(options: BuildTimeOptionsBase): Plugin[] { return [ { name: 'sentry-tanstackstart-react-source-maps', @@ -90,7 +87,7 @@ export function makeEnableSourceMapsVitePlugin(options: SentryTanstackStartReact */ export function getUpdatedSourceMapSettings( viteConfig: UserConfig, - sentryPluginOptions?: SentryTanstackStartReactPluginOptions, + sentryPluginOptions?: BuildTimeOptionsBase, ): boolean | 'inline' | 'hidden' { viteConfig.build = viteConfig.build || {}; diff --git a/packages/tanstackstart-react/test/vite/addSentryPlugins.test.ts b/packages/tanstackstart-react/test/vite/addSentryPlugins.test.ts index fbf9b441984f..8e369d7ff1e0 100644 --- a/packages/tanstackstart-react/test/vite/addSentryPlugins.test.ts +++ b/packages/tanstackstart-react/test/vite/addSentryPlugins.test.ts @@ -49,17 +49,25 @@ describe('addSentryPlugins()', () => { expect(result[0]).toBe(userPlugin); }); - it('does not add plugins when sourceMapsUploadOptions.enabled is false', () => { + it('does not add plugins when sourcemaps.disable is true', () => { const userPlugin: Plugin = { name: 'user-plugin' }; - const result = addSentryPlugins([userPlugin], { sourceMapsUploadOptions: { enabled: false } }, {}); + const result = addSentryPlugins([userPlugin], { sourcemaps: { disable: true } }, {}); expect(result).toHaveLength(1); expect(result[0]).toBe(userPlugin); }); - it('adds plugins when sourceMapsUploadOptions.enabled is true', () => { + it('does not add plugins when sourcemaps.disable is "disable-upload"', () => { const userPlugin: Plugin = { name: 'user-plugin' }; - const result = addSentryPlugins([userPlugin], { sourceMapsUploadOptions: { enabled: true } }, {}); + const result = addSentryPlugins([userPlugin], { sourcemaps: { disable: 'disable-upload' } }, {}); + + expect(result).toHaveLength(1); + expect(result[0]).toBe(userPlugin); + }); + + it('adds plugins when sourcemaps.disable is false', () => { + const userPlugin: Plugin = { name: 'user-plugin' }; + const result = addSentryPlugins([userPlugin], { sourcemaps: { disable: false } }, {}); expect(result).toHaveLength(3); expect(result[0]).toBe(mockSourceMapsPlugin); @@ -67,7 +75,7 @@ describe('addSentryPlugins()', () => { expect(result[2]).toBe(userPlugin); }); - it('adds plugins by default when sourceMapsUploadOptions is not specified', () => { + it('adds plugins by default when sourcemaps is not specified', () => { const userPlugin: Plugin = { name: 'user-plugin' }; const result = addSentryPlugins([userPlugin], {}, {}); diff --git a/packages/tanstackstart-react/test/vite/sourceMaps.test.ts b/packages/tanstackstart-react/test/vite/sourceMaps.test.ts index a7871668129a..4dbdaeaedde0 100644 --- a/packages/tanstackstart-react/test/vite/sourceMaps.test.ts +++ b/packages/tanstackstart-react/test/vite/sourceMaps.test.ts @@ -41,7 +41,7 @@ describe('makeAddSentryVitePlugin()', () => { { org: 'my-org', authToken: 'my-token', - sourceMapsUploadOptions: { + sourcemaps: { filesToDeleteAfterUpload: ['baz/*.js'], }, bundleSizeOptimizations: { From 9aaabd9b84fb150d44b3f1fc937c490c9e84fb06 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Tue, 13 Jan 2026 17:47:24 +0100 Subject: [PATCH 10/31] . --- packages/tanstackstart-react/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tanstackstart-react/package.json b/packages/tanstackstart-react/package.json index 37a8549b8dc9..96a86b98f710 100644 --- a/packages/tanstackstart-react/package.json +++ b/packages/tanstackstart-react/package.json @@ -55,7 +55,7 @@ "@sentry-internal/browser-utils": "10.33.0", "@sentry/core": "10.33.0", "@sentry/node": "10.33.0", - "@sentry/react": "10.33.0" + "@sentry/react": "10.33.0", "@sentry/vite-plugin": "^4.6.1" }, "devDependencies": { From bfa238a3082c251c386fce1dab50237871ada0a2 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Wed, 14 Jan 2026 10:35:35 +0100 Subject: [PATCH 11/31] switch to sentry vite plugin --- .../tanstackstart-react/vite.config.ts | 34 ++--- .../tanstackstart-react/src/config/index.ts | 1 - .../src/config/wrapConfigWithSentry.ts | 45 ------ .../tanstackstart-react/src/index.server.ts | 4 +- .../tanstackstart-react/src/index.types.ts | 2 +- .../src/vite/addSentryPlugins.ts | 30 ---- .../tanstackstart-react/src/vite/index.ts | 2 +- .../src/vite/sentryTanstackStart.ts | 46 ++++++ .../src/vite/sourceMaps.ts | 76 +++++----- .../test/vite/addSentryPlugins.test.ts | 95 ------------ .../test/vite/sentryTanstackStart.test.ts | 97 ++++++++++++ .../test/vite/sourceMaps.test.ts | 143 +++++++++--------- 12 files changed, 276 insertions(+), 299 deletions(-) delete mode 100644 packages/tanstackstart-react/src/config/index.ts delete mode 100644 packages/tanstackstart-react/src/config/wrapConfigWithSentry.ts delete mode 100644 packages/tanstackstart-react/src/vite/addSentryPlugins.ts create mode 100644 packages/tanstackstart-react/src/vite/sentryTanstackStart.ts delete mode 100644 packages/tanstackstart-react/test/vite/addSentryPlugins.test.ts create mode 100644 packages/tanstackstart-react/test/vite/sentryTanstackStart.test.ts diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react/vite.config.ts b/dev-packages/e2e-tests/test-applications/tanstackstart-react/vite.config.ts index af8ef89927ed..0abe265332df 100644 --- a/dev-packages/e2e-tests/test-applications/tanstackstart-react/vite.config.ts +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react/vite.config.ts @@ -3,27 +3,23 @@ import tsConfigPaths from 'vite-tsconfig-paths'; import { tanstackStart } from '@tanstack/react-start/plugin/vite'; import viteReact from '@vitejs/plugin-react-swc'; import { nitro } from 'nitro/vite'; -import { wrapConfigWithSentry } from '@sentry/tanstackstart-react'; +import { sentryTanstackStart } from '@sentry/tanstackstart-react'; -export default defineConfig( - wrapConfigWithSentry( - { - server: { - port: 3000, - }, - plugins: [ - tsConfigPaths(), - tanstackStart(), - nitro(), - // react's vite plugin must come after start's vite plugin - viteReact(), - ], - }, - { +export default defineConfig({ + server: { + port: 3000, + }, + plugins: [ + sentryTanstackStart({ org: process.env.E2E_TEST_SENTRY_ORG_SLUG, project: process.env.E2E_TEST_SENTRY_PROJECT, authToken: process.env.E2E_TEST_AUTH_TOKEN, debug: true, - }, - ), -); + }), + tsConfigPaths(), + tanstackStart(), + nitro(), + // react's vite plugin must come after start's vite plugin + viteReact(), + ], +}); diff --git a/packages/tanstackstart-react/src/config/index.ts b/packages/tanstackstart-react/src/config/index.ts deleted file mode 100644 index 41c03e21fda2..000000000000 --- a/packages/tanstackstart-react/src/config/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { wrapConfigWithSentry } from './wrapConfigWithSentry'; diff --git a/packages/tanstackstart-react/src/config/wrapConfigWithSentry.ts b/packages/tanstackstart-react/src/config/wrapConfigWithSentry.ts deleted file mode 100644 index 1184e5d0f06f..000000000000 --- a/packages/tanstackstart-react/src/config/wrapConfigWithSentry.ts +++ /dev/null @@ -1,45 +0,0 @@ -import type { BuildTimeOptionsBase } from '@sentry/core'; -import type { UserConfig } from 'vite'; -import { addSentryPlugins } from '../vite'; - -/** - * Wraps a Vite configuration object with Sentry build-time enhancements such as - * automatic source maps upload. - * - * @example - * ```typescript - * // vite.config.ts - * import { defineConfig } from 'vite'; - * import { wrapConfigWithSentry } from '@sentry/tanstackstart-react'; - * - * export default defineConfig( - * wrapConfigWithSentry( - * { - * // Your Vite/TanStack Start config - * plugins: [...] - * }, - * { - * // Sentry build-time options - * org: 'your-org', - * project: 'your-project', - * }, - * ), - * ); - * ``` - * - * @param config - A Vite configuration object - * @param sentryPluginOptions - Options to configure the Sentry Vite plugin - * @returns The modified Vite config to be passed to `defineConfig` - */ -export function wrapConfigWithSentry( - config: UserConfig = {}, - sentryPluginOptions: BuildTimeOptionsBase = {}, -): UserConfig { - const userPlugins = Array.isArray(config.plugins) ? [...config.plugins] : []; - const plugins = addSentryPlugins(userPlugins, sentryPluginOptions, config); - - return { - ...config, - plugins, - }; -} diff --git a/packages/tanstackstart-react/src/index.server.ts b/packages/tanstackstart-react/src/index.server.ts index 6b8974481131..01e28a6ed282 100644 --- a/packages/tanstackstart-react/src/index.server.ts +++ b/packages/tanstackstart-react/src/index.server.ts @@ -1,5 +1,3 @@ -/* eslint-disable import/export */ - -export * from './config'; +export * from './vite'; export * from './server'; export * from './common'; diff --git a/packages/tanstackstart-react/src/index.types.ts b/packages/tanstackstart-react/src/index.types.ts index 1ad387ea6a6e..673a7472e5c2 100644 --- a/packages/tanstackstart-react/src/index.types.ts +++ b/packages/tanstackstart-react/src/index.types.ts @@ -8,7 +8,7 @@ import type { Client, Integration, Options, StackParser } from '@sentry/core'; import type * as clientSdk from './client'; import type * as serverSdk from './server'; -export * from './config'; +export * from './vite'; export * from './client'; export * from './server'; export * from './common'; diff --git a/packages/tanstackstart-react/src/vite/addSentryPlugins.ts b/packages/tanstackstart-react/src/vite/addSentryPlugins.ts deleted file mode 100644 index 0c425145c76a..000000000000 --- a/packages/tanstackstart-react/src/vite/addSentryPlugins.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { BuildTimeOptionsBase } from '@sentry/core'; -import type { PluginOption, UserConfig } from 'vite'; -import { makeAddSentryVitePlugin, makeEnableSourceMapsVitePlugin } from './sourceMaps'; - -/** - * Adds Sentry plugins to the given array of Vite plugins. - */ -export function addSentryPlugins( - plugins: PluginOption[], - options: BuildTimeOptionsBase, - viteConfig: UserConfig, -): PluginOption[] { - const sentryPlugins: PluginOption[] = []; - - // Only add source map plugins in production builds - if (process.env.NODE_ENV !== 'development') { - // Check if source maps upload is enabled, default is enabled - const sourceMapsDisabled = options.sourcemaps?.disable === true || options.sourcemaps?.disable === 'disable-upload'; - - if (!sourceMapsDisabled) { - const sourceMapsPlugins = makeAddSentryVitePlugin(options, viteConfig); - const enableSourceMapsPlugin = makeEnableSourceMapsVitePlugin(options); - - sentryPlugins.push(...sourceMapsPlugins, ...enableSourceMapsPlugin); - } - } - - // Prepend Sentry plugins so they run first - return [...sentryPlugins, ...plugins]; -} diff --git a/packages/tanstackstart-react/src/vite/index.ts b/packages/tanstackstart-react/src/vite/index.ts index e66ccf733bd2..4af3423136fb 100644 --- a/packages/tanstackstart-react/src/vite/index.ts +++ b/packages/tanstackstart-react/src/vite/index.ts @@ -1 +1 @@ -export { addSentryPlugins } from './addSentryPlugins'; +export { sentryTanstackStart } from './sentryTanstackStart'; diff --git a/packages/tanstackstart-react/src/vite/sentryTanstackStart.ts b/packages/tanstackstart-react/src/vite/sentryTanstackStart.ts new file mode 100644 index 000000000000..182ec47a3883 --- /dev/null +++ b/packages/tanstackstart-react/src/vite/sentryTanstackStart.ts @@ -0,0 +1,46 @@ +import type { BuildTimeOptionsBase } from '@sentry/core'; +import type { Plugin } from 'vite'; +import { makeAddSentryVitePlugin, makeEnableSourceMapsVitePlugin } from './sourceMaps'; + +/** + * Vite plugins for the Sentry TanStack Start SDK. + * + * @example + * ```typescript + * // vite.config.ts + * import { defineConfig } from 'vite'; + * import { sentryTanstackStart } from '@sentry/tanstackstart-react'; + * import { tanstackStart } from '@tanstack/react-start/plugin/vite'; + * + * export default defineConfig({ + * plugins: [ + * sentryTanstackStart({ + * org: 'your-org', + * project: 'your-project', + * }), + * tanstackStart(), + * ], + * }); + * ``` + * + * @param options - Options to configure the Sentry Vite plugins + * @returns An array of Vite plugins + */ +export function sentryTanstackStart(options: BuildTimeOptionsBase = {}): Plugin[] { + const plugins: Plugin[] = []; + + // Only add source map plugins in production builds + if (process.env.NODE_ENV !== 'development') { + const sourceMapsDisabled = options.sourcemaps?.disable === true || options.sourcemaps?.disable === 'disable-upload'; + + if (!sourceMapsDisabled) { + // Add source maps upload plugin + plugins.push(...makeAddSentryVitePlugin(options)); + + // Add plugin to enable source maps if not already configured + plugins.push(...makeEnableSourceMapsVitePlugin(options)); + } + } + + return plugins; +} diff --git a/packages/tanstackstart-react/src/vite/sourceMaps.ts b/packages/tanstackstart-react/src/vite/sourceMaps.ts index 6d268549a3cd..9a741bab6953 100644 --- a/packages/tanstackstart-react/src/vite/sourceMaps.ts +++ b/packages/tanstackstart-react/src/vite/sourceMaps.ts @@ -5,47 +5,51 @@ import type { Plugin, UserConfig } from 'vite'; /** * A Sentry plugin for adding the @sentry/vite-plugin to automatically upload source maps to Sentry. */ -export function makeAddSentryVitePlugin(options: BuildTimeOptionsBase, viteConfig: UserConfig): Plugin[] { +export function makeAddSentryVitePlugin(options: BuildTimeOptionsBase): Plugin[] { const { authToken, bundleSizeOptimizations, debug, org, project, sourcemaps, telemetry } = options; - let updatedFilesToDeleteAfterUpload: string[] | undefined = undefined; - - if ( - typeof sourcemaps?.filesToDeleteAfterUpload === 'undefined' && - // Only if source maps were previously not set, we update the "filesToDeleteAfterUpload" (as we override the setting with "hidden") - typeof viteConfig.build?.sourcemap === 'undefined' - ) { - // For .output, .vercel, .netlify etc. - updatedFilesToDeleteAfterUpload = ['.*/**/*.map']; + const configPlugin: Plugin = { + name: 'sentry-tanstackstart-source-maps-config', + apply: 'build', + enforce: 'pre', + config(config) { + // Log if we're auto-deleting source maps (when user didn't configure sourcemap or filesToDeleteAfterUpload) + if ( + typeof sourcemaps?.filesToDeleteAfterUpload === 'undefined' && + typeof config.build?.sourcemap === 'undefined' && + debug + ) { + // eslint-disable-next-line no-console + console.log( + '[Sentry] Automatically setting `sourcemaps.filesToDeleteAfterUpload: [".*/**/*.map"]` to delete generated source maps after they were uploaded to Sentry.', + ); + } + }, + }; - if (debug) { - // eslint-disable-next-line no-console - console.log( - `[Sentry] Automatically setting \`sourcemaps.filesToDeleteAfterUpload: ${JSON.stringify( - updatedFilesToDeleteAfterUpload, - )}\` to delete generated source maps after they were uploaded to Sentry.`, - ); - } - } + // Default to auto-deleting source maps from hidden directories after upload + // Users can override this by explicitly setting sourcemaps.filesToDeleteAfterUpload + const defaultFilesToDelete = ['.*/**/*.map']; + const filesToDeleteAfterUpload = sourcemaps?.filesToDeleteAfterUpload ?? defaultFilesToDelete; - return [ - ...sentryVitePlugin({ - authToken: authToken ?? process.env.SENTRY_AUTH_TOKEN, - bundleSizeOptimizations: bundleSizeOptimizations ?? undefined, - debug: debug ?? false, - org: org ?? process.env.SENTRY_ORG, - project: project ?? process.env.SENTRY_PROJECT, - sourcemaps: { - filesToDeleteAfterUpload: sourcemaps?.filesToDeleteAfterUpload ?? updatedFilesToDeleteAfterUpload, - }, - telemetry: telemetry ?? true, - _metaOptions: { - telemetry: { - metaFramework: 'tanstackstart-react', - }, + const sentryPlugins = sentryVitePlugin({ + authToken: authToken ?? process.env.SENTRY_AUTH_TOKEN, + bundleSizeOptimizations: bundleSizeOptimizations ?? undefined, + debug: debug ?? false, + org: org ?? process.env.SENTRY_ORG, + project: project ?? process.env.SENTRY_PROJECT, + sourcemaps: { + filesToDeleteAfterUpload, + }, + telemetry: telemetry ?? true, + _metaOptions: { + telemetry: { + metaFramework: 'tanstackstart-react', }, - }), - ]; + }, + }); + + return [configPlugin, ...sentryPlugins]; } /** diff --git a/packages/tanstackstart-react/test/vite/addSentryPlugins.test.ts b/packages/tanstackstart-react/test/vite/addSentryPlugins.test.ts deleted file mode 100644 index 8e369d7ff1e0..000000000000 --- a/packages/tanstackstart-react/test/vite/addSentryPlugins.test.ts +++ /dev/null @@ -1,95 +0,0 @@ -import type { Plugin } from 'vite'; -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { addSentryPlugins } from '../../src/vite/addSentryPlugins'; - -const mockSourceMapsPlugin: Plugin = { - name: 'sentry-vite-debug-id-upload-plugin', - writeBundle: vi.fn(), -}; - -const mockEnableSourceMapsPlugin: Plugin = { - name: 'sentry-tanstackstart-react-source-maps', - apply: 'build', - enforce: 'post', - config: vi.fn(), -}; - -vi.mock('../../src/vite/sourceMaps', () => ({ - makeAddSentryVitePlugin: vi.fn(() => [mockSourceMapsPlugin]), - makeEnableSourceMapsVitePlugin: vi.fn(() => [mockEnableSourceMapsPlugin]), -})); - -describe('addSentryPlugins()', () => { - beforeEach(() => { - vi.clearAllMocks(); - process.env.NODE_ENV = 'production'; - }); - - afterEach(() => { - process.env.NODE_ENV = 'production'; - }); - - it('prepends Sentry plugins to the original plugins array', () => { - const userPlugin: Plugin = { name: 'user-plugin' }; - const result = addSentryPlugins([userPlugin], {}, {}); - - expect(result).toHaveLength(3); - expect(result[0]).toBe(mockSourceMapsPlugin); - expect(result[1]).toBe(mockEnableSourceMapsPlugin); - expect(result[2]).toBe(userPlugin); - }); - - it('does not add plugins in development mode', () => { - process.env.NODE_ENV = 'development'; - - const userPlugin: Plugin = { name: 'user-plugin' }; - const result = addSentryPlugins([userPlugin], {}, {}); - - expect(result).toHaveLength(1); - expect(result[0]).toBe(userPlugin); - }); - - it('does not add plugins when sourcemaps.disable is true', () => { - const userPlugin: Plugin = { name: 'user-plugin' }; - const result = addSentryPlugins([userPlugin], { sourcemaps: { disable: true } }, {}); - - expect(result).toHaveLength(1); - expect(result[0]).toBe(userPlugin); - }); - - it('does not add plugins when sourcemaps.disable is "disable-upload"', () => { - const userPlugin: Plugin = { name: 'user-plugin' }; - const result = addSentryPlugins([userPlugin], { sourcemaps: { disable: 'disable-upload' } }, {}); - - expect(result).toHaveLength(1); - expect(result[0]).toBe(userPlugin); - }); - - it('adds plugins when sourcemaps.disable is false', () => { - const userPlugin: Plugin = { name: 'user-plugin' }; - const result = addSentryPlugins([userPlugin], { sourcemaps: { disable: false } }, {}); - - expect(result).toHaveLength(3); - expect(result[0]).toBe(mockSourceMapsPlugin); - expect(result[1]).toBe(mockEnableSourceMapsPlugin); - expect(result[2]).toBe(userPlugin); - }); - - it('adds plugins by default when sourcemaps is not specified', () => { - const userPlugin: Plugin = { name: 'user-plugin' }; - const result = addSentryPlugins([userPlugin], {}, {}); - - expect(result).toHaveLength(3); - expect(result[0]).toBe(mockSourceMapsPlugin); - expect(result[1]).toBe(mockEnableSourceMapsPlugin); - expect(result[2]).toBe(userPlugin); - }); - - it('returns only Sentry plugins when no user plugins are provided', () => { - const result = addSentryPlugins([], {}, {}); - - expect(result).toHaveLength(2); - expect(result[0]).toBe(mockSourceMapsPlugin); - expect(result[1]).toBe(mockEnableSourceMapsPlugin); - }); -}); diff --git a/packages/tanstackstart-react/test/vite/sentryTanstackStart.test.ts b/packages/tanstackstart-react/test/vite/sentryTanstackStart.test.ts new file mode 100644 index 000000000000..d623ee2644a3 --- /dev/null +++ b/packages/tanstackstart-react/test/vite/sentryTanstackStart.test.ts @@ -0,0 +1,97 @@ +import type { Plugin } from 'vite'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { sentryTanstackStart } from '../../src/vite/sentryTanstackStart'; + +const mockSourceMapsConfigPlugin: Plugin = { + name: 'sentry-tanstackstart-source-maps-config', + apply: 'build', + enforce: 'pre', + config: vi.fn(), +}; + +const mockSentryVitePlugin: Plugin = { + name: 'sentry-vite-debug-id-upload-plugin', + writeBundle: vi.fn(), +}; + +const mockEnableSourceMapsPlugin: Plugin = { + name: 'sentry-tanstackstart-react-source-maps', + apply: 'build', + enforce: 'post', + config: vi.fn(), +}; + +vi.mock('../../src/vite/sourceMaps', () => ({ + makeAddSentryVitePlugin: vi.fn(() => [mockSourceMapsConfigPlugin, mockSentryVitePlugin]), + makeEnableSourceMapsVitePlugin: vi.fn(() => [mockEnableSourceMapsPlugin]), +})); + +describe('sentryTanstackStart()', () => { + beforeEach(() => { + vi.clearAllMocks(); + process.env.NODE_ENV = 'production'; + }); + + afterEach(() => { + process.env.NODE_ENV = 'production'; + }); + + it('returns plugins in production mode', () => { + const plugins = sentryTanstackStart({ org: 'test-org' }); + + // Should have plugins from makeAddSentryVitePlugin + makeEnableSourceMapsVitePlugin + expect(plugins.length).toBeGreaterThan(0); + }); + + it('returns empty array in development mode', () => { + process.env.NODE_ENV = 'development'; + + const plugins = sentryTanstackStart({ org: 'test-org' }); + + expect(plugins).toHaveLength(0); + }); + + it('returns empty array when sourcemaps.disable is true', () => { + const plugins = sentryTanstackStart({ + sourcemaps: { disable: true }, + }); + + expect(plugins).toHaveLength(0); + }); + + it('returns empty array when sourcemaps.disable is "disable-upload"', () => { + const plugins = sentryTanstackStart({ + sourcemaps: { disable: 'disable-upload' }, + }); + + expect(plugins).toHaveLength(0); + }); + + it('returns plugins when sourcemaps.disable is false', () => { + const plugins = sentryTanstackStart({ + sourcemaps: { disable: false }, + }); + + expect(plugins.length).toBeGreaterThan(0); + }); + + it('returns plugins by default when sourcemaps is not specified', () => { + const plugins = sentryTanstackStart({}); + + expect(plugins.length).toBeGreaterThan(0); + }); + + it('includes source maps config plugin from makeAddSentryVitePlugin', () => { + const plugins = sentryTanstackStart({}); + + const configPlugin = plugins.find(p => p.name === 'sentry-tanstackstart-source-maps-config'); + expect(configPlugin).toBeDefined(); + }); + + it('includes enable source maps plugin from makeEnableSourceMapsVitePlugin', () => { + const plugins = sentryTanstackStart({}); + + const enablePlugin = plugins.find(p => p.name === 'sentry-tanstackstart-react-source-maps'); + expect(enablePlugin).toBeDefined(); + }); +}); diff --git a/packages/tanstackstart-react/test/vite/sourceMaps.test.ts b/packages/tanstackstart-react/test/vite/sourceMaps.test.ts index 4dbdaeaedde0..14bde7ca599c 100644 --- a/packages/tanstackstart-react/test/vite/sourceMaps.test.ts +++ b/packages/tanstackstart-react/test/vite/sourceMaps.test.ts @@ -1,4 +1,5 @@ import type { SentryVitePluginOptions } from '@sentry/vite-plugin'; +import type { UserConfig } from 'vite'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { getUpdatedSourceMapSettings, @@ -37,19 +38,16 @@ describe('makeEnableSourceMapsVitePlugin()', () => { describe('makeAddSentryVitePlugin()', () => { it('passes user-specified vite plugin options to vite plugin', () => { - makeAddSentryVitePlugin( - { - org: 'my-org', - authToken: 'my-token', - sourcemaps: { - filesToDeleteAfterUpload: ['baz/*.js'], - }, - bundleSizeOptimizations: { - excludeTracing: true, - }, + makeAddSentryVitePlugin({ + org: 'my-org', + authToken: 'my-token', + sourcemaps: { + filesToDeleteAfterUpload: ['baz/*.js'], }, - {}, - ); + bundleSizeOptimizations: { + excludeTracing: true, + }, + }); expect(sentryVitePluginSpy).toHaveBeenCalledWith( expect.objectContaining({ @@ -65,77 +63,86 @@ describe('makeAddSentryVitePlugin()', () => { ); }); - it('should update `filesToDeleteAfterUpload` if source map generation was previously not defined', () => { - makeAddSentryVitePlugin( - { - org: 'my-org', - authToken: 'my-token', - bundleSizeOptimizations: { - excludeTracing: true, - }, - }, - {}, - ); + it('returns correct plugins including config plugin', () => { + const plugins = makeAddSentryVitePlugin({ + org: 'my-org', + authToken: 'my-token', + }); + + // Should have: config plugin + sentry vite plugin(s) + expect(plugins.length).toBeGreaterThanOrEqual(2); + + const configPlugin = plugins.find(p => p.name === 'sentry-tanstackstart-source-maps-config'); + expect(configPlugin).toBeDefined(); + expect(configPlugin?.apply).toBe('build'); + expect(configPlugin?.enforce).toBe('pre'); + expect(typeof configPlugin?.config).toBe('function'); + }); + + it('uses default filesToDeleteAfterUpload when not specified', () => { + makeAddSentryVitePlugin({ + org: 'my-org', + authToken: 'my-token', + }); expect(sentryVitePluginSpy).toHaveBeenCalledWith( expect.objectContaining({ - sourcemaps: expect.objectContaining({ + sourcemaps: { filesToDeleteAfterUpload: ['.*/**/*.map'], - }), + }, }), ); }); - it('should not update `filesToDeleteAfterUpload` if source map generation was previously enabled', () => { - makeAddSentryVitePlugin( - { - org: 'my-org', - authToken: 'my-token', - bundleSizeOptimizations: { - excludeTracing: true, - }, - }, - { build: { sourcemap: true } }, - ); + it('logs auto-delete message when user did not configure sourcemap', () => { + const plugins = makeAddSentryVitePlugin({ + org: 'my-org', + authToken: 'my-token', + debug: true, + }); - expect(sentryVitePluginSpy).toHaveBeenCalledWith( - expect.objectContaining({ - sourcemaps: expect.objectContaining({ - filesToDeleteAfterUpload: undefined, - }), - }), - ); + const configPlugin = plugins.find(p => p.name === 'sentry-tanstackstart-source-maps-config'); + expect(configPlugin).toBeDefined(); + + const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); + + // Simulate config hook with no sourcemap configured + if (configPlugin?.config) { + (configPlugin.config as (config: UserConfig) => void)({}); + } + + expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Automatically setting')); + + consoleSpy.mockRestore(); }); - it('should not update `filesToDeleteAfterUpload` if source map generation was previously disabled', () => { - makeAddSentryVitePlugin( - { - org: 'my-org', - authToken: 'my-token', - bundleSizeOptimizations: { - excludeTracing: true, - }, - }, - { build: { sourcemap: false } }, - ); + it('does not log auto-delete message when user configured sourcemap', () => { + const plugins = makeAddSentryVitePlugin({ + org: 'my-org', + authToken: 'my-token', + debug: true, + }); - expect(sentryVitePluginSpy).toHaveBeenCalledWith( - expect.objectContaining({ - sourcemaps: expect.objectContaining({ - filesToDeleteAfterUpload: undefined, - }), - }), - ); + const configPlugin = plugins.find(p => p.name === 'sentry-tanstackstart-source-maps-config'); + expect(configPlugin).toBeDefined(); + + const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); + + // Simulate config hook with sourcemap configured + if (configPlugin?.config) { + (configPlugin.config as (config: UserConfig) => void)({ build: { sourcemap: true } }); + } + + expect(consoleSpy).not.toHaveBeenCalledWith(expect.stringContaining('Automatically setting')); + + consoleSpy.mockRestore(); }); it('sets the correct metaFramework in telemetry options', () => { - makeAddSentryVitePlugin( - { - org: 'my-org', - authToken: 'my-token', - }, - {}, - ); + makeAddSentryVitePlugin({ + org: 'my-org', + authToken: 'my-token', + }); expect(sentryVitePluginSpy).toHaveBeenCalledWith( expect.objectContaining({ From 086b9f6d6822f3b8de448f406e4fe9b0164fdd0a Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Wed, 14 Jan 2026 10:46:10 +0100 Subject: [PATCH 12/31] add changelog entry and pass down all options --- CHANGELOG.md | 21 ++++++++++++++ .../src/vite/sourceMaps.ts | 22 +++++++++++++- .../test/vite/sourceMaps.test.ts | 29 +++++++++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9caa93dc01d1..3b6efddc68fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,27 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +- **feat(tanstackstart-react): Add `sentryTanstackStart` Vite plugin for source maps upload** + + You can now configure source maps upload for TanStack Start using the `sentryTanstackStart` Vite plugin: + + ```ts + // vite.config.ts + import { defineConfig } from 'vite'; + import { sentryTanstackStart } from '@sentry/tanstackstart-react'; + import { tanstackStart } from '@tanstack/react-start/plugin/vite'; + + export default defineConfig({ + plugins: [ + sentryTanstackStart({ + org: 'your-org', + project: 'your-project', + }), + tanstackStart(), + ], + }); + ``` + - **feat(tanstackstart-react): Add `wrapMiddlewaresWithSentry` for manual middleware instrumentation** You can now wrap your middlewares using `wrapMiddlewaresWithSentry`, allowing you to trace middleware execution in your TanStack Start application. diff --git a/packages/tanstackstart-react/src/vite/sourceMaps.ts b/packages/tanstackstart-react/src/vite/sourceMaps.ts index 9a741bab6953..f8d5f2225d4b 100644 --- a/packages/tanstackstart-react/src/vite/sourceMaps.ts +++ b/packages/tanstackstart-react/src/vite/sourceMaps.ts @@ -6,7 +6,20 @@ import type { Plugin, UserConfig } from 'vite'; * A Sentry plugin for adding the @sentry/vite-plugin to automatically upload source maps to Sentry. */ export function makeAddSentryVitePlugin(options: BuildTimeOptionsBase): Plugin[] { - const { authToken, bundleSizeOptimizations, debug, org, project, sourcemaps, telemetry } = options; + const { + authToken, + bundleSizeOptimizations, + debug, + errorHandler, + headers, + org, + project, + release, + sentryUrl, + silent, + sourcemaps, + telemetry, + } = options; const configPlugin: Plugin = { name: 'sentry-tanstackstart-source-maps-config', @@ -36,12 +49,19 @@ export function makeAddSentryVitePlugin(options: BuildTimeOptionsBase): Plugin[] authToken: authToken ?? process.env.SENTRY_AUTH_TOKEN, bundleSizeOptimizations: bundleSizeOptimizations ?? undefined, debug: debug ?? false, + errorHandler, + headers, org: org ?? process.env.SENTRY_ORG, project: project ?? process.env.SENTRY_PROJECT, + release, + silent, sourcemaps: { + assets: sourcemaps?.assets, + ignore: sourcemaps?.ignore, filesToDeleteAfterUpload, }, telemetry: telemetry ?? true, + url: sentryUrl, _metaOptions: { telemetry: { metaFramework: 'tanstackstart-react', diff --git a/packages/tanstackstart-react/test/vite/sourceMaps.test.ts b/packages/tanstackstart-react/test/vite/sourceMaps.test.ts index 14bde7ca599c..38b97b66f249 100644 --- a/packages/tanstackstart-react/test/vite/sourceMaps.test.ts +++ b/packages/tanstackstart-react/test/vite/sourceMaps.test.ts @@ -38,10 +38,24 @@ describe('makeEnableSourceMapsVitePlugin()', () => { describe('makeAddSentryVitePlugin()', () => { it('passes user-specified vite plugin options to vite plugin', () => { + const errorHandler = vi.fn(); makeAddSentryVitePlugin({ org: 'my-org', authToken: 'my-token', + sentryUrl: 'https://custom.sentry.io', + headers: { 'X-Custom-Header': 'value' }, + silent: true, + errorHandler, + release: { + name: 'my-release', + inject: true, + create: true, + finalize: true, + dist: 'dist-1', + }, sourcemaps: { + assets: ['dist/**/*.js'], + ignore: ['node_modules/**'], filesToDeleteAfterUpload: ['baz/*.js'], }, bundleSizeOptimizations: { @@ -53,7 +67,20 @@ describe('makeAddSentryVitePlugin()', () => { expect.objectContaining({ org: 'my-org', authToken: 'my-token', + url: 'https://custom.sentry.io', + headers: { 'X-Custom-Header': 'value' }, + silent: true, + errorHandler, + release: { + name: 'my-release', + inject: true, + create: true, + finalize: true, + dist: 'dist-1', + }, sourcemaps: { + assets: ['dist/**/*.js'], + ignore: ['node_modules/**'], filesToDeleteAfterUpload: ['baz/*.js'], }, bundleSizeOptimizations: { @@ -88,6 +115,8 @@ describe('makeAddSentryVitePlugin()', () => { expect(sentryVitePluginSpy).toHaveBeenCalledWith( expect.objectContaining({ sourcemaps: { + assets: undefined, + ignore: undefined, filesToDeleteAfterUpload: ['.*/**/*.map'], }, }), From 05e08f1e9d3accb832a3573481f10f80f01f2bb5 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Wed, 14 Jan 2026 10:59:27 +0100 Subject: [PATCH 13/31] clean --- packages/tanstackstart-react/src/config/index.ts | 1 + packages/tanstackstart-react/src/index.server.ts | 5 ++++- packages/tanstackstart-react/src/index.types.ts | 3 ++- 3 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 packages/tanstackstart-react/src/config/index.ts diff --git a/packages/tanstackstart-react/src/config/index.ts b/packages/tanstackstart-react/src/config/index.ts new file mode 100644 index 000000000000..cb0ff5c3b541 --- /dev/null +++ b/packages/tanstackstart-react/src/config/index.ts @@ -0,0 +1 @@ +export {}; diff --git a/packages/tanstackstart-react/src/index.server.ts b/packages/tanstackstart-react/src/index.server.ts index 01e28a6ed282..775f27086d6a 100644 --- a/packages/tanstackstart-react/src/index.server.ts +++ b/packages/tanstackstart-react/src/index.server.ts @@ -1,3 +1,6 @@ -export * from './vite'; +/* eslint-disable import/export */ + +export * from './config'; export * from './server'; export * from './common'; +export * from './vite'; diff --git a/packages/tanstackstart-react/src/index.types.ts b/packages/tanstackstart-react/src/index.types.ts index 673a7472e5c2..ca41d6ce05ee 100644 --- a/packages/tanstackstart-react/src/index.types.ts +++ b/packages/tanstackstart-react/src/index.types.ts @@ -8,10 +8,11 @@ import type { Client, Integration, Options, StackParser } from '@sentry/core'; import type * as clientSdk from './client'; import type * as serverSdk from './server'; -export * from './vite'; +export * from './config'; export * from './client'; export * from './server'; export * from './common'; +export * from './vite'; /** Initializes Sentry TanStack Start SDK */ export declare function init(options: Options | clientSdk.BrowserOptions | serverSdk.NodeOptions): Client | undefined; From bc0acddcbc419fef164b64263c60af9b13775387 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Wed, 14 Jan 2026 11:20:22 +0100 Subject: [PATCH 14/31] always add sentry vite plugin and pass down disable option --- .../src/vite/sentryTanstackStart.ts | 9 +++----- .../src/vite/sourceMaps.ts | 1 + .../test/vite/sentryTanstackStart.test.ts | 14 +++++++++---- .../test/vite/sourceMaps.test.ts | 21 +++++++++++++++++++ 4 files changed, 35 insertions(+), 10 deletions(-) diff --git a/packages/tanstackstart-react/src/vite/sentryTanstackStart.ts b/packages/tanstackstart-react/src/vite/sentryTanstackStart.ts index 182ec47a3883..8f1742436f9f 100644 --- a/packages/tanstackstart-react/src/vite/sentryTanstackStart.ts +++ b/packages/tanstackstart-react/src/vite/sentryTanstackStart.ts @@ -29,15 +29,12 @@ import { makeAddSentryVitePlugin, makeEnableSourceMapsVitePlugin } from './sourc export function sentryTanstackStart(options: BuildTimeOptionsBase = {}): Plugin[] { const plugins: Plugin[] = []; - // Only add source map plugins in production builds + // Only add plugins in production builds if (process.env.NODE_ENV !== 'development') { - const sourceMapsDisabled = options.sourcemaps?.disable === true || options.sourcemaps?.disable === 'disable-upload'; + plugins.push(...makeAddSentryVitePlugin(options)); + const sourceMapsDisabled = options.sourcemaps?.disable === true || options.sourcemaps?.disable === 'disable-upload'; if (!sourceMapsDisabled) { - // Add source maps upload plugin - plugins.push(...makeAddSentryVitePlugin(options)); - - // Add plugin to enable source maps if not already configured plugins.push(...makeEnableSourceMapsVitePlugin(options)); } } diff --git a/packages/tanstackstart-react/src/vite/sourceMaps.ts b/packages/tanstackstart-react/src/vite/sourceMaps.ts index f8d5f2225d4b..8d255f1950dd 100644 --- a/packages/tanstackstart-react/src/vite/sourceMaps.ts +++ b/packages/tanstackstart-react/src/vite/sourceMaps.ts @@ -57,6 +57,7 @@ export function makeAddSentryVitePlugin(options: BuildTimeOptionsBase): Plugin[] silent, sourcemaps: { assets: sourcemaps?.assets, + disable: sourcemaps?.disable, ignore: sourcemaps?.ignore, filesToDeleteAfterUpload, }, diff --git a/packages/tanstackstart-react/test/vite/sentryTanstackStart.test.ts b/packages/tanstackstart-react/test/vite/sentryTanstackStart.test.ts index d623ee2644a3..1408a5e3b244 100644 --- a/packages/tanstackstart-react/test/vite/sentryTanstackStart.test.ts +++ b/packages/tanstackstart-react/test/vite/sentryTanstackStart.test.ts @@ -51,20 +51,26 @@ describe('sentryTanstackStart()', () => { expect(plugins).toHaveLength(0); }); - it('returns empty array when sourcemaps.disable is true', () => { + it('returns Sentry Vite plugins but not enable source maps plugin when sourcemaps.disable is true', () => { const plugins = sentryTanstackStart({ sourcemaps: { disable: true }, }); - expect(plugins).toHaveLength(0); + expect(plugins.length).toBeGreaterThan(0); + expect(plugins.find(p => p.name === 'sentry-tanstackstart-source-maps-config')).toBeDefined(); + expect(plugins.find(p => p.name === 'sentry-vite-debug-id-upload-plugin')).toBeDefined(); + expect(plugins.find(p => p.name === 'sentry-tanstackstart-react-source-maps')).toBeUndefined(); }); - it('returns empty array when sourcemaps.disable is "disable-upload"', () => { + it('returns Sentry Vite plugins but not enable source maps plugin when sourcemaps.disable is "disable-upload"', () => { const plugins = sentryTanstackStart({ sourcemaps: { disable: 'disable-upload' }, }); - expect(plugins).toHaveLength(0); + expect(plugins.length).toBeGreaterThan(0); + expect(plugins.find(p => p.name === 'sentry-tanstackstart-source-maps-config')).toBeDefined(); + expect(plugins.find(p => p.name === 'sentry-vite-debug-id-upload-plugin')).toBeDefined(); + expect(plugins.find(p => p.name === 'sentry-tanstackstart-react-source-maps')).toBeUndefined(); }); it('returns plugins when sourcemaps.disable is false', () => { diff --git a/packages/tanstackstart-react/test/vite/sourceMaps.test.ts b/packages/tanstackstart-react/test/vite/sourceMaps.test.ts index 38b97b66f249..53c5703a56df 100644 --- a/packages/tanstackstart-react/test/vite/sourceMaps.test.ts +++ b/packages/tanstackstart-react/test/vite/sourceMaps.test.ts @@ -55,6 +55,7 @@ describe('makeAddSentryVitePlugin()', () => { }, sourcemaps: { assets: ['dist/**/*.js'], + disable: false, ignore: ['node_modules/**'], filesToDeleteAfterUpload: ['baz/*.js'], }, @@ -80,6 +81,7 @@ describe('makeAddSentryVitePlugin()', () => { }, sourcemaps: { assets: ['dist/**/*.js'], + disable: false, ignore: ['node_modules/**'], filesToDeleteAfterUpload: ['baz/*.js'], }, @@ -116,6 +118,7 @@ describe('makeAddSentryVitePlugin()', () => { expect.objectContaining({ sourcemaps: { assets: undefined, + disable: undefined, ignore: undefined, filesToDeleteAfterUpload: ['.*/**/*.map'], }, @@ -123,6 +126,24 @@ describe('makeAddSentryVitePlugin()', () => { ); }); + it('passes sourcemaps.disable option to vite plugin', () => { + makeAddSentryVitePlugin({ + org: 'my-org', + authToken: 'my-token', + sourcemaps: { + disable: true, + }, + }); + + expect(sentryVitePluginSpy).toHaveBeenCalledWith( + expect.objectContaining({ + sourcemaps: expect.objectContaining({ + disable: true, + }), + }), + ); + }); + it('logs auto-delete message when user did not configure sourcemap', () => { const plugins = makeAddSentryVitePlugin({ org: 'my-org', From 9bbd0b0d00f06ac66b636cc8287eb80e0b645a4c Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Wed, 14 Jan 2026 15:48:48 +0100 Subject: [PATCH 15/31] readability --- .../src/vite/sourceMaps.ts | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/packages/tanstackstart-react/src/vite/sourceMaps.ts b/packages/tanstackstart-react/src/vite/sourceMaps.ts index 8d255f1950dd..f31a5bc18679 100644 --- a/packages/tanstackstart-react/src/vite/sourceMaps.ts +++ b/packages/tanstackstart-react/src/vite/sourceMaps.ts @@ -26,7 +26,7 @@ export function makeAddSentryVitePlugin(options: BuildTimeOptionsBase): Plugin[] apply: 'build', enforce: 'pre', config(config) { - // Log if we're auto-deleting source maps (when user didn't configure sourcemap or filesToDeleteAfterUpload) + // Emit a warning if we're auto-deleting source maps if ( typeof sourcemaps?.filesToDeleteAfterUpload === 'undefined' && typeof config.build?.sourcemap === 'undefined' && @@ -116,15 +116,12 @@ export function getUpdatedSourceMapSettings( ): boolean | 'inline' | 'hidden' { viteConfig.build = viteConfig.build || {}; - const viteSourceMap = viteConfig.build?.sourcemap; - let updatedSourceMapSetting = viteSourceMap; - + const viteUserSourceMapSetting = viteConfig.build?.sourcemap; const settingKey = 'vite.build.sourcemap'; const debug = sentryPluginOptions?.debug; - if (viteSourceMap === false) { - updatedSourceMapSetting = viteSourceMap; - + // Respect user source map setting if it is explicitly set + if (viteUserSourceMapSetting === false) { if (debug) { // eslint-disable-next-line no-console console.warn( @@ -134,25 +131,26 @@ export function getUpdatedSourceMapSettings( // eslint-disable-next-line no-console console.warn('[Sentry] Source map generation is disabled in your TanStack Start configuration.'); } - } else if (viteSourceMap && ['hidden', 'inline', true].includes(viteSourceMap)) { - updatedSourceMapSetting = viteSourceMap; + return viteUserSourceMapSetting; + } else if (viteUserSourceMapSetting && ['hidden', 'inline', true].includes(viteUserSourceMapSetting)) { if (debug) { // eslint-disable-next-line no-console console.log( - `[Sentry] We discovered \`${settingKey}\` is set to \`${viteSourceMap.toString()}\`. Sentry will keep this source map setting.`, + `[Sentry] We discovered \`${settingKey}\` is set to \`${viteUserSourceMapSetting.toString()}\`. Sentry will keep this source map setting.`, ); } - } else { - updatedSourceMapSetting = 'hidden'; - if (debug) { - // eslint-disable-next-line no-console - console.log( - `[Sentry] Enabled source map generation in the build options with \`${settingKey}: 'hidden'\`. The source maps will be deleted after they were uploaded to Sentry.`, - ); - } + return viteUserSourceMapSetting; + } + + // If the user did not specify a source map setting, we enable 'hidden' by default + if (debug) { + // eslint-disable-next-line no-console + console.log( + `[Sentry] Enabled source map generation in the build options with \`${settingKey}: 'hidden'\`. The source maps will be deleted after they were uploaded to Sentry.`, + ); } - return updatedSourceMapSetting; + return 'hidden'; } From 283a545e1cc2751cf83d0a20111090e430bec144 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Wed, 14 Jan 2026 16:25:18 +0100 Subject: [PATCH 16/31] update tests --- .../test/vite/sentryTanstackStart.test.ts | 35 +++++++------------ .../test/vite/sourceMaps.test.ts | 21 +---------- 2 files changed, 14 insertions(+), 42 deletions(-) diff --git a/packages/tanstackstart-react/test/vite/sentryTanstackStart.test.ts b/packages/tanstackstart-react/test/vite/sentryTanstackStart.test.ts index 1408a5e3b244..53b3657c5475 100644 --- a/packages/tanstackstart-react/test/vite/sentryTanstackStart.test.ts +++ b/packages/tanstackstart-react/test/vite/sentryTanstackStart.test.ts @@ -39,11 +39,10 @@ describe('sentryTanstackStart()', () => { it('returns plugins in production mode', () => { const plugins = sentryTanstackStart({ org: 'test-org' }); - // Should have plugins from makeAddSentryVitePlugin + makeEnableSourceMapsVitePlugin expect(plugins.length).toBeGreaterThan(0); }); - it('returns empty array in development mode', () => { + it('returns no plugins in development mode', () => { process.env.NODE_ENV = 'development'; const plugins = sentryTanstackStart({ org: 'test-org' }); @@ -56,7 +55,7 @@ describe('sentryTanstackStart()', () => { sourcemaps: { disable: true }, }); - expect(plugins.length).toBeGreaterThan(0); + expect(plugins).toHaveLength(2); expect(plugins.find(p => p.name === 'sentry-tanstackstart-source-maps-config')).toBeDefined(); expect(plugins.find(p => p.name === 'sentry-vite-debug-id-upload-plugin')).toBeDefined(); expect(plugins.find(p => p.name === 'sentry-tanstackstart-react-source-maps')).toBeUndefined(); @@ -67,37 +66,29 @@ describe('sentryTanstackStart()', () => { sourcemaps: { disable: 'disable-upload' }, }); - expect(plugins.length).toBeGreaterThan(0); + expect(plugins).toHaveLength(2); expect(plugins.find(p => p.name === 'sentry-tanstackstart-source-maps-config')).toBeDefined(); expect(plugins.find(p => p.name === 'sentry-vite-debug-id-upload-plugin')).toBeDefined(); expect(plugins.find(p => p.name === 'sentry-tanstackstart-react-source-maps')).toBeUndefined(); }); - it('returns plugins when sourcemaps.disable is false', () => { + it('returns Sentry Vite plugins and enable source maps plugin when sourcemaps.disable is false', () => { const plugins = sentryTanstackStart({ sourcemaps: { disable: false }, }); - expect(plugins.length).toBeGreaterThan(0); - }); - - it('returns plugins by default when sourcemaps is not specified', () => { - const plugins = sentryTanstackStart({}); - - expect(plugins.length).toBeGreaterThan(0); - }); - - it('includes source maps config plugin from makeAddSentryVitePlugin', () => { - const plugins = sentryTanstackStart({}); - - const configPlugin = plugins.find(p => p.name === 'sentry-tanstackstart-source-maps-config'); - expect(configPlugin).toBeDefined(); + expect(plugins).toHaveLength(3); + expect(plugins.find(p => p.name === 'sentry-tanstackstart-source-maps-config')).toBeDefined(); + expect(plugins.find(p => p.name === 'sentry-vite-debug-id-upload-plugin')).toBeDefined(); + expect(plugins.find(p => p.name === 'sentry-tanstackstart-react-source-maps')).toBeDefined(); }); - it('includes enable source maps plugin from makeEnableSourceMapsVitePlugin', () => { + it('returns Sentry Vite Plugins and enable source maps plugin by default when sourcemaps is not specified', () => { const plugins = sentryTanstackStart({}); - const enablePlugin = plugins.find(p => p.name === 'sentry-tanstackstart-react-source-maps'); - expect(enablePlugin).toBeDefined(); + expect(plugins).toHaveLength(3); + expect(plugins.find(p => p.name === 'sentry-tanstackstart-source-maps-config')).toBeDefined(); + expect(plugins.find(p => p.name === 'sentry-vite-debug-id-upload-plugin')).toBeDefined(); + expect(plugins.find(p => p.name === 'sentry-tanstackstart-react-source-maps')).toBeDefined(); }); }); diff --git a/packages/tanstackstart-react/test/vite/sourceMaps.test.ts b/packages/tanstackstart-react/test/vite/sourceMaps.test.ts index 53c5703a56df..bc4f5ac64c93 100644 --- a/packages/tanstackstart-react/test/vite/sourceMaps.test.ts +++ b/packages/tanstackstart-react/test/vite/sourceMaps.test.ts @@ -92,13 +92,12 @@ describe('makeAddSentryVitePlugin()', () => { ); }); - it('returns correct plugins including config plugin', () => { + it('returns Sentry Vite plugins and config plugin', () => { const plugins = makeAddSentryVitePlugin({ org: 'my-org', authToken: 'my-token', }); - // Should have: config plugin + sentry vite plugin(s) expect(plugins.length).toBeGreaterThanOrEqual(2); const configPlugin = plugins.find(p => p.name === 'sentry-tanstackstart-source-maps-config'); @@ -126,24 +125,6 @@ describe('makeAddSentryVitePlugin()', () => { ); }); - it('passes sourcemaps.disable option to vite plugin', () => { - makeAddSentryVitePlugin({ - org: 'my-org', - authToken: 'my-token', - sourcemaps: { - disable: true, - }, - }); - - expect(sentryVitePluginSpy).toHaveBeenCalledWith( - expect.objectContaining({ - sourcemaps: expect.objectContaining({ - disable: true, - }), - }), - ); - }); - it('logs auto-delete message when user did not configure sourcemap', () => { const plugins = makeAddSentryVitePlugin({ org: 'my-org', From 841a31162c31f46d5ffc1662c93d02e4be1e7ecc Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Thu, 15 Jan 2026 11:55:45 +0100 Subject: [PATCH 17/31] bump bundler plugins --- packages/tanstackstart-react/package.json | 2 +- yarn.lock | 27 +++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/packages/tanstackstart-react/package.json b/packages/tanstackstart-react/package.json index 1675afb58c7d..371bf8b1cef7 100644 --- a/packages/tanstackstart-react/package.json +++ b/packages/tanstackstart-react/package.json @@ -56,7 +56,7 @@ "@sentry/core": "10.34.0", "@sentry/node": "10.34.0", "@sentry/react": "10.34.0", - "@sentry/vite-plugin": "^4.6.1" + "@sentry/vite-plugin": "^4.6.2" }, "devDependencies": { "vite": "^5.4.11" diff --git a/yarn.lock b/yarn.lock index 35c1b6941b0d..8f1968c9a10d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7074,6 +7074,11 @@ resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-4.6.1.tgz#94eec0293be8289daa574e18783e64d29203c236" integrity sha512-aSIk0vgBqv7PhX6/Eov+vlI4puCE0bRXzUG5HdCsHBpAfeMkI8Hva6kSOusnzKqs8bf04hU7s3Sf0XxGTj/1AA== +"@sentry/babel-plugin-component-annotate@4.6.2": + version "4.6.2" + resolved "http://127.0.0.1:4873/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-4.6.2.tgz#b052ded0fc12088d4a5032a4022b65551717a631" + integrity sha512-6VTjLJXtIHKwxMmThtZKwi1+hdklLNzlbYH98NhbH22/Vzb/c6BlSD2b5A0NGN9vFB807rD4x4tuP+Su7BxQXQ== + "@sentry/bundler-plugin-core@4.6.1", "@sentry/bundler-plugin-core@^4.6.1": version "4.6.1" resolved "https://registry.yarnpkg.com/@sentry/bundler-plugin-core/-/bundler-plugin-core-4.6.1.tgz#d6013e6233bf663114f581bbd3c3a380ff9311d4" @@ -7088,6 +7093,20 @@ magic-string "0.30.8" unplugin "1.0.1" +"@sentry/bundler-plugin-core@4.6.2": + version "4.6.2" + resolved "http://127.0.0.1:4873/@sentry/bundler-plugin-core/-/bundler-plugin-core-4.6.2.tgz#65239308aba07de9dad48bf51d6589be5d492860" + integrity sha512-JkOc3JkVzi/fbXsFp8R9uxNKmBrPRaU4Yu4y1i3ihWfugqymsIYaN0ixLENZbGk2j4xGHIk20PAJzBJqBMTHew== + dependencies: + "@babel/core" "^7.18.5" + "@sentry/babel-plugin-component-annotate" "4.6.2" + "@sentry/cli" "^2.57.0" + dotenv "^16.3.1" + find-up "^5.0.0" + glob "^10.5.0" + magic-string "0.30.8" + unplugin "1.0.1" + "@sentry/cli-darwin@2.58.4": version "2.58.4" resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.58.4.tgz#5e3005c1f845acac243e8dcb23bef17337924768" @@ -7164,6 +7183,14 @@ "@sentry/bundler-plugin-core" "4.6.1" unplugin "1.0.1" +"@sentry/vite-plugin@^4.6.2": + version "4.6.2" + resolved "http://127.0.0.1:4873/@sentry/vite-plugin/-/vite-plugin-4.6.2.tgz#e4d4321c089af8bf2bc20b8e9ee467881154d267" + integrity sha512-hK9N50LlTaPlb2P1r87CFupU7MJjvtrp+Js96a2KDdiP8ViWnw4Gsa/OvA0pkj2wAFXFeBQMLS6g/SktTKG54w== + dependencies: + "@sentry/bundler-plugin-core" "4.6.2" + unplugin "1.0.1" + "@sentry/webpack-plugin@^4.6.1": version "4.6.1" resolved "https://registry.yarnpkg.com/@sentry/webpack-plugin/-/webpack-plugin-4.6.1.tgz#8041ef27f302c3f63902bc4bb01e2ee53ea94c15" From 9ad7a8da2ee14288126278439ce58ab964c7b52c Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Thu, 15 Jan 2026 11:59:12 +0100 Subject: [PATCH 18/31] update --- packages/tanstackstart-react/package.json | 2 +- yarn.lock | 27 ----------------------- 2 files changed, 1 insertion(+), 28 deletions(-) diff --git a/packages/tanstackstart-react/package.json b/packages/tanstackstart-react/package.json index 371bf8b1cef7..1675afb58c7d 100644 --- a/packages/tanstackstart-react/package.json +++ b/packages/tanstackstart-react/package.json @@ -56,7 +56,7 @@ "@sentry/core": "10.34.0", "@sentry/node": "10.34.0", "@sentry/react": "10.34.0", - "@sentry/vite-plugin": "^4.6.2" + "@sentry/vite-plugin": "^4.6.1" }, "devDependencies": { "vite": "^5.4.11" diff --git a/yarn.lock b/yarn.lock index 8f1968c9a10d..35c1b6941b0d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7074,11 +7074,6 @@ resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-4.6.1.tgz#94eec0293be8289daa574e18783e64d29203c236" integrity sha512-aSIk0vgBqv7PhX6/Eov+vlI4puCE0bRXzUG5HdCsHBpAfeMkI8Hva6kSOusnzKqs8bf04hU7s3Sf0XxGTj/1AA== -"@sentry/babel-plugin-component-annotate@4.6.2": - version "4.6.2" - resolved "http://127.0.0.1:4873/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-4.6.2.tgz#b052ded0fc12088d4a5032a4022b65551717a631" - integrity sha512-6VTjLJXtIHKwxMmThtZKwi1+hdklLNzlbYH98NhbH22/Vzb/c6BlSD2b5A0NGN9vFB807rD4x4tuP+Su7BxQXQ== - "@sentry/bundler-plugin-core@4.6.1", "@sentry/bundler-plugin-core@^4.6.1": version "4.6.1" resolved "https://registry.yarnpkg.com/@sentry/bundler-plugin-core/-/bundler-plugin-core-4.6.1.tgz#d6013e6233bf663114f581bbd3c3a380ff9311d4" @@ -7093,20 +7088,6 @@ magic-string "0.30.8" unplugin "1.0.1" -"@sentry/bundler-plugin-core@4.6.2": - version "4.6.2" - resolved "http://127.0.0.1:4873/@sentry/bundler-plugin-core/-/bundler-plugin-core-4.6.2.tgz#65239308aba07de9dad48bf51d6589be5d492860" - integrity sha512-JkOc3JkVzi/fbXsFp8R9uxNKmBrPRaU4Yu4y1i3ihWfugqymsIYaN0ixLENZbGk2j4xGHIk20PAJzBJqBMTHew== - dependencies: - "@babel/core" "^7.18.5" - "@sentry/babel-plugin-component-annotate" "4.6.2" - "@sentry/cli" "^2.57.0" - dotenv "^16.3.1" - find-up "^5.0.0" - glob "^10.5.0" - magic-string "0.30.8" - unplugin "1.0.1" - "@sentry/cli-darwin@2.58.4": version "2.58.4" resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.58.4.tgz#5e3005c1f845acac243e8dcb23bef17337924768" @@ -7183,14 +7164,6 @@ "@sentry/bundler-plugin-core" "4.6.1" unplugin "1.0.1" -"@sentry/vite-plugin@^4.6.2": - version "4.6.2" - resolved "http://127.0.0.1:4873/@sentry/vite-plugin/-/vite-plugin-4.6.2.tgz#e4d4321c089af8bf2bc20b8e9ee467881154d267" - integrity sha512-hK9N50LlTaPlb2P1r87CFupU7MJjvtrp+Js96a2KDdiP8ViWnw4Gsa/OvA0pkj2wAFXFeBQMLS6g/SktTKG54w== - dependencies: - "@sentry/bundler-plugin-core" "4.6.2" - unplugin "1.0.1" - "@sentry/webpack-plugin@^4.6.1": version "4.6.1" resolved "https://registry.yarnpkg.com/@sentry/webpack-plugin/-/webpack-plugin-4.6.1.tgz#8041ef27f302c3f63902bc4bb01e2ee53ea94c15" From 286f62434e6ccb11290e0f80e6d53c9020aa58af Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Thu, 15 Jan 2026 14:01:50 +0100 Subject: [PATCH 19/31] update bundler plugins fr this time --- packages/tanstackstart-react/package.json | 2 +- yarn.lock | 27 +++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/packages/tanstackstart-react/package.json b/packages/tanstackstart-react/package.json index 1675afb58c7d..371bf8b1cef7 100644 --- a/packages/tanstackstart-react/package.json +++ b/packages/tanstackstart-react/package.json @@ -56,7 +56,7 @@ "@sentry/core": "10.34.0", "@sentry/node": "10.34.0", "@sentry/react": "10.34.0", - "@sentry/vite-plugin": "^4.6.1" + "@sentry/vite-plugin": "^4.6.2" }, "devDependencies": { "vite": "^5.4.11" diff --git a/yarn.lock b/yarn.lock index 35c1b6941b0d..822e7de599bf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7074,6 +7074,11 @@ resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-4.6.1.tgz#94eec0293be8289daa574e18783e64d29203c236" integrity sha512-aSIk0vgBqv7PhX6/Eov+vlI4puCE0bRXzUG5HdCsHBpAfeMkI8Hva6kSOusnzKqs8bf04hU7s3Sf0XxGTj/1AA== +"@sentry/babel-plugin-component-annotate@4.6.2": + version "4.6.2" + resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-4.6.2.tgz#b052ded0fc12088d4a5032a4022b65551717a631" + integrity sha512-6VTjLJXtIHKwxMmThtZKwi1+hdklLNzlbYH98NhbH22/Vzb/c6BlSD2b5A0NGN9vFB807rD4x4tuP+Su7BxQXQ== + "@sentry/bundler-plugin-core@4.6.1", "@sentry/bundler-plugin-core@^4.6.1": version "4.6.1" resolved "https://registry.yarnpkg.com/@sentry/bundler-plugin-core/-/bundler-plugin-core-4.6.1.tgz#d6013e6233bf663114f581bbd3c3a380ff9311d4" @@ -7088,6 +7093,20 @@ magic-string "0.30.8" unplugin "1.0.1" +"@sentry/bundler-plugin-core@4.6.2": + version "4.6.2" + resolved "https://registry.yarnpkg.com/@sentry/bundler-plugin-core/-/bundler-plugin-core-4.6.2.tgz#65239308aba07de9dad48bf51d6589be5d492860" + integrity sha512-JkOc3JkVzi/fbXsFp8R9uxNKmBrPRaU4Yu4y1i3ihWfugqymsIYaN0ixLENZbGk2j4xGHIk20PAJzBJqBMTHew== + dependencies: + "@babel/core" "^7.18.5" + "@sentry/babel-plugin-component-annotate" "4.6.2" + "@sentry/cli" "^2.57.0" + dotenv "^16.3.1" + find-up "^5.0.0" + glob "^10.5.0" + magic-string "0.30.8" + unplugin "1.0.1" + "@sentry/cli-darwin@2.58.4": version "2.58.4" resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.58.4.tgz#5e3005c1f845acac243e8dcb23bef17337924768" @@ -7164,6 +7183,14 @@ "@sentry/bundler-plugin-core" "4.6.1" unplugin "1.0.1" +"@sentry/vite-plugin@^4.6.2": + version "4.6.2" + resolved "https://registry.yarnpkg.com/@sentry/vite-plugin/-/vite-plugin-4.6.2.tgz#e4d4321c089af8bf2bc20b8e9ee467881154d267" + integrity sha512-hK9N50LlTaPlb2P1r87CFupU7MJjvtrp+Js96a2KDdiP8ViWnw4Gsa/OvA0pkj2wAFXFeBQMLS6g/SktTKG54w== + dependencies: + "@sentry/bundler-plugin-core" "4.6.2" + unplugin "1.0.1" + "@sentry/webpack-plugin@^4.6.1": version "4.6.1" resolved "https://registry.yarnpkg.com/@sentry/webpack-plugin/-/webpack-plugin-4.6.1.tgz#8041ef27f302c3f63902bc4bb01e2ee53ea94c15" From 505c92cbee8bfd2e01e42d83031284f52d5912c1 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Thu, 15 Jan 2026 14:19:10 +0100 Subject: [PATCH 20/31] Revert "update bundler plugins fr this time" This reverts commit 286f62434e6ccb11290e0f80e6d53c9020aa58af. --- packages/tanstackstart-react/package.json | 2 +- yarn.lock | 27 ----------------------- 2 files changed, 1 insertion(+), 28 deletions(-) diff --git a/packages/tanstackstart-react/package.json b/packages/tanstackstart-react/package.json index 371bf8b1cef7..1675afb58c7d 100644 --- a/packages/tanstackstart-react/package.json +++ b/packages/tanstackstart-react/package.json @@ -56,7 +56,7 @@ "@sentry/core": "10.34.0", "@sentry/node": "10.34.0", "@sentry/react": "10.34.0", - "@sentry/vite-plugin": "^4.6.2" + "@sentry/vite-plugin": "^4.6.1" }, "devDependencies": { "vite": "^5.4.11" diff --git a/yarn.lock b/yarn.lock index 822e7de599bf..35c1b6941b0d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7074,11 +7074,6 @@ resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-4.6.1.tgz#94eec0293be8289daa574e18783e64d29203c236" integrity sha512-aSIk0vgBqv7PhX6/Eov+vlI4puCE0bRXzUG5HdCsHBpAfeMkI8Hva6kSOusnzKqs8bf04hU7s3Sf0XxGTj/1AA== -"@sentry/babel-plugin-component-annotate@4.6.2": - version "4.6.2" - resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-4.6.2.tgz#b052ded0fc12088d4a5032a4022b65551717a631" - integrity sha512-6VTjLJXtIHKwxMmThtZKwi1+hdklLNzlbYH98NhbH22/Vzb/c6BlSD2b5A0NGN9vFB807rD4x4tuP+Su7BxQXQ== - "@sentry/bundler-plugin-core@4.6.1", "@sentry/bundler-plugin-core@^4.6.1": version "4.6.1" resolved "https://registry.yarnpkg.com/@sentry/bundler-plugin-core/-/bundler-plugin-core-4.6.1.tgz#d6013e6233bf663114f581bbd3c3a380ff9311d4" @@ -7093,20 +7088,6 @@ magic-string "0.30.8" unplugin "1.0.1" -"@sentry/bundler-plugin-core@4.6.2": - version "4.6.2" - resolved "https://registry.yarnpkg.com/@sentry/bundler-plugin-core/-/bundler-plugin-core-4.6.2.tgz#65239308aba07de9dad48bf51d6589be5d492860" - integrity sha512-JkOc3JkVzi/fbXsFp8R9uxNKmBrPRaU4Yu4y1i3ihWfugqymsIYaN0ixLENZbGk2j4xGHIk20PAJzBJqBMTHew== - dependencies: - "@babel/core" "^7.18.5" - "@sentry/babel-plugin-component-annotate" "4.6.2" - "@sentry/cli" "^2.57.0" - dotenv "^16.3.1" - find-up "^5.0.0" - glob "^10.5.0" - magic-string "0.30.8" - unplugin "1.0.1" - "@sentry/cli-darwin@2.58.4": version "2.58.4" resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.58.4.tgz#5e3005c1f845acac243e8dcb23bef17337924768" @@ -7183,14 +7164,6 @@ "@sentry/bundler-plugin-core" "4.6.1" unplugin "1.0.1" -"@sentry/vite-plugin@^4.6.2": - version "4.6.2" - resolved "https://registry.yarnpkg.com/@sentry/vite-plugin/-/vite-plugin-4.6.2.tgz#e4d4321c089af8bf2bc20b8e9ee467881154d267" - integrity sha512-hK9N50LlTaPlb2P1r87CFupU7MJjvtrp+Js96a2KDdiP8ViWnw4Gsa/OvA0pkj2wAFXFeBQMLS6g/SktTKG54w== - dependencies: - "@sentry/bundler-plugin-core" "4.6.2" - unplugin "1.0.1" - "@sentry/webpack-plugin@^4.6.1": version "4.6.1" resolved "https://registry.yarnpkg.com/@sentry/webpack-plugin/-/webpack-plugin-4.6.1.tgz#8041ef27f302c3f63902bc4bb01e2ee53ea94c15" From 8ce5358b79a321fc899883ab684792d4d4985d24 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Fri, 16 Jan 2026 08:21:42 +0100 Subject: [PATCH 21/31] update sentry vite plugin --- packages/tanstackstart-react/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tanstackstart-react/package.json b/packages/tanstackstart-react/package.json index 1675afb58c7d..371bf8b1cef7 100644 --- a/packages/tanstackstart-react/package.json +++ b/packages/tanstackstart-react/package.json @@ -56,7 +56,7 @@ "@sentry/core": "10.34.0", "@sentry/node": "10.34.0", "@sentry/react": "10.34.0", - "@sentry/vite-plugin": "^4.6.1" + "@sentry/vite-plugin": "^4.6.2" }, "devDependencies": { "vite": "^5.4.11" From 76c746c84b89da8a9a5ac1be02d91739509ca7d2 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Fri, 16 Jan 2026 14:49:51 +0100 Subject: [PATCH 22/31] address some pr comments --- .../src/vite/sentryTanstackStart.ts | 16 ++++++------- .../src/vite/sourceMaps.ts | 6 ++--- .../test/vite/sentryTanstackStart.test.ts | 24 +++++-------------- .../test/vite/sourceMaps.test.ts | 2 +- 4 files changed, 18 insertions(+), 30 deletions(-) diff --git a/packages/tanstackstart-react/src/vite/sentryTanstackStart.ts b/packages/tanstackstart-react/src/vite/sentryTanstackStart.ts index 8f1742436f9f..00dc145117be 100644 --- a/packages/tanstackstart-react/src/vite/sentryTanstackStart.ts +++ b/packages/tanstackstart-react/src/vite/sentryTanstackStart.ts @@ -27,16 +27,16 @@ import { makeAddSentryVitePlugin, makeEnableSourceMapsVitePlugin } from './sourc * @returns An array of Vite plugins */ export function sentryTanstackStart(options: BuildTimeOptionsBase = {}): Plugin[] { - const plugins: Plugin[] = []; - // Only add plugins in production builds - if (process.env.NODE_ENV !== 'development') { - plugins.push(...makeAddSentryVitePlugin(options)); + if (process.env.NODE_ENV === 'development') { + return []; + } + + const plugins: Plugin[] = [...makeAddSentryVitePlugin(options)]; - const sourceMapsDisabled = options.sourcemaps?.disable === true || options.sourcemaps?.disable === 'disable-upload'; - if (!sourceMapsDisabled) { - plugins.push(...makeEnableSourceMapsVitePlugin(options)); - } + const sourceMapsDisabled = options.sourcemaps?.disable === true || options.sourcemaps?.disable === 'disable-upload'; + if (!sourceMapsDisabled) { + plugins.push(...makeEnableSourceMapsVitePlugin(options)); } return plugins; diff --git a/packages/tanstackstart-react/src/vite/sourceMaps.ts b/packages/tanstackstart-react/src/vite/sourceMaps.ts index f31a5bc18679..0f96d765e00c 100644 --- a/packages/tanstackstart-react/src/vite/sourceMaps.ts +++ b/packages/tanstackstart-react/src/vite/sourceMaps.ts @@ -34,15 +34,15 @@ export function makeAddSentryVitePlugin(options: BuildTimeOptionsBase): Plugin[] ) { // eslint-disable-next-line no-console console.log( - '[Sentry] Automatically setting `sourcemaps.filesToDeleteAfterUpload: [".*/**/*.map"]` to delete generated source maps after they were uploaded to Sentry.', + '[Sentry] Automatically setting `sourcemaps.filesToDeleteAfterUpload: ["./**/*.map"]` to delete generated source maps after they were uploaded to Sentry.', ); } }, }; - // Default to auto-deleting source maps from hidden directories after upload + // Default to auto-deleting all source maps after upload // Users can override this by explicitly setting sourcemaps.filesToDeleteAfterUpload - const defaultFilesToDelete = ['.*/**/*.map']; + const defaultFilesToDelete = ['./**/*.map']; const filesToDeleteAfterUpload = sourcemaps?.filesToDeleteAfterUpload ?? defaultFilesToDelete; const sentryPlugins = sentryVitePlugin({ diff --git a/packages/tanstackstart-react/test/vite/sentryTanstackStart.test.ts b/packages/tanstackstart-react/test/vite/sentryTanstackStart.test.ts index 53b3657c5475..ff0f253297db 100644 --- a/packages/tanstackstart-react/test/vite/sentryTanstackStart.test.ts +++ b/packages/tanstackstart-react/test/vite/sentryTanstackStart.test.ts @@ -39,7 +39,7 @@ describe('sentryTanstackStart()', () => { it('returns plugins in production mode', () => { const plugins = sentryTanstackStart({ org: 'test-org' }); - expect(plugins.length).toBeGreaterThan(0); + expect(plugins).toEqual([mockSourceMapsConfigPlugin, mockSentryVitePlugin, mockEnableSourceMapsPlugin]); }); it('returns no plugins in development mode', () => { @@ -47,7 +47,7 @@ describe('sentryTanstackStart()', () => { const plugins = sentryTanstackStart({ org: 'test-org' }); - expect(plugins).toHaveLength(0); + expect(plugins).toEqual([]); }); it('returns Sentry Vite plugins but not enable source maps plugin when sourcemaps.disable is true', () => { @@ -55,10 +55,7 @@ describe('sentryTanstackStart()', () => { sourcemaps: { disable: true }, }); - expect(plugins).toHaveLength(2); - expect(plugins.find(p => p.name === 'sentry-tanstackstart-source-maps-config')).toBeDefined(); - expect(plugins.find(p => p.name === 'sentry-vite-debug-id-upload-plugin')).toBeDefined(); - expect(plugins.find(p => p.name === 'sentry-tanstackstart-react-source-maps')).toBeUndefined(); + expect(plugins).toEqual([mockSourceMapsConfigPlugin, mockSentryVitePlugin]); }); it('returns Sentry Vite plugins but not enable source maps plugin when sourcemaps.disable is "disable-upload"', () => { @@ -66,10 +63,7 @@ describe('sentryTanstackStart()', () => { sourcemaps: { disable: 'disable-upload' }, }); - expect(plugins).toHaveLength(2); - expect(plugins.find(p => p.name === 'sentry-tanstackstart-source-maps-config')).toBeDefined(); - expect(plugins.find(p => p.name === 'sentry-vite-debug-id-upload-plugin')).toBeDefined(); - expect(plugins.find(p => p.name === 'sentry-tanstackstart-react-source-maps')).toBeUndefined(); + expect(plugins).toEqual([mockSourceMapsConfigPlugin, mockSentryVitePlugin]); }); it('returns Sentry Vite plugins and enable source maps plugin when sourcemaps.disable is false', () => { @@ -77,18 +71,12 @@ describe('sentryTanstackStart()', () => { sourcemaps: { disable: false }, }); - expect(plugins).toHaveLength(3); - expect(plugins.find(p => p.name === 'sentry-tanstackstart-source-maps-config')).toBeDefined(); - expect(plugins.find(p => p.name === 'sentry-vite-debug-id-upload-plugin')).toBeDefined(); - expect(plugins.find(p => p.name === 'sentry-tanstackstart-react-source-maps')).toBeDefined(); + expect(plugins).toEqual([mockSourceMapsConfigPlugin, mockSentryVitePlugin, mockEnableSourceMapsPlugin]); }); it('returns Sentry Vite Plugins and enable source maps plugin by default when sourcemaps is not specified', () => { const plugins = sentryTanstackStart({}); - expect(plugins).toHaveLength(3); - expect(plugins.find(p => p.name === 'sentry-tanstackstart-source-maps-config')).toBeDefined(); - expect(plugins.find(p => p.name === 'sentry-vite-debug-id-upload-plugin')).toBeDefined(); - expect(plugins.find(p => p.name === 'sentry-tanstackstart-react-source-maps')).toBeDefined(); + expect(plugins).toEqual([mockSourceMapsConfigPlugin, mockSentryVitePlugin, mockEnableSourceMapsPlugin]); }); }); diff --git a/packages/tanstackstart-react/test/vite/sourceMaps.test.ts b/packages/tanstackstart-react/test/vite/sourceMaps.test.ts index bc4f5ac64c93..1fba13b90407 100644 --- a/packages/tanstackstart-react/test/vite/sourceMaps.test.ts +++ b/packages/tanstackstart-react/test/vite/sourceMaps.test.ts @@ -119,7 +119,7 @@ describe('makeAddSentryVitePlugin()', () => { assets: undefined, disable: undefined, ignore: undefined, - filesToDeleteAfterUpload: ['.*/**/*.map'], + filesToDeleteAfterUpload: ['./**/*.map'], }, }), ); From bbf7be43302e980f4d453fdaaf578d850851216a Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Fri, 16 Jan 2026 14:54:00 +0100 Subject: [PATCH 23/31] use post for config plugin --- packages/tanstackstart-react/src/vite/sourceMaps.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tanstackstart-react/src/vite/sourceMaps.ts b/packages/tanstackstart-react/src/vite/sourceMaps.ts index 0f96d765e00c..49e45f186452 100644 --- a/packages/tanstackstart-react/src/vite/sourceMaps.ts +++ b/packages/tanstackstart-react/src/vite/sourceMaps.ts @@ -24,7 +24,7 @@ export function makeAddSentryVitePlugin(options: BuildTimeOptionsBase): Plugin[] const configPlugin: Plugin = { name: 'sentry-tanstackstart-source-maps-config', apply: 'build', - enforce: 'pre', + enforce: 'post', config(config) { // Emit a warning if we're auto-deleting source maps if ( From 63a66609a6a41c664c4b14421b5ecffa3bafa114 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Fri, 16 Jan 2026 15:43:43 +0100 Subject: [PATCH 24/31] fix files to delete after upload settings --- .../src/vite/sourceMaps.ts | 40 +++++----- .../test/vite/sentryTanstackStart.test.ts | 2 +- .../test/vite/sourceMaps.test.ts | 74 +++++++++++++------ 3 files changed, 74 insertions(+), 42 deletions(-) diff --git a/packages/tanstackstart-react/src/vite/sourceMaps.ts b/packages/tanstackstart-react/src/vite/sourceMaps.ts index 49e45f186452..e9ee193b8d8a 100644 --- a/packages/tanstackstart-react/src/vite/sourceMaps.ts +++ b/packages/tanstackstart-react/src/vite/sourceMaps.ts @@ -2,6 +2,8 @@ import type { BuildTimeOptionsBase } from '@sentry/core'; import { sentryVitePlugin } from '@sentry/vite-plugin'; import type { Plugin, UserConfig } from 'vite'; +type FilesToDeleteAfterUpload = string | string[] | undefined; + /** * A Sentry plugin for adding the @sentry/vite-plugin to automatically upload source maps to Sentry. */ @@ -21,30 +23,34 @@ export function makeAddSentryVitePlugin(options: BuildTimeOptionsBase): Plugin[] telemetry, } = options; + // defer resolving the filesToDeleteAfterUpload until we got access to the Vite config + let resolveFilesToDeleteAfterUpload: ((value: FilesToDeleteAfterUpload) => void) | undefined; + const filesToDeleteAfterUploadPromise = new Promise(resolve => { + resolveFilesToDeleteAfterUpload = resolve; + }); + const configPlugin: Plugin = { - name: 'sentry-tanstackstart-source-maps-config', + name: 'sentry-tanstackstart-files-to-delete-after-upload-plugin', apply: 'build', enforce: 'post', config(config) { - // Emit a warning if we're auto-deleting source maps - if ( - typeof sourcemaps?.filesToDeleteAfterUpload === 'undefined' && - typeof config.build?.sourcemap === 'undefined' && - debug - ) { - // eslint-disable-next-line no-console - console.log( - '[Sentry] Automatically setting `sourcemaps.filesToDeleteAfterUpload: ["./**/*.map"]` to delete generated source maps after they were uploaded to Sentry.', - ); + const userFilesToDelete = sourcemaps?.filesToDeleteAfterUpload; + + // Only auto-delete source maps if the user didn't configure sourcemaps at all + if (typeof userFilesToDelete === 'undefined' && typeof config.build?.sourcemap === 'undefined') { + if (debug) { + // eslint-disable-next-line no-console + console.log( + '[Sentry] Automatically setting `sourcemaps.filesToDeleteAfterUpload: ["./**/*.map"]` to delete generated source maps after they were uploaded to Sentry.', + ); + } + resolveFilesToDeleteAfterUpload?.(['./**/*.map']); + } else { + resolveFilesToDeleteAfterUpload?.(userFilesToDelete); } }, }; - // Default to auto-deleting all source maps after upload - // Users can override this by explicitly setting sourcemaps.filesToDeleteAfterUpload - const defaultFilesToDelete = ['./**/*.map']; - const filesToDeleteAfterUpload = sourcemaps?.filesToDeleteAfterUpload ?? defaultFilesToDelete; - const sentryPlugins = sentryVitePlugin({ authToken: authToken ?? process.env.SENTRY_AUTH_TOKEN, bundleSizeOptimizations: bundleSizeOptimizations ?? undefined, @@ -59,7 +65,7 @@ export function makeAddSentryVitePlugin(options: BuildTimeOptionsBase): Plugin[] assets: sourcemaps?.assets, disable: sourcemaps?.disable, ignore: sourcemaps?.ignore, - filesToDeleteAfterUpload, + filesToDeleteAfterUpload: filesToDeleteAfterUploadPromise, }, telemetry: telemetry ?? true, url: sentryUrl, diff --git a/packages/tanstackstart-react/test/vite/sentryTanstackStart.test.ts b/packages/tanstackstart-react/test/vite/sentryTanstackStart.test.ts index ff0f253297db..390b601d8808 100644 --- a/packages/tanstackstart-react/test/vite/sentryTanstackStart.test.ts +++ b/packages/tanstackstart-react/test/vite/sentryTanstackStart.test.ts @@ -3,7 +3,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { sentryTanstackStart } from '../../src/vite/sentryTanstackStart'; const mockSourceMapsConfigPlugin: Plugin = { - name: 'sentry-tanstackstart-source-maps-config', + name: 'sentry-tanstackstart-files-to-delete-after-upload-plugin', apply: 'build', enforce: 'pre', config: vi.fn(), diff --git a/packages/tanstackstart-react/test/vite/sourceMaps.test.ts b/packages/tanstackstart-react/test/vite/sourceMaps.test.ts index 1fba13b90407..f3ddf6362847 100644 --- a/packages/tanstackstart-react/test/vite/sourceMaps.test.ts +++ b/packages/tanstackstart-react/test/vite/sourceMaps.test.ts @@ -37,9 +37,9 @@ describe('makeEnableSourceMapsVitePlugin()', () => { }); describe('makeAddSentryVitePlugin()', () => { - it('passes user-specified vite plugin options to vite plugin', () => { + it('passes user-specified vite plugin options to vite plugin', async () => { const errorHandler = vi.fn(); - makeAddSentryVitePlugin({ + const plugins = makeAddSentryVitePlugin({ org: 'my-org', authToken: 'my-token', sentryUrl: 'https://custom.sentry.io', @@ -64,6 +64,18 @@ describe('makeAddSentryVitePlugin()', () => { }, }); + // The filesToDeleteAfterUpload is now a Promise that resolves when the config hook runs + const calledOptions = sentryVitePluginSpy.mock.calls[0]?.[0]; + expect(calledOptions?.sourcemaps?.filesToDeleteAfterUpload).toBeInstanceOf(Promise); + + // Trigger the config hook to resolve the Promise + const configPlugin = plugins.find(p => p.name === 'sentry-tanstackstart-files-to-delete-after-upload-plugin'); + (configPlugin?.config as (config: UserConfig) => void)({}); + + // Verify the Promise resolves to the user-specified value + const resolvedFilesToDelete = await calledOptions?.sourcemaps?.filesToDeleteAfterUpload; + expect(resolvedFilesToDelete).toEqual(['baz/*.js']); + expect(sentryVitePluginSpy).toHaveBeenCalledWith( expect.objectContaining({ org: 'my-org', @@ -79,12 +91,6 @@ describe('makeAddSentryVitePlugin()', () => { finalize: true, dist: 'dist-1', }, - sourcemaps: { - assets: ['dist/**/*.js'], - disable: false, - ignore: ['node_modules/**'], - filesToDeleteAfterUpload: ['baz/*.js'], - }, bundleSizeOptimizations: { excludeTracing: true, }, @@ -100,29 +106,49 @@ describe('makeAddSentryVitePlugin()', () => { expect(plugins.length).toBeGreaterThanOrEqual(2); - const configPlugin = plugins.find(p => p.name === 'sentry-tanstackstart-source-maps-config'); + const configPlugin = plugins.find(p => p.name === 'sentry-tanstackstart-files-to-delete-after-upload-plugin'); expect(configPlugin).toBeDefined(); expect(configPlugin?.apply).toBe('build'); - expect(configPlugin?.enforce).toBe('pre'); + expect(configPlugin?.enforce).toBe('post'); expect(typeof configPlugin?.config).toBe('function'); }); - it('uses default filesToDeleteAfterUpload when not specified', () => { - makeAddSentryVitePlugin({ + it('uses default filesToDeleteAfterUpload when not specified', async () => { + const plugins = makeAddSentryVitePlugin({ org: 'my-org', authToken: 'my-token', }); - expect(sentryVitePluginSpy).toHaveBeenCalledWith( - expect.objectContaining({ - sourcemaps: { - assets: undefined, - disable: undefined, - ignore: undefined, - filesToDeleteAfterUpload: ['./**/*.map'], - }, - }), - ); + // The filesToDeleteAfterUpload is now a Promise + const calledOptions = sentryVitePluginSpy.mock.calls[0]?.[0]; + expect(calledOptions?.sourcemaps?.filesToDeleteAfterUpload).toBeInstanceOf(Promise); + + // Trigger the config hook to resolve the Promise (with no sourcemap configured) + const configPlugin = plugins.find(p => p.name === 'sentry-tanstackstart-files-to-delete-after-upload-plugin'); + (configPlugin?.config as (config: UserConfig) => void)({}); + + // Verify the Promise resolves to the default value + const resolvedFilesToDelete = await calledOptions?.sourcemaps?.filesToDeleteAfterUpload; + expect(resolvedFilesToDelete).toEqual(['./**/*.map']); + }); + + it('does not auto-delete when user configured build.sourcemap', async () => { + const plugins = makeAddSentryVitePlugin({ + org: 'my-org', + authToken: 'my-token', + }); + + // The filesToDeleteAfterUpload is a Promise + const calledOptions = sentryVitePluginSpy.mock.calls[0]?.[0]; + expect(calledOptions?.sourcemaps?.filesToDeleteAfterUpload).toBeInstanceOf(Promise); + + // Trigger the config hook with sourcemap configured by user + const configPlugin = plugins.find(p => p.name === 'sentry-tanstackstart-files-to-delete-after-upload-plugin'); + (configPlugin?.config as (config: UserConfig) => void)({ build: { sourcemap: true } }); + + // Verify the Promise resolves to undefined (no auto-delete) + const resolvedFilesToDelete = await calledOptions?.sourcemaps?.filesToDeleteAfterUpload; + expect(resolvedFilesToDelete).toBeUndefined(); }); it('logs auto-delete message when user did not configure sourcemap', () => { @@ -132,7 +158,7 @@ describe('makeAddSentryVitePlugin()', () => { debug: true, }); - const configPlugin = plugins.find(p => p.name === 'sentry-tanstackstart-source-maps-config'); + const configPlugin = plugins.find(p => p.name === 'sentry-tanstackstart-files-to-delete-after-upload-plugin'); expect(configPlugin).toBeDefined(); const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); @@ -154,7 +180,7 @@ describe('makeAddSentryVitePlugin()', () => { debug: true, }); - const configPlugin = plugins.find(p => p.name === 'sentry-tanstackstart-source-maps-config'); + const configPlugin = plugins.find(p => p.name === 'sentry-tanstackstart-files-to-delete-after-upload-plugin'); expect(configPlugin).toBeDefined(); const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); From e2bf4b0a1e96f5027de081332e9e84152cacbca1 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Fri, 16 Jan 2026 08:18:49 +0100 Subject: [PATCH 25/31] make global middleware auto-wrapping work --- .../tanstackstart-react/src/middleware.ts | 12 ++- .../tanstackstart-react/src/start.ts | 7 +- .../src/vite/autoInstrumentMiddleware.ts | 89 +++++++++++++++++++ .../tanstackstart-react/src/vite/index.ts | 1 + .../src/vite/sentryTanstackStart.ts | 30 ++++++- 5 files changed, 127 insertions(+), 12 deletions(-) create mode 100644 packages/tanstackstart-react/src/vite/autoInstrumentMiddleware.ts diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react/src/middleware.ts b/dev-packages/e2e-tests/test-applications/tanstackstart-react/src/middleware.ts index daf81ea97e10..ec41ba331228 100644 --- a/dev-packages/e2e-tests/test-applications/tanstackstart-react/src/middleware.ts +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react/src/middleware.ts @@ -2,13 +2,15 @@ import { createMiddleware } from '@tanstack/react-start'; import { wrapMiddlewaresWithSentry } from '@sentry/tanstackstart-react'; // Global request middleware - runs on every request -const globalRequestMiddleware = createMiddleware().server(async ({ next }) => { +// NOTE: This is exported unwrapped to test auto-instrumentation via the Vite plugin +export const globalRequestMiddleware = createMiddleware().server(async ({ next }) => { console.log('Global request middleware executed'); return next(); }); // Global function middleware - runs on every server function -const globalFunctionMiddleware = createMiddleware({ type: 'function' }).server(async ({ next }) => { +// NOTE: This is exported unwrapped to test auto-instrumentation via the Vite plugin +export const globalFunctionMiddleware = createMiddleware({ type: 'function' }).server(async ({ next }) => { console.log('Global function middleware executed'); return next(); }); @@ -37,17 +39,13 @@ const errorMiddleware = createMiddleware({ type: 'function' }).server(async () = throw new Error('Middleware Error Test'); }); -// Manually wrap middlewares with Sentry +// Manually wrap middlewares with Sentry (for middlewares that aren't auto-instrumented yet) export const [ - wrappedGlobalRequestMiddleware, - wrappedGlobalFunctionMiddleware, wrappedServerFnMiddleware, wrappedServerRouteRequestMiddleware, wrappedEarlyReturnMiddleware, wrappedErrorMiddleware, ] = wrapMiddlewaresWithSentry({ - globalRequestMiddleware, - globalFunctionMiddleware, serverFnMiddleware, serverRouteRequestMiddleware, earlyReturnMiddleware, diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react/src/start.ts b/dev-packages/e2e-tests/test-applications/tanstackstart-react/src/start.ts index eecd2816e492..0dc32ebd112f 100644 --- a/dev-packages/e2e-tests/test-applications/tanstackstart-react/src/start.ts +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react/src/start.ts @@ -1,9 +1,10 @@ import { createStart } from '@tanstack/react-start'; -import { wrappedGlobalRequestMiddleware, wrappedGlobalFunctionMiddleware } from './middleware'; +// NOTE: These are NOT wrapped - auto-instrumentation via the Vite plugin will wrap them +import { globalRequestMiddleware, globalFunctionMiddleware } from './middleware'; export const startInstance = createStart(() => { return { - requestMiddleware: [wrappedGlobalRequestMiddleware], - functionMiddleware: [wrappedGlobalFunctionMiddleware], + requestMiddleware: [globalRequestMiddleware], + functionMiddleware: [globalFunctionMiddleware], }; }); diff --git a/packages/tanstackstart-react/src/vite/autoInstrumentMiddleware.ts b/packages/tanstackstart-react/src/vite/autoInstrumentMiddleware.ts new file mode 100644 index 000000000000..f01b3ea30a05 --- /dev/null +++ b/packages/tanstackstart-react/src/vite/autoInstrumentMiddleware.ts @@ -0,0 +1,89 @@ +import type { Plugin } from 'vite'; + +type AutoInstrumentMiddlewareOptions = { + enabled?: boolean; + debug?: boolean; +}; + +/** + * A Vite plugin that automatically instruments TanStack Start middlewares. + * + * Phase 1: Only wraps global middlewares in `createStart()` configuration + * (`requestMiddleware` and `functionMiddleware` arrays). + * + * @param options - Configuration options for the plugin + * @returns A Vite plugin + */ +export function makeAutoInstrumentMiddlewarePlugin(options: AutoInstrumentMiddlewareOptions = {}): Plugin { + const { enabled = true, debug = false } = options; + + return { + name: 'sentry-tanstack-middleware-auto-instrument', + enforce: 'pre', + + transform(code, id) { + if (!enabled) { + return null; + } + + // Skip if not a TS/JS file + if (!/\.(ts|tsx|js|jsx|mjs|mts)$/.test(id)) { + return null; + } + + // only wrap requestMiddleware and functionMiddleware in createStart() + if (!code.includes('createStart')) { + return null; + } + + let transformed = code; + let needsImport = false; + + transformed = transformed.replace( + /(requestMiddleware|functionMiddleware)\s*:\s*\[([^\]]*)\]/g, + (match, key, contents) => { + const objContents = arrayToObjectShorthand(contents); + if (objContents) { + needsImport = true; + if (debug) { + // eslint-disable-next-line no-console + console.log(`[Sentry] Auto-wrapping ${key} in ${id}`); + } + return `${key}: wrapMiddlewaresWithSentry(${objContents})`; + } + return match; + }, + ); + + if (needsImport) { + transformed = `import { wrapMiddlewaresWithSentry } from '@sentry/tanstackstart-react';\n${transformed}`; + console.log(`[Sentry] transformed:`, transformed); + return { code: transformed, map: null }; + } + + return null; + }, + }; +} + +/** + * Convert array contents to object shorthand syntax. + * e.g., "foo, bar, baz" → "{ foo, bar, baz }" + * + * Returns null if contents contain non-identifier expressions (function calls, etc.) + * which cannot be converted to object shorthand. + */ +function arrayToObjectShorthand(contents: string): string | null { + const items = contents + .split(',') + .map(s => s.trim()) + .filter(Boolean); + + // Only convert if all items are valid identifiers (no complex expressions) + const allIdentifiers = items.every(item => /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(item)); + if (!allIdentifiers || items.length === 0) { + return null; + } + + return `{ ${items.join(', ')} }`; +} diff --git a/packages/tanstackstart-react/src/vite/index.ts b/packages/tanstackstart-react/src/vite/index.ts index 4af3423136fb..85143344028d 100644 --- a/packages/tanstackstart-react/src/vite/index.ts +++ b/packages/tanstackstart-react/src/vite/index.ts @@ -1 +1,2 @@ export { sentryTanstackStart } from './sentryTanstackStart'; +export type { SentryTanstackStartOptions } from './sentryTanstackStart'; diff --git a/packages/tanstackstart-react/src/vite/sentryTanstackStart.ts b/packages/tanstackstart-react/src/vite/sentryTanstackStart.ts index 00dc145117be..0057fc734be6 100644 --- a/packages/tanstackstart-react/src/vite/sentryTanstackStart.ts +++ b/packages/tanstackstart-react/src/vite/sentryTanstackStart.ts @@ -1,7 +1,26 @@ import type { BuildTimeOptionsBase } from '@sentry/core'; import type { Plugin } from 'vite'; +import { makeAutoInstrumentMiddlewarePlugin } from './autoInstrumentMiddleware'; import { makeAddSentryVitePlugin, makeEnableSourceMapsVitePlugin } from './sourceMaps'; +/** + * Build-time options for the Sentry TanStack Start SDK. + */ +export interface SentryTanstackStartOptions extends BuildTimeOptionsBase { + /** + * If this flag is `true`, the Sentry plugins will automatically instrument TanStack Start middlewares. + * + * This wraps global middlewares (`requestMiddleware` and `functionMiddleware`) in `createStart()` with Sentry + * instrumentation to capture performance data. + * + * Set to `false` to disable automatic middleware instrumentation if you prefer to wrap middlewares manually + * using `wrapMiddlewaresWithSentry`. + * + * @default true + */ + autoInstrumentMiddleware?: boolean; +} + /** * Vite plugins for the Sentry TanStack Start SDK. * @@ -26,14 +45,21 @@ import { makeAddSentryVitePlugin, makeEnableSourceMapsVitePlugin } from './sourc * @param options - Options to configure the Sentry Vite plugins * @returns An array of Vite plugins */ -export function sentryTanstackStart(options: BuildTimeOptionsBase = {}): Plugin[] { - // Only add plugins in production builds +export function sentryTanstackStart(options: SentryTanstackStartOptions = {}): Plugin[] { if (process.env.NODE_ENV === 'development') { return []; } const plugins: Plugin[] = [...makeAddSentryVitePlugin(options)]; + // middleware auto-instrumentation + const autoInstrumentMiddleware = options.autoInstrumentMiddleware !== false; + if (autoInstrumentMiddleware) { + console.log('autoInstrumentMiddleware', autoInstrumentMiddleware); + plugins.push(makeAutoInstrumentMiddlewarePlugin({ enabled: true, debug: options.debug })); + } + + // source maps const sourceMapsDisabled = options.sourcemaps?.disable === true || options.sourcemaps?.disable === 'disable-upload'; if (!sourceMapsDisabled) { plugins.push(...makeEnableSourceMapsVitePlugin(options)); From b7d35ad476015457ce23ee767a95ce021b3e87ce Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Fri, 16 Jan 2026 09:41:24 +0100 Subject: [PATCH 26/31] update unit tests for sentryTanstackStart --- .../src/vite/autoInstrumentMiddleware.ts | 2 +- .../test/vite/sentryTanstackStart.test.ts | 87 +++++++++++++------ 2 files changed, 62 insertions(+), 27 deletions(-) diff --git a/packages/tanstackstart-react/src/vite/autoInstrumentMiddleware.ts b/packages/tanstackstart-react/src/vite/autoInstrumentMiddleware.ts index f01b3ea30a05..b0c767018546 100644 --- a/packages/tanstackstart-react/src/vite/autoInstrumentMiddleware.ts +++ b/packages/tanstackstart-react/src/vite/autoInstrumentMiddleware.ts @@ -73,7 +73,7 @@ export function makeAutoInstrumentMiddlewarePlugin(options: AutoInstrumentMiddle * Returns null if contents contain non-identifier expressions (function calls, etc.) * which cannot be converted to object shorthand. */ -function arrayToObjectShorthand(contents: string): string | null { +export function arrayToObjectShorthand(contents: string): string | null { const items = contents .split(',') .map(s => s.trim()) diff --git a/packages/tanstackstart-react/test/vite/sentryTanstackStart.test.ts b/packages/tanstackstart-react/test/vite/sentryTanstackStart.test.ts index 390b601d8808..d97ad9e16807 100644 --- a/packages/tanstackstart-react/test/vite/sentryTanstackStart.test.ts +++ b/packages/tanstackstart-react/test/vite/sentryTanstackStart.test.ts @@ -21,11 +21,21 @@ const mockEnableSourceMapsPlugin: Plugin = { config: vi.fn(), }; +const mockMiddlewarePlugin: Plugin = { + name: 'sentry-tanstack-middleware-auto-instrument', + apply: 'build', + transform: vi.fn(), +}; + vi.mock('../../src/vite/sourceMaps', () => ({ makeAddSentryVitePlugin: vi.fn(() => [mockSourceMapsConfigPlugin, mockSentryVitePlugin]), makeEnableSourceMapsVitePlugin: vi.fn(() => [mockEnableSourceMapsPlugin]), })); +vi.mock('../../src/vite/autoInstrumentMiddleware', () => ({ + makeAutoInstrumentMiddlewarePlugin: vi.fn(() => mockMiddlewarePlugin), +})); + describe('sentryTanstackStart()', () => { beforeEach(() => { vi.clearAllMocks(); @@ -36,47 +46,72 @@ describe('sentryTanstackStart()', () => { process.env.NODE_ENV = 'production'; }); - it('returns plugins in production mode', () => { - const plugins = sentryTanstackStart({ org: 'test-org' }); + describe('source maps', () => { + it('returns source maps plugins in production mode', () => { + const plugins = sentryTanstackStart({ autoInstrumentMiddleware: false }); - expect(plugins).toEqual([mockSourceMapsConfigPlugin, mockSentryVitePlugin, mockEnableSourceMapsPlugin]); - }); + expect(plugins).toEqual([mockSourceMapsConfigPlugin, mockSentryVitePlugin, mockEnableSourceMapsPlugin]); + }); - it('returns no plugins in development mode', () => { - process.env.NODE_ENV = 'development'; + it('returns no plugins in development mode', () => { + process.env.NODE_ENV = 'development'; - const plugins = sentryTanstackStart({ org: 'test-org' }); + const plugins = sentryTanstackStart({ autoInstrumentMiddleware: false }); - expect(plugins).toEqual([]); - }); + expect(plugins).toEqual([]); + }); - it('returns Sentry Vite plugins but not enable source maps plugin when sourcemaps.disable is true', () => { - const plugins = sentryTanstackStart({ - sourcemaps: { disable: true }, + it('returns Sentry Vite plugins but not enable source maps plugin when sourcemaps.disable is true', () => { + const plugins = sentryTanstackStart({ + autoInstrumentMiddleware: false, + sourcemaps: { disable: true }, + }); + + expect(plugins).toEqual([mockSourceMapsConfigPlugin, mockSentryVitePlugin]); }); - expect(plugins).toEqual([mockSourceMapsConfigPlugin, mockSentryVitePlugin]); - }); + it('returns Sentry Vite plugins but not enable source maps plugin when sourcemaps.disable is "disable-upload"', () => { + const plugins = sentryTanstackStart({ + autoInstrumentMiddleware: false, + sourcemaps: { disable: 'disable-upload' }, + }); - it('returns Sentry Vite plugins but not enable source maps plugin when sourcemaps.disable is "disable-upload"', () => { - const plugins = sentryTanstackStart({ - sourcemaps: { disable: 'disable-upload' }, + expect(plugins).toEqual([mockSourceMapsConfigPlugin, mockSentryVitePlugin]); }); - expect(plugins).toEqual([mockSourceMapsConfigPlugin, mockSentryVitePlugin]); + it('returns Sentry Vite plugins and enable source maps plugin when sourcemaps.disable is false', () => { + const plugins = sentryTanstackStart({ + autoInstrumentMiddleware: false, + sourcemaps: { disable: false }, + }); + + expect(plugins).toEqual([mockSourceMapsConfigPlugin, mockSentryVitePlugin, mockEnableSourceMapsPlugin]); + }); }); - it('returns Sentry Vite plugins and enable source maps plugin when sourcemaps.disable is false', () => { - const plugins = sentryTanstackStart({ - sourcemaps: { disable: false }, + describe('middleware auto-instrumentation', () => { + it('includes middleware plugin by default', () => { + const plugins = sentryTanstackStart({ sourcemaps: { disable: true } }); + + expect(plugins).toEqual([mockSourceMapsConfigPlugin, mockSentryVitePlugin, mockMiddlewarePlugin]); }); - expect(plugins).toEqual([mockSourceMapsConfigPlugin, mockSentryVitePlugin, mockEnableSourceMapsPlugin]); - }); + it('includes middleware plugin when autoInstrumentMiddleware is true', () => { + const plugins = sentryTanstackStart({ + autoInstrumentMiddleware: true, + sourcemaps: { disable: true }, + }); + + expect(plugins).toEqual([mockSourceMapsConfigPlugin, mockSentryVitePlugin, mockMiddlewarePlugin]); + }); - it('returns Sentry Vite Plugins and enable source maps plugin by default when sourcemaps is not specified', () => { - const plugins = sentryTanstackStart({}); + it('does not include middleware plugin when autoInstrumentMiddleware is false', () => { + const plugins = sentryTanstackStart({ + autoInstrumentMiddleware: false, + sourcemaps: { disable: true }, + }); - expect(plugins).toEqual([mockSourceMapsConfigPlugin, mockSentryVitePlugin, mockEnableSourceMapsPlugin]); + expect(plugins).toEqual([mockSourceMapsConfigPlugin, mockSentryVitePlugin]); + }); }); }); From c6d76246d4790d4af435c66dce14dfc22aaa2350 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Fri, 16 Jan 2026 10:25:14 +0100 Subject: [PATCH 27/31] do not transform files with manul middleware wrapping --- .../src/vite/autoInstrumentMiddleware.ts | 7 +- .../vite/autoInstrumentMiddleware.test.ts | 140 ++++++++++++++++++ 2 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 packages/tanstackstart-react/test/vite/autoInstrumentMiddleware.test.ts diff --git a/packages/tanstackstart-react/src/vite/autoInstrumentMiddleware.ts b/packages/tanstackstart-react/src/vite/autoInstrumentMiddleware.ts index b0c767018546..f179b47ecbe8 100644 --- a/packages/tanstackstart-react/src/vite/autoInstrumentMiddleware.ts +++ b/packages/tanstackstart-react/src/vite/autoInstrumentMiddleware.ts @@ -31,11 +31,16 @@ export function makeAutoInstrumentMiddlewarePlugin(options: AutoInstrumentMiddle return null; } - // only wrap requestMiddleware and functionMiddleware in createStart() + // Only wrap requestMiddleware and functionMiddleware in createStart() if (!code.includes('createStart')) { return null; } + // Skip if the user already did some manual wrapping + if (code.includes('wrapMiddlewaresWithSentry')) { + return null; + } + let transformed = code; let needsImport = false; diff --git a/packages/tanstackstart-react/test/vite/autoInstrumentMiddleware.test.ts b/packages/tanstackstart-react/test/vite/autoInstrumentMiddleware.test.ts new file mode 100644 index 000000000000..8ad7ca4d6964 --- /dev/null +++ b/packages/tanstackstart-react/test/vite/autoInstrumentMiddleware.test.ts @@ -0,0 +1,140 @@ +import type { Plugin } from 'vite'; +import { describe, expect, it } from 'vitest'; +import { + arrayToObjectShorthand, + makeAutoInstrumentMiddlewarePlugin, +} from '../../src/vite/autoInstrumentMiddleware'; + +type PluginWithTransform = Plugin & { + transform: (code: string, id: string) => { code: string; map: null } | null; +}; + +describe('makeAutoInstrumentMiddlewarePlugin', () => { + const createStartFile = ` +import { createStart } from '@tanstack/react-start'; +import { authMiddleware, loggingMiddleware } from './middleware'; + +export const startInstance = createStart(() => ({ + requestMiddleware: [authMiddleware], + functionMiddleware: [loggingMiddleware], +})); +`; + + it('instruments a file with createStart and middleware arrays', () => { + const plugin = makeAutoInstrumentMiddlewarePlugin() as PluginWithTransform; + const result = plugin.transform(createStartFile, '/app/start.ts'); + + expect(result).not.toBeNull(); + expect(result!.code).toContain("import { wrapMiddlewaresWithSentry } from '@sentry/tanstackstart-react'"); + expect(result!.code).toContain('requestMiddleware: wrapMiddlewaresWithSentry({ authMiddleware })'); + expect(result!.code).toContain('functionMiddleware: wrapMiddlewaresWithSentry({ loggingMiddleware })'); + }); + + it('does not instrument files without createStart', () => { + const plugin = makeAutoInstrumentMiddlewarePlugin() as PluginWithTransform; + const code = `export const foo = 'bar';`; + const result = plugin.transform(code, '/app/other.ts'); + + expect(result).toBeNull(); + }); + + it('does not instrument non-TS/JS files', () => { + const plugin = makeAutoInstrumentMiddlewarePlugin() as PluginWithTransform; + const result = plugin.transform(createStartFile, '/app/start.css'); + + expect(result).toBeNull(); + }); + + it('does not instrument when enabled is false', () => { + const plugin = makeAutoInstrumentMiddlewarePlugin({ enabled: false }) as PluginWithTransform; + const result = plugin.transform(createStartFile, '/app/start.ts'); + + expect(result).toBeNull(); + }); + + it('wraps single middleware entry correctly', () => { + const plugin = makeAutoInstrumentMiddlewarePlugin() as PluginWithTransform; + const code = ` +import { createStart } from '@tanstack/react-start'; +createStart(() => ({ requestMiddleware: [singleMiddleware] })); +`; + const result = plugin.transform(code, '/app/start.ts'); + + expect(result!.code).toContain('requestMiddleware: wrapMiddlewaresWithSentry({ singleMiddleware })'); + }); + + it('wraps multiple middleware entries correctly', () => { + const plugin = makeAutoInstrumentMiddlewarePlugin() as PluginWithTransform; + const code = ` +import { createStart } from '@tanstack/react-start'; +createStart(() => ({ requestMiddleware: [a, b, c] })); +`; + const result = plugin.transform(code, '/app/start.ts'); + + expect(result!.code).toContain('requestMiddleware: wrapMiddlewaresWithSentry({ a, b, c })'); + }); + + it('does not wrap empty middleware arrays', () => { + const plugin = makeAutoInstrumentMiddlewarePlugin() as PluginWithTransform; + const code = ` +import { createStart } from '@tanstack/react-start'; +createStart(() => ({ requestMiddleware: [] })); +`; + const result = plugin.transform(code, '/app/start.ts'); + + expect(result).toBeNull(); + }); + + it('does not wrap if middleware contains function calls', () => { + const plugin = makeAutoInstrumentMiddlewarePlugin() as PluginWithTransform; + const code = ` +import { createStart } from '@tanstack/react-start'; +createStart(() => ({ requestMiddleware: [getMiddleware()] })); +`; + const result = plugin.transform(code, '/app/start.ts'); + + expect(result).toBeNull(); + }); + + it('does not instrument files that already use wrapMiddlewaresWithSentry', () => { + const plugin = makeAutoInstrumentMiddlewarePlugin() as PluginWithTransform; + const code = ` +import { createStart } from '@tanstack/react-start'; +import { wrapMiddlewaresWithSentry } from '@sentry/tanstackstart-react'; +createStart(() => ({ requestMiddleware: wrapMiddlewaresWithSentry({ myMiddleware }) })); +`; + const result = plugin.transform(code, '/app/start.ts'); + + expect(result).toBeNull(); + }); +}); + +describe('arrayToObjectShorthand', () => { + it('converts single identifier', () => { + expect(arrayToObjectShorthand('foo')).toBe('{ foo }'); + }); + + it('converts multiple identifiers', () => { + expect(arrayToObjectShorthand('foo, bar, baz')).toBe('{ foo, bar, baz }'); + }); + + it('handles whitespace', () => { + expect(arrayToObjectShorthand(' foo , bar ')).toBe('{ foo, bar }'); + }); + + it('returns null for empty string', () => { + expect(arrayToObjectShorthand('')).toBeNull(); + }); + + it('returns null for function calls', () => { + expect(arrayToObjectShorthand('getMiddleware()')).toBeNull(); + }); + + it('returns null for spread syntax', () => { + expect(arrayToObjectShorthand('...middlewares')).toBeNull(); + }); + + it('returns null for mixed valid and invalid', () => { + expect(arrayToObjectShorthand('foo, bar(), baz')).toBeNull(); + }); +}); From 8142b2057ef36e52fc2115adfd97556f01b54d41 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Fri, 16 Jan 2026 10:31:27 +0100 Subject: [PATCH 28/31] clean --- .../src/vite/autoInstrumentMiddleware.ts | 10 ++-------- .../test/vite/autoInstrumentMiddleware.test.ts | 7 ++----- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/packages/tanstackstart-react/src/vite/autoInstrumentMiddleware.ts b/packages/tanstackstart-react/src/vite/autoInstrumentMiddleware.ts index f179b47ecbe8..351e8b27ad7b 100644 --- a/packages/tanstackstart-react/src/vite/autoInstrumentMiddleware.ts +++ b/packages/tanstackstart-react/src/vite/autoInstrumentMiddleware.ts @@ -6,13 +6,8 @@ type AutoInstrumentMiddlewareOptions = { }; /** - * A Vite plugin that automatically instruments TanStack Start middlewares. - * - * Phase 1: Only wraps global middlewares in `createStart()` configuration - * (`requestMiddleware` and `functionMiddleware` arrays). - * - * @param options - Configuration options for the plugin - * @returns A Vite plugin + * A Vite plugin that automatically instruments TanStack Start middlewares + * by wrapping `requestMiddleware` and `functionMiddleware` arrays in `createStart()`. */ export function makeAutoInstrumentMiddlewarePlugin(options: AutoInstrumentMiddlewareOptions = {}): Plugin { const { enabled = true, debug = false } = options; @@ -62,7 +57,6 @@ export function makeAutoInstrumentMiddlewarePlugin(options: AutoInstrumentMiddle if (needsImport) { transformed = `import { wrapMiddlewaresWithSentry } from '@sentry/tanstackstart-react';\n${transformed}`; - console.log(`[Sentry] transformed:`, transformed); return { code: transformed, map: null }; } diff --git a/packages/tanstackstart-react/test/vite/autoInstrumentMiddleware.test.ts b/packages/tanstackstart-react/test/vite/autoInstrumentMiddleware.test.ts index 8ad7ca4d6964..e0fd35a488e1 100644 --- a/packages/tanstackstart-react/test/vite/autoInstrumentMiddleware.test.ts +++ b/packages/tanstackstart-react/test/vite/autoInstrumentMiddleware.test.ts @@ -1,9 +1,6 @@ import type { Plugin } from 'vite'; import { describe, expect, it } from 'vitest'; -import { - arrayToObjectShorthand, - makeAutoInstrumentMiddlewarePlugin, -} from '../../src/vite/autoInstrumentMiddleware'; +import { arrayToObjectShorthand, makeAutoInstrumentMiddlewarePlugin } from '../../src/vite/autoInstrumentMiddleware'; type PluginWithTransform = Plugin & { transform: (code: string, id: string) => { code: string; map: null } | null; @@ -32,7 +29,7 @@ export const startInstance = createStart(() => ({ it('does not instrument files without createStart', () => { const plugin = makeAutoInstrumentMiddlewarePlugin() as PluginWithTransform; - const code = `export const foo = 'bar';`; + const code = "export const foo = 'bar';"; const result = plugin.transform(code, '/app/other.ts'); expect(result).toBeNull(); From 463d7f00448a337fcc59ead027ca55da2395cb32 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Fri, 16 Jan 2026 10:34:59 +0100 Subject: [PATCH 29/31] clean --- .../test-applications/tanstackstart-react/src/middleware.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-packages/e2e-tests/test-applications/tanstackstart-react/src/middleware.ts b/dev-packages/e2e-tests/test-applications/tanstackstart-react/src/middleware.ts index ec41ba331228..780d8a3a2a9d 100644 --- a/dev-packages/e2e-tests/test-applications/tanstackstart-react/src/middleware.ts +++ b/dev-packages/e2e-tests/test-applications/tanstackstart-react/src/middleware.ts @@ -39,7 +39,7 @@ const errorMiddleware = createMiddleware({ type: 'function' }).server(async () = throw new Error('Middleware Error Test'); }); -// Manually wrap middlewares with Sentry (for middlewares that aren't auto-instrumented yet) +// Manually wrap middlewares with Sentry (for middlewares that won't be auto-instrumented) export const [ wrappedServerFnMiddleware, wrappedServerRouteRequestMiddleware, From 1ed23082d542eba0dd5ed5f4be1027ad92245d76 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Fri, 16 Jan 2026 10:53:43 +0100 Subject: [PATCH 30/31] . --- packages/tanstackstart-react/src/vite/sentryTanstackStart.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/tanstackstart-react/src/vite/sentryTanstackStart.ts b/packages/tanstackstart-react/src/vite/sentryTanstackStart.ts index 0057fc734be6..dfea62f2d221 100644 --- a/packages/tanstackstart-react/src/vite/sentryTanstackStart.ts +++ b/packages/tanstackstart-react/src/vite/sentryTanstackStart.ts @@ -55,7 +55,6 @@ export function sentryTanstackStart(options: SentryTanstackStartOptions = {}): P // middleware auto-instrumentation const autoInstrumentMiddleware = options.autoInstrumentMiddleware !== false; if (autoInstrumentMiddleware) { - console.log('autoInstrumentMiddleware', autoInstrumentMiddleware); plugins.push(makeAutoInstrumentMiddlewarePlugin({ enabled: true, debug: options.debug })); } From 2706bc0033f367adf973d8f9b5d6c0c39338ae48 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Fri, 16 Jan 2026 10:59:33 +0100 Subject: [PATCH 31/31] Add changelog entry --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67e3c4e618b5..49fca3a8ef9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,21 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +- **feat(tanstackstart-react): Auto-instrument global middleware in `sentryTanstackStart` Vite plugin** + + The `sentryTanstackStart` Vite plugin now automatically instruments `requestMiddleware` and `functionMiddleware` arrays in `createStart()`. This captures performance data without requiring manual wrapping. + + Auto-instrumentation is enabled by default. To disable it: + + ```ts + // vite.config.ts + sentryTanstackStart({ + org: 'your-org', + project: 'your-project', + autoInstrumentMiddleware: false, + }); + ``` + - **feat(tanstackstart-react): Add `sentryTanstackStart` Vite plugin for source maps upload** You can now configure source maps upload for TanStack Start using the `sentryTanstackStart` Vite plugin: