1
+ /* eslint-disable max-lines */
1
2
import * as child_process from 'child_process' ;
2
3
import * as fs from 'fs' ;
3
4
import * as path from 'path' ;
4
- import { escapeStringForRegex , uuid4 } from '@sentry/core' ;
5
+ import { consoleSandbox , escapeStringForRegex , uuid4 } from '@sentry/core' ;
5
6
import { getSentryRelease } from '@sentry/node' ;
6
7
import type { SentryVitePluginOptions } from '@sentry/vite-plugin' ;
7
8
import { sentryVitePlugin } from '@sentry/vite-plugin' ;
8
- import type { Plugin } from 'vite' ;
9
+ import { type Plugin , type UserConfig , loadConfigFromFile } from 'vite' ;
9
10
10
11
import MagicString from 'magic-string' ;
11
12
import { WRAPPED_MODULE_SUFFIX } from './autoInstrument' ;
@@ -23,6 +24,13 @@ type Sorcery = {
23
24
load ( filepath : string ) : Promise < Chain > ;
24
25
} ;
25
26
27
+ type GlobalWithSourceMapSetting = typeof globalThis & {
28
+ _sentry_sourceMapSetting ?: {
29
+ updatedSourceMapSetting ?: boolean | 'inline' | 'hidden' ;
30
+ previousSourceMapSetting ?: UserSourceMapSetting ;
31
+ } ;
32
+ } ;
33
+
26
34
// storing this in the module scope because `makeCustomSentryVitePlugin` is called multiple times
27
35
// and we only want to generate a uuid once in case we have to fall back to it.
28
36
const releaseName = detectSentryRelease ( ) ;
@@ -47,7 +55,9 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
47
55
const svelteConfig = await loadSvelteConfig ( ) ;
48
56
49
57
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 ;
51
61
52
62
const defaultPluginOptions : SentryVitePluginOptions = {
53
63
release : {
@@ -60,14 +70,58 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
60
70
} ,
61
71
} ;
62
72
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
+
63
110
const mergedOptions = {
64
111
...defaultPluginOptions ,
65
112
...options ,
66
113
release : {
67
114
...defaultPluginOptions . release ,
68
115
...options ?. release ,
69
116
} ,
117
+ sourcemaps : {
118
+ ...options ?. sourcemaps ,
119
+ filesToDeleteAfterUpload : shouldDeleteDefaultSourceMaps
120
+ ? defaultFileDeletionGlob
121
+ : options ?. sourcemaps ?. filesToDeleteAfterUpload ,
122
+ } ,
70
123
} ;
124
+
71
125
const { debug } = mergedOptions ;
72
126
73
127
const sentryPlugins : Plugin [ ] = await sentryVitePlugin ( mergedOptions ) ;
@@ -126,37 +180,51 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
126
180
const serverHooksFile = getHooksFileName ( svelteConfig , 'server' ) ;
127
181
128
182
const globalSentryValues : GlobalSentryValues = {
129
- __sentry_sveltekit_output_dir : outputDir ,
183
+ __sentry_sveltekit_output_dir : adapterOutputDir ,
130
184
} ;
131
185
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' ,
134
188
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' ;
136
191
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
145
205
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\`).` ,
148
207
) ;
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
+ } ) ;
149
217
}
150
218
}
151
- return {
152
- ...config ,
153
- build : {
154
- ...config . build ,
155
- sourcemap : true ,
156
- } ,
157
- } ;
219
+
220
+ return config ;
158
221
} ,
222
+ } ;
159
223
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
160
228
resolveId : ( id , _importer , _ref ) => {
161
229
if ( id === VIRTUAL_GLOBAL_VALUES_FILE ) {
162
230
return {
@@ -211,7 +279,7 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
211
279
return ;
212
280
}
213
281
214
- const outDir = path . resolve ( process . cwd ( ) , outputDir ) ;
282
+ const outDir = path . resolve ( process . cwd ( ) , adapterOutputDir ) ;
215
283
// eslint-disable-next-line no-console
216
284
debug && console . log ( '[Source Maps Plugin] Looking up source maps in' , outDir ) ;
217
285
@@ -297,7 +365,7 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
297
365
const writeBundleFn = sentryViteFileDeletionPlugin ?. writeBundle ;
298
366
if ( typeof writeBundleFn === 'function' ) {
299
367
// 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 ) ;
301
369
try {
302
370
// @ts -expect-error - the writeBundle hook expects two args we can't pass in here (they're only available in `writeBundle`)
303
371
await writeBundleFn ( { dir : outDir } ) ;
@@ -326,12 +394,59 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
326
394
327
395
return [
328
396
...unchangedSentryVitePlugins ,
397
+ sourceMapSettingsPlugin ,
329
398
customReleaseManagementPlugin ,
330
399
customDebugIdUploadPlugin ,
331
400
customFileDeletionPlugin ,
332
401
] ;
333
402
}
334
403
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
+
335
450
function getFiles ( dir : string ) : string [ ] {
336
451
if ( ! fs . existsSync ( dir ) ) {
337
452
return [ ] ;
0 commit comments