-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
feat(tanstackstart-react): Add sentryTanstackStart Vite plugin for Source Map Uploads #18712
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
704a781
6cc5784
b4a0d83
b6ddbff
8c6f0fa
207a96a
53e9099
b9c51e9
6eb1c8b
4dcdabf
9aaabd9
bfa238a
086b9f6
05e08f1
bc0acdd
8e60de8
9bbd0b0
283a545
1e736b4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,3 +3,4 @@ | |
| export * from './config'; | ||
| export * from './server'; | ||
| export * from './common'; | ||
| export * from './vite'; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export { sentryTanstackStart } from './sentryTanstackStart'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| 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 plugins in production builds | ||
| if (process.env.NODE_ENV !== 'development') { | ||
| plugins.push(...makeAddSentryVitePlugin(options)); | ||
|
|
||
| const sourceMapsDisabled = options.sourcemaps?.disable === true || options.sourcemaps?.disable === 'disable-upload'; | ||
| if (!sourceMapsDisabled) { | ||
| plugins.push(...makeEnableSourceMapsVitePlugin(options)); | ||
| } | ||
| } | ||
|
|
||
| return plugins; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,156 @@ | ||
| import type { BuildTimeOptionsBase } from '@sentry/core'; | ||
| import { sentryVitePlugin } from '@sentry/vite-plugin'; | ||
| 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, | ||
| errorHandler, | ||
| headers, | ||
| org, | ||
| project, | ||
| release, | ||
| sentryUrl, | ||
| silent, | ||
| sourcemaps, | ||
| telemetry, | ||
| } = options; | ||
|
|
||
| const configPlugin: Plugin = { | ||
| name: 'sentry-tanstackstart-source-maps-config', | ||
| apply: 'build', | ||
| enforce: 'pre', | ||
| 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.', | ||
| ); | ||
| } | ||
| }, | ||
| }; | ||
|
|
||
| // 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; | ||
nicohrubec marked this conversation as resolved.
Show resolved
Hide resolved
nicohrubec marked this conversation as resolved.
Show resolved
Hide resolved
Comment on lines
+44
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: The plugin unconditionally deletes source maps after upload by default, ignoring the user's Suggested FixThe logic for determining Prompt for AI AgentDid we get this right? 👍 / 👎 to inform future reviews.
Comment on lines
+45
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Source maps are deleted unexpectedly because the logic for Suggested FixUpdate the logic to only apply the default Prompt for AI AgentDid we get this right? 👍 / 👎 to inform future reviews. |
||
|
|
||
| const sentryPlugins = sentryVitePlugin({ | ||
| 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, | ||
| disable: sourcemaps?.disable, | ||
| ignore: sourcemaps?.ignore, | ||
| filesToDeleteAfterUpload, | ||
| }, | ||
| telemetry: telemetry ?? true, | ||
| url: sentryUrl, | ||
| _metaOptions: { | ||
| telemetry: { | ||
| metaFramework: 'tanstackstart-react', | ||
| }, | ||
| }, | ||
| }); | ||
|
|
||
| return [configPlugin, ...sentryPlugins]; | ||
| } | ||
|
|
||
| /** | ||
| * A Sentry plugin for TanStack Start React to enable "hidden" source maps if they are unset. | ||
| */ | ||
| export function makeEnableSourceMapsVitePlugin(options: BuildTimeOptionsBase): 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?: BuildTimeOptionsBase, | ||
| ): boolean | 'inline' | 'hidden' { | ||
| viteConfig.build = viteConfig.build || {}; | ||
|
|
||
| const viteUserSourceMapSetting = viteConfig.build?.sourcemap; | ||
| const settingKey = 'vite.build.sourcemap'; | ||
| const debug = sentryPluginOptions?.debug; | ||
|
|
||
| // Respect user source map setting if it is explicitly set | ||
| if (viteUserSourceMapSetting === false) { | ||
| 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.'); | ||
| } | ||
|
|
||
| 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 \`${viteUserSourceMapSetting.toString()}\`. Sentry will keep this source map setting.`, | ||
| ); | ||
| } | ||
|
|
||
| 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 'hidden'; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| 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' }); | ||
|
|
||
| expect(plugins.length).toBeGreaterThan(0); | ||
| }); | ||
|
|
||
| it('returns no plugins in development mode', () => { | ||
| process.env.NODE_ENV = 'development'; | ||
|
|
||
| const plugins = sentryTanstackStart({ org: 'test-org' }); | ||
|
|
||
| expect(plugins).toHaveLength(0); | ||
| }); | ||
|
|
||
| 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(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 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(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 Sentry Vite plugins and enable source maps plugin when sourcemaps.disable is false', () => { | ||
| const plugins = 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(); | ||
| }); | ||
|
|
||
| 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(); | ||
| }); | ||
| }); |
Uh oh!
There was an error while loading. Please reload this page.