Skip to content

Commit 2f302d7

Browse files
s1gr1dLms24
andauthored
feat(sveltekit): Respect user-provided source map generation settings (#14886)
Enables `hidden` source maps if source maps are unset. In case they are explicitly disabled or enabled the setting is kept as is. --------- Co-authored-by: Lukas Stracke <[email protected]>
1 parent 1f5edf6 commit 2f302d7

File tree

4 files changed

+258
-40
lines changed

4 files changed

+258
-40
lines changed

packages/solidstart/src/vite/sourceMaps.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export function makeAddSentryVitePlugin(options: SentrySolidStartPluginOptions,
1818
// Only if source maps were previously not set, we update the "filesToDeleteAfterUpload" (as we override the setting with "hidden")
1919
typeof viteConfig.build?.sourcemap === 'undefined'
2020
) {
21-
// This also works for adapters, as the source maps are also copied to e.g. the .vercel folder
21+
// For .output, .vercel, .netlify etc.
2222
updatedFilesToDeleteAfterUpload = ['.*/**/*.map'];
2323

2424
consoleSandbox(() => {

packages/sveltekit/src/vite/sourceMaps.ts

+141-26
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
/* eslint-disable max-lines */
12
import * as child_process from 'child_process';
23
import * as fs from 'fs';
34
import * as path from 'path';
4-
import { escapeStringForRegex, uuid4 } from '@sentry/core';
5+
import { consoleSandbox, escapeStringForRegex, uuid4 } from '@sentry/core';
56
import { getSentryRelease } from '@sentry/node';
67
import type { SentryVitePluginOptions } from '@sentry/vite-plugin';
78
import { sentryVitePlugin } from '@sentry/vite-plugin';
8-
import type { Plugin } from 'vite';
9+
import { type Plugin, type UserConfig, loadConfigFromFile } from 'vite';
910

1011
import MagicString from 'magic-string';
1112
import { 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.
2836
const 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+
335450
function getFiles(dir: string): string[] {
336451
if (!fs.existsSync(dir)) {
337452
return [];

packages/sveltekit/test/vite/sentrySvelteKitPlugins.test.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ describe('sentrySvelteKit()', () => {
4343

4444
expect(plugins).toBeInstanceOf(Array);
4545
// 1 auto instrument plugin + 5 source maps plugins
46-
expect(plugins).toHaveLength(7);
46+
expect(plugins).toHaveLength(8);
4747
});
4848

4949
it('returns the custom sentry source maps upload plugin, unmodified sourcemaps plugins and the auto-instrument plugin by default', async () => {
@@ -56,6 +56,7 @@ describe('sentrySvelteKit()', () => {
5656
'sentry-telemetry-plugin',
5757
'sentry-vite-release-injection-plugin',
5858
'sentry-vite-debug-id-injection-plugin',
59+
'sentry-sveltekit-update-source-map-setting-plugin',
5960
// custom release plugin:
6061
'sentry-sveltekit-release-management-plugin',
6162
// custom source maps plugin:
@@ -86,7 +87,7 @@ describe('sentrySvelteKit()', () => {
8687
it("doesn't return the auto instrument plugin if autoInstrument is `false`", async () => {
8788
const plugins = await getSentrySvelteKitPlugins({ autoInstrument: false });
8889
const pluginNames = plugins.map(plugin => plugin.name);
89-
expect(plugins).toHaveLength(6);
90+
expect(plugins).toHaveLength(7);
9091
expect(pluginNames).not.toContain('sentry-upload-source-maps');
9192
});
9293

0 commit comments

Comments
 (0)