1+ /* eslint-disable max-lines */
12import * as child_process from 'child_process' ;
23import * as fs from 'fs' ;
34import * as path from 'path' ;
4- import { escapeStringForRegex , uuid4 } from '@sentry/core' ;
5+ import { consoleSandbox , escapeStringForRegex , uuid4 } from '@sentry/core' ;
56import { getSentryRelease } from '@sentry/node' ;
67import type { SentryVitePluginOptions } from '@sentry/vite-plugin' ;
78import { sentryVitePlugin } from '@sentry/vite-plugin' ;
8- import type { Plugin } from 'vite' ;
9+ import { type Plugin , type UserConfig , loadConfigFromFile } from 'vite' ;
910
1011import MagicString from 'magic-string' ;
1112import { WRAPPED_MODULE_SUFFIX } from './autoInstrument' ;
@@ -23,6 +24,13 @@ type Sorcery = {
2324 load ( filepath : string ) : Promise < Chain > ;
2425} ;
2526
27+ type GlobalWithSourceMapSetting = typeof globalThis & {
28+ _sentry_sourceMapSetting ?: {
29+ updatedSourceMapSetting ?: boolean | 'inline' | 'hidden' ;
30+ previousSourceMapSetting ?: UserSourceMapSetting ;
31+ } ;
32+ } ;
33+
2634// storing this in the module scope because `makeCustomSentryVitePlugin` is called multiple times
2735// and we only want to generate a uuid once in case we have to fall back to it.
2836const releaseName = detectSentryRelease ( ) ;
@@ -47,7 +55,9 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
4755 const svelteConfig = await loadSvelteConfig ( ) ;
4856
4957 const usedAdapter = options ?. adapter || 'other' ;
50- const outputDir = await getAdapterOutputDir ( svelteConfig , usedAdapter ) ;
58+ const adapterOutputDir = await getAdapterOutputDir ( svelteConfig , usedAdapter ) ;
59+
60+ const globalWithSourceMapSetting = globalThis as GlobalWithSourceMapSetting ;
5161
5262 const defaultPluginOptions : SentryVitePluginOptions = {
5363 release : {
@@ -60,14 +70,58 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
6070 } ,
6171 } ;
6272
73+ // Including all hidden (`.*`) directories by default so that folders like .vercel,
74+ // .netlify, etc are also cleaned up. Additionally, we include the adapter output
75+ // dir which could be a non-hidden directory, like `build` for the Node adapter.
76+ const defaultFileDeletionGlob = [ './.*/**/*.map' , `./${ adapterOutputDir } /**/*.map` ] ;
77+
78+ if ( ! globalWithSourceMapSetting . _sentry_sourceMapSetting ) {
79+ const configFile = await loadConfigFromFile ( { command : 'build' , mode : 'production' } ) ;
80+
81+ if ( configFile ) {
82+ globalWithSourceMapSetting . _sentry_sourceMapSetting = getUpdatedSourceMapSetting ( configFile . config ) ;
83+ } else {
84+ if ( options ?. debug ) {
85+ consoleSandbox ( ( ) => {
86+ // eslint-disable-next-line no-console
87+ console . warn (
88+ '[Sentry] Could not load Vite config with Vite "production" mode. This is needed for Sentry to automatically update source map settings.' ,
89+ ) ;
90+ } ) ;
91+ }
92+ }
93+
94+ if ( options ?. debug && globalWithSourceMapSetting . _sentry_sourceMapSetting ?. previousSourceMapSetting === 'unset' ) {
95+ consoleSandbox ( ( ) => {
96+ // eslint-disable-next-line no-console
97+ console . warn (
98+ `[Sentry] Automatically setting \`sourceMapsUploadOptions.sourcemaps.filesToDeleteAfterUpload: [${ defaultFileDeletionGlob
99+ . map ( file => `"${ file } "` )
100+ . join ( ', ' ) } ]\` to delete generated source maps after they were uploaded to Sentry.`,
101+ ) ;
102+ } ) ;
103+ }
104+ }
105+
106+ const shouldDeleteDefaultSourceMaps =
107+ globalWithSourceMapSetting . _sentry_sourceMapSetting ?. previousSourceMapSetting === 'unset' &&
108+ ! options ?. sourcemaps ?. filesToDeleteAfterUpload ;
109+
63110 const mergedOptions = {
64111 ...defaultPluginOptions ,
65112 ...options ,
66113 release : {
67114 ...defaultPluginOptions . release ,
68115 ...options ?. release ,
69116 } ,
117+ sourcemaps : {
118+ ...options ?. sourcemaps ,
119+ filesToDeleteAfterUpload : shouldDeleteDefaultSourceMaps
120+ ? defaultFileDeletionGlob
121+ : options ?. sourcemaps ?. filesToDeleteAfterUpload ,
122+ } ,
70123 } ;
124+
71125 const { debug } = mergedOptions ;
72126
73127 const sentryPlugins : Plugin [ ] = await sentryVitePlugin ( mergedOptions ) ;
@@ -126,37 +180,51 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
126180 const serverHooksFile = getHooksFileName ( svelteConfig , 'server' ) ;
127181
128182 const globalSentryValues : GlobalSentryValues = {
129- __sentry_sveltekit_output_dir : outputDir ,
183+ __sentry_sveltekit_output_dir : adapterOutputDir ,
130184 } ;
131185
132- const customDebugIdUploadPlugin : Plugin = {
133- name : 'sentry-sveltekit-debug-id-upload -plugin' ,
186+ const sourceMapSettingsPlugin : Plugin = {
187+ name : 'sentry-sveltekit-update-source-map-setting -plugin' ,
134188 apply : 'build' , // only apply this plugin at build time
135- enforce : 'post' , // this needs to be set to post, otherwise we don't pick up the output from the SvelteKit adapter
189+ config : ( config : UserConfig ) => {
190+ const settingKey = 'build.sourcemap' ;
136191
137- // Modify the config to generate source maps
138- config : config => {
139- const sourceMapsPreviouslyNotEnabled = ! config . build ?. sourcemap ;
140- if ( debug && sourceMapsPreviouslyNotEnabled ) {
141- // eslint-disable-next-line no-console
142- console . log ( '[Source Maps Plugin] Enabling source map generation' ) ;
143- if ( ! mergedOptions . sourcemaps ?. filesToDeleteAfterUpload ) {
144- // eslint-disable-next-line no-console
192+ if ( globalWithSourceMapSetting . _sentry_sourceMapSetting ?. previousSourceMapSetting === 'unset' ) {
193+ consoleSandbox ( ( ) => {
194+ // eslint-disable-next-line no-console
195+ console . log ( `[Sentry] Enabled source map generation in the build options with \`${ settingKey } : "hidden"\`.` ) ;
196+ } ) ;
197+
198+ return {
199+ ...config ,
200+ build : { ...config . build , sourcemap : 'hidden' } ,
201+ } ;
202+ } else if ( globalWithSourceMapSetting . _sentry_sourceMapSetting ?. previousSourceMapSetting === 'disabled' ) {
203+ consoleSandbox ( ( ) => {
204+ // eslint-disable-next-line no-console
145205 console . warn (
146- `[Source Maps Plugin] We recommend setting the \`sourceMapsUploadOptions.sourcemaps.filesToDeleteAfterUpload\` option to clean up source maps after uploading.
147- [Source Maps Plugin] Otherwise, source maps might be deployed to production, depending on your configuration` ,
206+ `[Sentry] Parts of source map generation are currently disabled in your Vite configuration (\`${ settingKey } : false\`). This setting is either a default setting or was explicitly set in your configuration. Sentry won't override this setting. Without source maps, code snippets on the Sentry Issues page will remain minified. To show unminified code, enable source maps in \`${ settingKey } \` (e.g. by setting them to \`hidden\`).` ,
148207 ) ;
208+ } ) ;
209+ } else if ( globalWithSourceMapSetting . _sentry_sourceMapSetting ?. previousSourceMapSetting === 'enabled' ) {
210+ if ( mergedOptions ?. debug ) {
211+ consoleSandbox ( ( ) => {
212+ // eslint-disable-next-line no-console
213+ console . log (
214+ `[Sentry] We discovered you enabled source map generation in your Vite configuration (\`${ settingKey } \`). Sentry will keep this source map setting. This will un-minify the code snippet on the Sentry Issue page.` ,
215+ ) ;
216+ } ) ;
149217 }
150218 }
151- return {
152- ...config ,
153- build : {
154- ...config . build ,
155- sourcemap : true ,
156- } ,
157- } ;
219+
220+ return config ;
158221 } ,
222+ } ;
159223
224+ const customDebugIdUploadPlugin : Plugin = {
225+ name : 'sentry-sveltekit-debug-id-upload-plugin' ,
226+ apply : 'build' , // only apply this plugin at build time
227+ enforce : 'post' , // this needs to be set to post, otherwise we don't pick up the output from the SvelteKit adapter
160228 resolveId : ( id , _importer , _ref ) => {
161229 if ( id === VIRTUAL_GLOBAL_VALUES_FILE ) {
162230 return {
@@ -211,7 +279,7 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
211279 return ;
212280 }
213281
214- const outDir = path . resolve ( process . cwd ( ) , outputDir ) ;
282+ const outDir = path . resolve ( process . cwd ( ) , adapterOutputDir ) ;
215283 // eslint-disable-next-line no-console
216284 debug && console . log ( '[Source Maps Plugin] Looking up source maps in' , outDir ) ;
217285
@@ -297,7 +365,7 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
297365 const writeBundleFn = sentryViteFileDeletionPlugin ?. writeBundle ;
298366 if ( typeof writeBundleFn === 'function' ) {
299367 // This is fine though, because the original method doesn't consume any arguments in its `writeBundle` callback.
300- const outDir = path . resolve ( process . cwd ( ) , outputDir ) ;
368+ const outDir = path . resolve ( process . cwd ( ) , adapterOutputDir ) ;
301369 try {
302370 // @ts -expect-error - the writeBundle hook expects two args we can't pass in here (they're only available in `writeBundle`)
303371 await writeBundleFn ( { dir : outDir } ) ;
@@ -326,12 +394,59 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
326394
327395 return [
328396 ...unchangedSentryVitePlugins ,
397+ sourceMapSettingsPlugin ,
329398 customReleaseManagementPlugin ,
330399 customDebugIdUploadPlugin ,
331400 customFileDeletionPlugin ,
332401 ] ;
333402}
334403
404+ /**
405+ * Whether the user enabled (true, 'hidden', 'inline') or disabled (false) source maps
406+ */
407+ export type UserSourceMapSetting = 'enabled' | 'disabled' | 'unset' | undefined ;
408+
409+ /** There are 3 ways to set up source map generation (https://github.com/getsentry/sentry-javascript/issues/13993)
410+ *
411+ * 1. User explicitly disabled source maps
412+ * - keep this setting (emit a warning that errors won't be unminified in Sentry)
413+ * - We won't upload anything
414+ *
415+ * 2. Users enabled source map generation (true, 'hidden', 'inline').
416+ * - keep this setting (don't do anything - like deletion - besides uploading)
417+ *
418+ * 3. Users didn't set source maps generation
419+ * - we enable 'hidden' source maps generation
420+ * - configure `filesToDeleteAfterUpload` to delete all .map files (we emit a log about this)
421+ *
422+ * --> only exported for testing
423+ */
424+ export function getUpdatedSourceMapSetting ( viteConfig : {
425+ build ?: {
426+ sourcemap ?: boolean | 'inline' | 'hidden' ;
427+ } ;
428+ } ) : { updatedSourceMapSetting : boolean | 'inline' | 'hidden' ; previousSourceMapSetting : UserSourceMapSetting } {
429+ let previousSourceMapSetting : UserSourceMapSetting ;
430+ let updatedSourceMapSetting : boolean | 'inline' | 'hidden' | undefined ;
431+
432+ viteConfig . build = viteConfig . build || { } ;
433+
434+ const viteSourceMap = viteConfig . build . sourcemap ;
435+
436+ if ( viteSourceMap === false ) {
437+ previousSourceMapSetting = 'disabled' ;
438+ updatedSourceMapSetting = viteSourceMap ;
439+ } else if ( viteSourceMap && [ 'hidden' , 'inline' , true ] . includes ( viteSourceMap ) ) {
440+ previousSourceMapSetting = 'enabled' ;
441+ updatedSourceMapSetting = viteSourceMap ;
442+ } else {
443+ previousSourceMapSetting = 'unset' ;
444+ updatedSourceMapSetting = 'hidden' ;
445+ }
446+
447+ return { previousSourceMapSetting, updatedSourceMapSetting } ;
448+ }
449+
335450function getFiles ( dir : string ) : string [ ] {
336451 if ( ! fs . existsSync ( dir ) ) {
337452 return [ ] ;
0 commit comments