Skip to content

Commit 6c69710

Browse files
authored
fix(sveltekit): Avoid loading vite config to determine source maps setting (#15440)
- read sourcemap generation config in `config` hook of our source maps settings sub plugin - resolve the `filesToDeleteAfterUpload` promise whenever we know what to set it to (using a promise allows us to defer this decision to plugin hook runtime rather than plugin creation time) - adusted tests
1 parent a0be1a5 commit 6c69710

File tree

4 files changed

+135
-144
lines changed

4 files changed

+135
-144
lines changed

packages/sveltekit/package.json

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@
99
"engines": {
1010
"node": ">=18"
1111
},
12-
"files": [
13-
"/build"
14-
],
12+
"files": ["/build"],
1513
"main": "build/cjs/index.server.js",
1614
"module": "build/esm/index.server.js",
1715
"browser": "build/esm/index.client.js",
@@ -44,7 +42,7 @@
4442
"@sentry/node": "9.1.0",
4543
"@sentry/opentelemetry": "9.1.0",
4644
"@sentry/svelte": "9.1.0",
47-
"@sentry/vite-plugin": "2.22.6",
45+
"@sentry/vite-plugin": "3.2.0",
4846
"magic-string": "0.30.7",
4947
"magicast": "0.2.8",
5048
"sorcery": "1.0.0"

packages/sveltekit/src/vite/sourceMaps.ts

Lines changed: 85 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,6 @@ type Sorcery = {
2424
load(filepath: string): Promise<Chain>;
2525
};
2626

27-
type GlobalWithSourceMapSetting = typeof globalThis & {
28-
_sentry_sourceMapSetting?: {
29-
updatedSourceMapSetting?: boolean | 'inline' | 'hidden';
30-
previousSourceMapSetting?: UserSourceMapSetting;
31-
};
32-
};
33-
3427
// storing this in the module scope because `makeCustomSentryVitePlugin` is called multiple times
3528
// and we only want to generate a uuid once in case we have to fall back to it.
3629
const releaseName = detectSentryRelease();
@@ -57,8 +50,6 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
5750
const usedAdapter = options?.adapter || 'other';
5851
const adapterOutputDir = await getAdapterOutputDir(svelteConfig, usedAdapter);
5952

60-
const globalWithSourceMapSetting = globalThis as GlobalWithSourceMapSetting;
61-
6253
const defaultPluginOptions: SentryVitePluginOptions = {
6354
release: {
6455
name: releaseName,
@@ -70,61 +61,8 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
7061
},
7162
};
7263

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-
let configFile: {
80-
path: string;
81-
config: UserConfig;
82-
dependencies: string[];
83-
} | null = null;
84-
85-
try {
86-
// @ts-expect-error - the dynamic import here works fine
87-
const Vite = await import('vite');
88-
configFile = await Vite.loadConfigFromFile({ command: 'build', mode: 'production' });
89-
} catch {
90-
if (options?.debug) {
91-
consoleSandbox(() => {
92-
// eslint-disable-next-line no-console
93-
console.warn(
94-
'[Sentry] Could not import Vite to load your vite config. Please set `build.sourcemap` to `true` or `hidden` to enable source map generation.',
95-
);
96-
});
97-
}
98-
}
99-
100-
if (configFile) {
101-
globalWithSourceMapSetting._sentry_sourceMapSetting = getUpdatedSourceMapSetting(configFile.config);
102-
} else {
103-
if (options?.debug) {
104-
consoleSandbox(() => {
105-
// eslint-disable-next-line no-console
106-
console.warn(
107-
'[Sentry] Could not load Vite config with Vite "production" mode. This is needed for Sentry to automatically update source map settings.',
108-
);
109-
});
110-
}
111-
}
112-
113-
if (options?.debug && globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'unset') {
114-
consoleSandbox(() => {
115-
// eslint-disable-next-line no-console
116-
console.warn(
117-
`[Sentry] Automatically setting \`sourceMapsUploadOptions.sourcemaps.filesToDeleteAfterUpload: [${defaultFileDeletionGlob
118-
.map(file => `"${file}"`)
119-
.join(', ')}]\` to delete generated source maps after they were uploaded to Sentry.`,
120-
);
121-
});
122-
}
123-
}
124-
125-
const shouldDeleteDefaultSourceMaps =
126-
globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'unset' &&
127-
!options?.sourcemaps?.filesToDeleteAfterUpload;
64+
const { promise: filesToDeleteAfterUpload, resolve: resolveFilesToDeleteAfterUpload } =
65+
createFilesToDeleteAfterUploadPromise();
12866

12967
const mergedOptions = {
13068
...defaultPluginOptions,
@@ -135,9 +73,7 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
13573
},
13674
sourcemaps: {
13775
...options?.sourcemaps,
138-
filesToDeleteAfterUpload: shouldDeleteDefaultSourceMaps
139-
? defaultFileDeletionGlob
140-
: options?.sourcemaps?.filesToDeleteAfterUpload,
76+
filesToDeleteAfterUpload,
14177
},
14278
};
14379

@@ -163,6 +99,10 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
16399
console.warn(
164100
'sentry-vite-debug-id-upload-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
165101
);
102+
103+
// resolving filesToDeleteAfterUpload here, because we return the original deletion plugin which awaits the promise
104+
resolveFilesToDeleteAfterUpload(undefined);
105+
166106
return sentryPlugins;
167107
}
168108

@@ -172,6 +112,10 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
172112
console.warn(
173113
'sentry-file-deletion-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
174114
);
115+
116+
// resolving filesToDeleteAfterUpload here, because we return the original deletion plugin which awaits the promise
117+
resolveFilesToDeleteAfterUpload(undefined);
118+
175119
return sentryPlugins;
176120
}
177121

@@ -181,6 +125,10 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
181125
console.warn(
182126
'sentry-release-management-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
183127
);
128+
129+
// resolving filesToDeleteAfterUpload here, because we return the original deletion plugin which awaits the promise
130+
resolveFilesToDeleteAfterUpload(undefined);
131+
184132
return sentryPlugins;
185133
}
186134

@@ -205,37 +153,66 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
205153
const sourceMapSettingsPlugin: Plugin = {
206154
name: 'sentry-sveltekit-update-source-map-setting-plugin',
207155
apply: 'build', // only apply this plugin at build time
208-
config: (config: UserConfig) => {
156+
config: async (config: UserConfig) => {
209157
const settingKey = 'build.sourcemap';
210158

211-
if (globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'unset') {
159+
const { updatedSourceMapSetting, previousSourceMapSetting } = getUpdatedSourceMapSetting(config);
160+
161+
const userProvidedFilesToDeleteAfterUpload = await options?.sourcemaps?.filesToDeleteAfterUpload;
162+
163+
if (previousSourceMapSetting === 'unset') {
212164
consoleSandbox(() => {
213165
// eslint-disable-next-line no-console
214166
console.log(`[Sentry] Enabled source map generation in the build options with \`${settingKey}: "hidden"\`.`);
215167
});
216168

169+
if (userProvidedFilesToDeleteAfterUpload) {
170+
resolveFilesToDeleteAfterUpload(userProvidedFilesToDeleteAfterUpload);
171+
} else {
172+
// Including all hidden (`.*`) directories by default so that folders like .vercel,
173+
// .netlify, etc are also cleaned up. Additionally, we include the adapter output
174+
// dir which could be a non-hidden directory, like `build` for the Node adapter.
175+
const defaultFileDeletionGlob = ['./.*/**/*.map', `./${adapterOutputDir}/**/*.map`];
176+
177+
consoleSandbox(() => {
178+
// eslint-disable-next-line no-console
179+
console.warn(
180+
`[Sentry] Automatically setting \`sourceMapsUploadOptions.sourcemaps.filesToDeleteAfterUpload: [${defaultFileDeletionGlob
181+
.map(file => `"${file}"`)
182+
.join(', ')}]\` to delete generated source maps after they were uploaded to Sentry.`,
183+
);
184+
});
185+
186+
// In case we enabled source maps and users didn't specify a glob patter to delete, we set a default pattern:
187+
resolveFilesToDeleteAfterUpload(defaultFileDeletionGlob);
188+
}
189+
217190
return {
218191
...config,
219-
build: { ...config.build, sourcemap: 'hidden' },
192+
build: { ...config.build, sourcemap: updatedSourceMapSetting },
220193
};
221-
} else if (globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'disabled') {
194+
}
195+
196+
if (previousSourceMapSetting === 'disabled') {
222197
consoleSandbox(() => {
223198
// eslint-disable-next-line no-console
224199
console.warn(
225200
`[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\`).`,
226201
);
227202
});
228-
} else if (globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'enabled') {
203+
} else if (previousSourceMapSetting === 'enabled') {
229204
if (mergedOptions?.debug) {
230205
consoleSandbox(() => {
231206
// eslint-disable-next-line no-console
232207
console.log(
233-
`[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.`,
208+
`[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.`,
234209
);
235210
});
236211
}
237212
}
238213

214+
resolveFilesToDeleteAfterUpload(userProvidedFilesToDeleteAfterUpload);
215+
239216
return config;
240217
},
241218
};
@@ -423,7 +400,7 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
423400
/**
424401
* Whether the user enabled (true, 'hidden', 'inline') or disabled (false) source maps
425402
*/
426-
export type UserSourceMapSetting = 'enabled' | 'disabled' | 'unset' | undefined;
403+
type UserSourceMapSetting = 'enabled' | 'disabled' | 'unset' | undefined;
427404

428405
/** There are 3 ways to set up source map generation (https://github.com/getsentry/sentry-javascript/issues/13993)
429406
*
@@ -445,25 +422,25 @@ export function getUpdatedSourceMapSetting(viteConfig: {
445422
sourcemap?: boolean | 'inline' | 'hidden';
446423
};
447424
}): { updatedSourceMapSetting: boolean | 'inline' | 'hidden'; previousSourceMapSetting: UserSourceMapSetting } {
448-
let previousSourceMapSetting: UserSourceMapSetting;
449-
let updatedSourceMapSetting: boolean | 'inline' | 'hidden' | undefined;
450-
451425
viteConfig.build = viteConfig.build || {};
452426

453-
const viteSourceMap = viteConfig.build.sourcemap;
454-
455-
if (viteSourceMap === false) {
456-
previousSourceMapSetting = 'disabled';
457-
updatedSourceMapSetting = viteSourceMap;
458-
} else if (viteSourceMap && ['hidden', 'inline', true].includes(viteSourceMap)) {
459-
previousSourceMapSetting = 'enabled';
460-
updatedSourceMapSetting = viteSourceMap;
461-
} else {
462-
previousSourceMapSetting = 'unset';
463-
updatedSourceMapSetting = 'hidden';
427+
const originalSourcemapSetting = viteConfig.build.sourcemap;
428+
429+
if (originalSourcemapSetting === false) {
430+
return {
431+
previousSourceMapSetting: 'disabled',
432+
updatedSourceMapSetting: originalSourcemapSetting,
433+
};
434+
}
435+
436+
if (originalSourcemapSetting && ['hidden', 'inline', true].includes(originalSourcemapSetting)) {
437+
return { previousSourceMapSetting: 'enabled', updatedSourceMapSetting: originalSourcemapSetting };
464438
}
465439

466-
return { previousSourceMapSetting, updatedSourceMapSetting };
440+
return {
441+
previousSourceMapSetting: 'unset',
442+
updatedSourceMapSetting: 'hidden',
443+
};
467444
}
468445

469446
function getFiles(dir: string): string[] {
@@ -499,3 +476,22 @@ function detectSentryRelease(): string {
499476

500477
return release;
501478
}
479+
480+
/**
481+
* Creates a deferred promise that can be resolved/rejected by calling the
482+
* `resolve` or `reject` function.
483+
* Inspired by: https://stackoverflow.com/a/69027809
484+
*/
485+
function createFilesToDeleteAfterUploadPromise(): {
486+
promise: Promise<string | string[] | undefined>;
487+
resolve: (value: string | string[] | undefined) => void;
488+
reject: (reason?: unknown) => void;
489+
} {
490+
let resolve!: (value: string | string[] | undefined) => void;
491+
let reject!: (reason?: unknown) => void;
492+
const promise = new Promise<string | string[] | undefined>((res, rej) => {
493+
resolve = res;
494+
reject = rej;
495+
});
496+
return { resolve, reject, promise };
497+
}

packages/sveltekit/test/vite/sourceMaps.test.ts

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { beforeEach, describe, expect, it, vi } from 'vitest';
2-
import { makeCustomSentryVitePlugins } from '../../src/vite/sourceMaps';
2+
import { getUpdatedSourceMapSetting, makeCustomSentryVitePlugins } from '../../src/vite/sourceMaps';
33

44
import type { Plugin } from 'vite';
55

@@ -113,7 +113,7 @@ describe('makeCustomSentryVitePlugins()', () => {
113113
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-update-source-map-setting-plugin');
114114

115115
// @ts-expect-error this function exists!
116-
const sentryConfig = plugin.config(originalConfig);
116+
const sentryConfig = await plugin.config(originalConfig);
117117

118118
expect(sentryConfig).toEqual(originalConfig);
119119
});
@@ -132,7 +132,7 @@ describe('makeCustomSentryVitePlugins()', () => {
132132
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-update-source-map-setting-plugin');
133133

134134
// @ts-expect-error this function exists!
135-
const sentryConfig = plugin.config(originalConfig);
135+
const sentryConfig = await plugin.config(originalConfig);
136136

137137
expect(sentryConfig).toEqual({
138138
build: {
@@ -155,7 +155,7 @@ describe('makeCustomSentryVitePlugins()', () => {
155155

156156
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-update-source-map-setting-plugin');
157157
// @ts-expect-error this function exists!
158-
const sentryConfig = plugin.config(originalConfig);
158+
const sentryConfig = await plugin.config(originalConfig);
159159
expect(sentryConfig).toEqual({
160160
...originalConfig,
161161
build: {
@@ -320,22 +320,23 @@ describe('makeCustomSentryVitePlugins()', () => {
320320
describe('changeViteSourceMapSettings()', () => {
321321
const cases = [
322322
{ sourcemap: false, expectedSourcemap: false, expectedPrevious: 'disabled' },
323-
{ sourcemap: 'hidden', expectedSourcemap: 'hidden', expectedPrevious: 'enabled' },
324-
{ sourcemap: 'inline', expectedSourcemap: 'inline', expectedPrevious: 'enabled' },
323+
{ sourcemap: 'hidden' as const, expectedSourcemap: 'hidden', expectedPrevious: 'enabled' },
324+
{ sourcemap: 'inline' as const, expectedSourcemap: 'inline', expectedPrevious: 'enabled' },
325325
{ sourcemap: true, expectedSourcemap: true, expectedPrevious: 'enabled' },
326326
{ sourcemap: undefined, expectedSourcemap: 'hidden', expectedPrevious: 'unset' },
327327
];
328328

329-
it.each(cases)('handles vite source map settings $1', async ({ sourcemap, expectedSourcemap, expectedPrevious }) => {
330-
const viteConfig = { build: { sourcemap } };
329+
it.each(cases)(
330+
'handles vite source map setting `build.sourcemap: $sourcemap`',
331+
async ({ sourcemap, expectedSourcemap, expectedPrevious }) => {
332+
const viteConfig = { build: { sourcemap } };
331333

332-
const { getUpdatedSourceMapSetting } = await import('../../src/vite/sourceMaps');
334+
const result = getUpdatedSourceMapSetting(viteConfig);
333335

334-
const result = getUpdatedSourceMapSetting(viteConfig);
335-
336-
expect(result).toEqual({
337-
updatedSourceMapSetting: expectedSourcemap,
338-
previousSourceMapSetting: expectedPrevious,
339-
});
340-
});
336+
expect(result).toEqual({
337+
updatedSourceMapSetting: expectedSourcemap,
338+
previousSourceMapSetting: expectedPrevious,
339+
});
340+
},
341+
);
341342
});

0 commit comments

Comments
 (0)