Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions packages/sveltekit/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,9 +9,7 @@
"engines": {
"node": ">=18"
},
"files": [
"/build"
],
"files": ["/build"],
"main": "build/cjs/index.server.js",
"module": "build/esm/index.server.js",
"browser": "build/esm/index.client.js",
Expand DownExpand Up@@ -44,7 +42,7 @@
"@sentry/node": "9.1.0",
"@sentry/opentelemetry": "9.1.0",
"@sentry/svelte": "9.1.0",
"@sentry/vite-plugin": "2.22.6",
"@sentry/vite-plugin": "3.2.0",
"magic-string": "0.30.7",
"magicast": "0.2.8",
"sorcery": "1.0.0"
Expand Down
174 changes: 85 additions & 89 deletions packages/sveltekit/src/vite/sourceMaps.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,13 +24,6 @@ type Sorcery = {
load(filepath: string): Promise<Chain>;
};

type GlobalWithSourceMapSetting = typeof globalThis & {
_sentry_sourceMapSetting?: {
updatedSourceMapSetting?: boolean | 'inline' | 'hidden';
previousSourceMapSetting?: UserSourceMapSetting;
};
};

// storing this in the module scope because `makeCustomSentryVitePlugin` is called multiple times
// and we only want to generate a uuid once in case we have to fall back to it.
const releaseName = detectSentryRelease();
Expand All@@ -57,8 +50,6 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
const usedAdapter = options?.adapter || 'other';
const adapterOutputDir = await getAdapterOutputDir(svelteConfig, usedAdapter);

const globalWithSourceMapSetting = globalThis as GlobalWithSourceMapSetting;

const defaultPluginOptions: SentryVitePluginOptions = {
release: {
name: releaseName,
Expand All@@ -70,61 +61,8 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
},
};

// Including all hidden (`.*`) directories by default so that folders like .vercel,
// .netlify, etc are also cleaned up. Additionally, we include the adapter output
// dir which could be a non-hidden directory, like `build` for the Node adapter.
const defaultFileDeletionGlob = ['./.*/**/*.map', `./${adapterOutputDir}/**/*.map`];

if (!globalWithSourceMapSetting._sentry_sourceMapSetting) {
let configFile: {
path: string;
config: UserConfig;
dependencies: string[];
} | null = null;

try {
// @ts-expect-error - the dynamic import here works fine
const Vite = await import('vite');
configFile = await Vite.loadConfigFromFile({ command: 'build', mode: 'production' });
} catch {
if (options?.debug) {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
'[Sentry] Could not import Vite to load your vite config. Please set `build.sourcemap` to `true` or `hidden` to enable source map generation.',
);
});
}
}

if (configFile) {
globalWithSourceMapSetting._sentry_sourceMapSetting = getUpdatedSourceMapSetting(configFile.config);
} else {
if (options?.debug) {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
'[Sentry] Could not load Vite config with Vite "production" mode. This is needed for Sentry to automatically update source map settings.',
);
});
}
}

if (options?.debug && globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'unset') {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
`[Sentry] Automatically setting \`sourceMapsUploadOptions.sourcemaps.filesToDeleteAfterUpload: [${defaultFileDeletionGlob
.map(file => `"${file}"`)
.join(', ')}]\` to delete generated source maps after they were uploaded to Sentry.`,
);
});
}
}

const shouldDeleteDefaultSourceMaps =
globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'unset' &&
!options?.sourcemaps?.filesToDeleteAfterUpload;
const { promise: filesToDeleteAfterUpload, resolve: resolveFilesToDeleteAfterUpload } =
createFilesToDeleteAfterUploadPromise();

const mergedOptions = {
...defaultPluginOptions,
Expand All@@ -135,9 +73,7 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
},
sourcemaps: {
...options?.sourcemaps,
filesToDeleteAfterUpload: shouldDeleteDefaultSourceMaps
? defaultFileDeletionGlob
: options?.sourcemaps?.filesToDeleteAfterUpload,
filesToDeleteAfterUpload,
},
};

Expand All@@ -163,6 +99,10 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
console.warn(
'sentry-vite-debug-id-upload-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
);

// resolving filesToDeleteAfterUpload here, because we return the original deletion plugin which awaits the promise
resolveFilesToDeleteAfterUpload(undefined);
Comment on lines +103 to +104

@Lms24Lms24Feb 19, 2025

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the ugly part of this PR: We always need to resolve the promise we pass to the vite plugin. So everywhere where we early return, in addition to the expected places.


return sentryPlugins;
}

Expand All@@ -172,6 +112,10 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
console.warn(
'sentry-file-deletion-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
);

// resolving filesToDeleteAfterUpload here, because we return the original deletion plugin which awaits the promise
resolveFilesToDeleteAfterUpload(undefined);

return sentryPlugins;
}

Expand All@@ -181,6 +125,10 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
console.warn(
'sentry-release-management-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
);

// resolving filesToDeleteAfterUpload here, because we return the original deletion plugin which awaits the promise
resolveFilesToDeleteAfterUpload(undefined);

return sentryPlugins;
}

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

if (globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'unset') {
const { updatedSourceMapSetting, previousSourceMapSetting } = getUpdatedSourceMapSetting(config);

const userProvidedFilesToDeleteAfterUpload = await options?.sourcemaps?.filesToDeleteAfterUpload;

if (previousSourceMapSetting === 'unset') {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.log(`[Sentry] Enabled source map generation in the build options with \`${settingKey}: "hidden"\`.`);
});

if (userProvidedFilesToDeleteAfterUpload) {
resolveFilesToDeleteAfterUpload(userProvidedFilesToDeleteAfterUpload);
} else {
// Including all hidden (`.*`) directories by default so that folders like .vercel,
// .netlify, etc are also cleaned up. Additionally, we include the adapter output
// dir which could be a non-hidden directory, like `build` for the Node adapter.
const defaultFileDeletionGlob = ['./.*/**/*.map', `./${adapterOutputDir}/**/*.map`];

consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
`[Sentry] Automatically setting \`sourceMapsUploadOptions.sourcemaps.filesToDeleteAfterUpload: [${defaultFileDeletionGlob
.map(file => `"${file}"`)
.join(', ')}]\` to delete generated source maps after they were uploaded to Sentry.`,
);
});

// In case we enabled source maps and users didn't specify a glob patter to delete, we set a default pattern:
resolveFilesToDeleteAfterUpload(defaultFileDeletionGlob);
}

return {
...config,
build: { ...config.build, sourcemap: 'hidden' },
build: { ...config.build, sourcemap: updatedSourceMapSetting },
};
} else if (globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'disabled') {
}

if (previousSourceMapSetting === 'disabled') {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
`[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\`).`,
);
});
} else if (globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'enabled') {
} else if (previousSourceMapSetting === 'enabled') {
if (mergedOptions?.debug) {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.log(
`[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.`,
`[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.`,
);
});
}
}

resolveFilesToDeleteAfterUpload(userProvidedFilesToDeleteAfterUpload);

return config;
},
};
Expand DownExpand Up@@ -423,7 +400,7 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
/**
* Whether the user enabled (true, 'hidden', 'inline') or disabled (false) source maps
*/
export type UserSourceMapSetting = 'enabled' | 'disabled' | 'unset' | undefined;
type UserSourceMapSetting = 'enabled' | 'disabled' | 'unset' | undefined;

/** There are 3 ways to set up source map generation (https://github.com/getsentry/sentry-javascript/issues/13993)
*
Expand All@@ -445,25 +422,25 @@ export function getUpdatedSourceMapSetting(viteConfig: {
sourcemap?: boolean | 'inline' | 'hidden';
};
}): { updatedSourceMapSetting: boolean | 'inline' | 'hidden'; previousSourceMapSetting: UserSourceMapSetting } {
let previousSourceMapSetting: UserSourceMapSetting;
let updatedSourceMapSetting: boolean | 'inline' | 'hidden' | undefined;

viteConfig.build = viteConfig.build || {};

const viteSourceMap = viteConfig.build.sourcemap;

if (viteSourceMap === false) {
previousSourceMapSetting = 'disabled';
updatedSourceMapSetting = viteSourceMap;
} else if (viteSourceMap && ['hidden', 'inline', true].includes(viteSourceMap)) {
previousSourceMapSetting = 'enabled';
updatedSourceMapSetting = viteSourceMap;
} else {
previousSourceMapSetting = 'unset';
updatedSourceMapSetting = 'hidden';
const originalSourcemapSetting = viteConfig.build.sourcemap;

if (originalSourcemapSetting === false) {
return {
previousSourceMapSetting: 'disabled',
updatedSourceMapSetting: originalSourcemapSetting,
};
}

if (originalSourcemapSetting && ['hidden', 'inline', true].includes(originalSourcemapSetting)) {
return { previousSourceMapSetting: 'enabled', updatedSourceMapSetting: originalSourcemapSetting };
}

return { previousSourceMapSetting, updatedSourceMapSetting };
return {
previousSourceMapSetting: 'unset',
updatedSourceMapSetting: 'hidden',
};
}

function getFiles(dir: string): string[] {
Expand DownExpand Up@@ -499,3 +476,22 @@ function detectSentryRelease(): string {

return release;
}

/**
* Creates a deferred promise that can be resolved/rejected by calling the
* `resolve` or `reject` function.
* Inspired by: https://stackoverflow.com/a/69027809
*/
function createFilesToDeleteAfterUploadPromise(): {
Comment thread
chargome marked this conversation as resolved.
promise: Promise<string | string[] | undefined>;
resolve: (value: string | string[] | undefined) => void;
reject: (reason?: unknown) => void;
} {
let resolve!: (value: string | string[] | undefined) => void;
let reject!: (reason?: unknown) => void;
const promise = new Promise<string | string[] | undefined>((res, rej) => {
resolve = res;
reject = rej;
});
return { resolve, reject, promise };
}
33 changes: 17 additions & 16 deletions packages/sveltekit/test/vite/sourceMaps.test.ts
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { makeCustomSentryVitePlugins } from '../../src/vite/sourceMaps';
import { getUpdatedSourceMapSetting, makeCustomSentryVitePlugins } from '../../src/vite/sourceMaps';

import type { Plugin } from 'vite';

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

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

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

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

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

const plugin = await getSentryViteSubPlugin('sentry-sveltekit-update-source-map-setting-plugin');
// @ts-expect-error this function exists!
const sentryConfig = plugin.config(originalConfig);
const sentryConfig = await plugin.config(originalConfig);
expect(sentryConfig).toEqual({
...originalConfig,
build: {
Expand DownExpand Up@@ -320,22 +320,23 @@ describe('makeCustomSentryVitePlugins()', () => {
describe('changeViteSourceMapSettings()', () => {
const cases = [
{ sourcemap: false, expectedSourcemap: false, expectedPrevious: 'disabled' },
{ sourcemap: 'hidden', expectedSourcemap: 'hidden', expectedPrevious: 'enabled' },
{ sourcemap: 'inline', expectedSourcemap: 'inline', expectedPrevious: 'enabled' },
{ sourcemap: 'hidden' as const, expectedSourcemap: 'hidden', expectedPrevious: 'enabled' },
{ sourcemap: 'inline' as const, expectedSourcemap: 'inline', expectedPrevious: 'enabled' },
{ sourcemap: true, expectedSourcemap: true, expectedPrevious: 'enabled' },
{ sourcemap: undefined, expectedSourcemap: 'hidden', expectedPrevious: 'unset' },
];

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

const { getUpdatedSourceMapSetting } = await import('../../src/vite/sourceMaps');
const result = getUpdatedSourceMapSetting(viteConfig);

const result = getUpdatedSourceMapSetting(viteConfig);

expect(result).toEqual({
updatedSourceMapSetting: expectedSourcemap,
previousSourceMapSetting: expectedPrevious,
});
});
expect(result).toEqual({
updatedSourceMapSetting: expectedSourcemap,
previousSourceMapSetting: expectedPrevious,
});
},
);
});
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions packages/sveltekit/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,9 +9,7 @@
"engines": {
"node": ">=18"
},
"files": [
"/build"
],
"files": ["/build"],
"main": "build/cjs/index.server.js",
"module": "build/esm/index.server.js",
"browser": "build/esm/index.client.js",
Expand DownExpand Up@@ -44,7 +42,7 @@
"@sentry/node": "9.1.0",
"@sentry/opentelemetry": "9.1.0",
"@sentry/svelte": "9.1.0",
"@sentry/vite-plugin": "2.22.6",
"@sentry/vite-plugin": "3.2.0",
"magic-string": "0.30.7",
"magicast": "0.2.8",
"sorcery": "1.0.0"
Expand Down
174 changes: 85 additions & 89 deletions packages/sveltekit/src/vite/sourceMaps.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,13 +24,6 @@ type Sorcery = {
load(filepath: string): Promise<Chain>;
};

type GlobalWithSourceMapSetting = typeof globalThis & {
_sentry_sourceMapSetting?: {
updatedSourceMapSetting?: boolean | 'inline' | 'hidden';
previousSourceMapSetting?: UserSourceMapSetting;
};
};

// storing this in the module scope because `makeCustomSentryVitePlugin` is called multiple times
// and we only want to generate a uuid once in case we have to fall back to it.
const releaseName = detectSentryRelease();
Expand All@@ -57,8 +50,6 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
const usedAdapter = options?.adapter || 'other';
const adapterOutputDir = await getAdapterOutputDir(svelteConfig, usedAdapter);

const globalWithSourceMapSetting = globalThis as GlobalWithSourceMapSetting;

const defaultPluginOptions: SentryVitePluginOptions = {
release: {
name: releaseName,
Expand All@@ -70,61 +61,8 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
},
};

// Including all hidden (`.*`) directories by default so that folders like .vercel,
// .netlify, etc are also cleaned up. Additionally, we include the adapter output
// dir which could be a non-hidden directory, like `build` for the Node adapter.
const defaultFileDeletionGlob = ['./.*/**/*.map', `./${adapterOutputDir}/**/*.map`];

if (!globalWithSourceMapSetting._sentry_sourceMapSetting) {
let configFile: {
path: string;
config: UserConfig;
dependencies: string[];
} | null = null;

try {
// @ts-expect-error - the dynamic import here works fine
const Vite = await import('vite');
configFile = await Vite.loadConfigFromFile({ command: 'build', mode: 'production' });
} catch {
if (options?.debug) {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
'[Sentry] Could not import Vite to load your vite config. Please set `build.sourcemap` to `true` or `hidden` to enable source map generation.',
);
});
}
}

if (configFile) {
globalWithSourceMapSetting._sentry_sourceMapSetting = getUpdatedSourceMapSetting(configFile.config);
} else {
if (options?.debug) {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
'[Sentry] Could not load Vite config with Vite "production" mode. This is needed for Sentry to automatically update source map settings.',
);
});
}
}

if (options?.debug && globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'unset') {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
`[Sentry] Automatically setting \`sourceMapsUploadOptions.sourcemaps.filesToDeleteAfterUpload: [${defaultFileDeletionGlob
.map(file => `"${file}"`)
.join(', ')}]\` to delete generated source maps after they were uploaded to Sentry.`,
);
});
}
}

const shouldDeleteDefaultSourceMaps =
globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'unset' &&
!options?.sourcemaps?.filesToDeleteAfterUpload;
const { promise: filesToDeleteAfterUpload, resolve: resolveFilesToDeleteAfterUpload } =
createFilesToDeleteAfterUploadPromise();

const mergedOptions = {
...defaultPluginOptions,
Expand All@@ -135,9 +73,7 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
},
sourcemaps: {
...options?.sourcemaps,
filesToDeleteAfterUpload: shouldDeleteDefaultSourceMaps
? defaultFileDeletionGlob
: options?.sourcemaps?.filesToDeleteAfterUpload,
filesToDeleteAfterUpload,
},
};

Expand All@@ -163,6 +99,10 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
console.warn(
'sentry-vite-debug-id-upload-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
);

// resolving filesToDeleteAfterUpload here, because we return the original deletion plugin which awaits the promise
resolveFilesToDeleteAfterUpload(undefined);
Comment on lines +103 to +104

@Lms24Lms24Feb 19, 2025

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the ugly part of this PR: We always need to resolve the promise we pass to the vite plugin. So everywhere where we early return, in addition to the expected places.


return sentryPlugins;
}

Expand All@@ -172,6 +112,10 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
console.warn(
'sentry-file-deletion-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
);

// resolving filesToDeleteAfterUpload here, because we return the original deletion plugin which awaits the promise
resolveFilesToDeleteAfterUpload(undefined);

return sentryPlugins;
}

Expand All@@ -181,6 +125,10 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
console.warn(
'sentry-release-management-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
);

// resolving filesToDeleteAfterUpload here, because we return the original deletion plugin which awaits the promise
resolveFilesToDeleteAfterUpload(undefined);

return sentryPlugins;
}

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

if (globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'unset') {
const { updatedSourceMapSetting, previousSourceMapSetting } = getUpdatedSourceMapSetting(config);

const userProvidedFilesToDeleteAfterUpload = await options?.sourcemaps?.filesToDeleteAfterUpload;

if (previousSourceMapSetting === 'unset') {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.log(`[Sentry] Enabled source map generation in the build options with \`${settingKey}: "hidden"\`.`);
});

if (userProvidedFilesToDeleteAfterUpload) {
resolveFilesToDeleteAfterUpload(userProvidedFilesToDeleteAfterUpload);
} else {
// Including all hidden (`.*`) directories by default so that folders like .vercel,
// .netlify, etc are also cleaned up. Additionally, we include the adapter output
// dir which could be a non-hidden directory, like `build` for the Node adapter.
const defaultFileDeletionGlob = ['./.*/**/*.map', `./${adapterOutputDir}/**/*.map`];

consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
`[Sentry] Automatically setting \`sourceMapsUploadOptions.sourcemaps.filesToDeleteAfterUpload: [${defaultFileDeletionGlob
.map(file => `"${file}"`)
.join(', ')}]\` to delete generated source maps after they were uploaded to Sentry.`,
);
});

// In case we enabled source maps and users didn't specify a glob patter to delete, we set a default pattern:
resolveFilesToDeleteAfterUpload(defaultFileDeletionGlob);
}

return {
...config,
build: { ...config.build, sourcemap: 'hidden' },
build: { ...config.build, sourcemap: updatedSourceMapSetting },
};
} else if (globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'disabled') {
}

if (previousSourceMapSetting === 'disabled') {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
`[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\`).`,
);
});
} else if (globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'enabled') {
} else if (previousSourceMapSetting === 'enabled') {
if (mergedOptions?.debug) {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.log(
`[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.`,
`[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.`,
);
});
}
}

resolveFilesToDeleteAfterUpload(userProvidedFilesToDeleteAfterUpload);

return config;
},
};
Expand DownExpand Up@@ -423,7 +400,7 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
/**
* Whether the user enabled (true, 'hidden', 'inline') or disabled (false) source maps
*/
export type UserSourceMapSetting = 'enabled' | 'disabled' | 'unset' | undefined;
type UserSourceMapSetting = 'enabled' | 'disabled' | 'unset' | undefined;

/** There are 3 ways to set up source map generation (https://github.com/getsentry/sentry-javascript/issues/13993)
*
Expand All@@ -445,25 +422,25 @@ export function getUpdatedSourceMapSetting(viteConfig: {
sourcemap?: boolean | 'inline' | 'hidden';
};
}): { updatedSourceMapSetting: boolean | 'inline' | 'hidden'; previousSourceMapSetting: UserSourceMapSetting } {
let previousSourceMapSetting: UserSourceMapSetting;
let updatedSourceMapSetting: boolean | 'inline' | 'hidden' | undefined;

viteConfig.build = viteConfig.build || {};

const viteSourceMap = viteConfig.build.sourcemap;

if (viteSourceMap === false) {
previousSourceMapSetting = 'disabled';
updatedSourceMapSetting = viteSourceMap;
} else if (viteSourceMap && ['hidden', 'inline', true].includes(viteSourceMap)) {
previousSourceMapSetting = 'enabled';
updatedSourceMapSetting = viteSourceMap;
} else {
previousSourceMapSetting = 'unset';
updatedSourceMapSetting = 'hidden';
const originalSourcemapSetting = viteConfig.build.sourcemap;

if (originalSourcemapSetting === false) {
return {
previousSourceMapSetting: 'disabled',
updatedSourceMapSetting: originalSourcemapSetting,
};
}

if (originalSourcemapSetting && ['hidden', 'inline', true].includes(originalSourcemapSetting)) {
return { previousSourceMapSetting: 'enabled', updatedSourceMapSetting: originalSourcemapSetting };
}

return { previousSourceMapSetting, updatedSourceMapSetting };
return {
previousSourceMapSetting: 'unset',
updatedSourceMapSetting: 'hidden',
};
}

function getFiles(dir: string): string[] {
Expand DownExpand Up@@ -499,3 +476,22 @@ function detectSentryRelease(): string {

return release;
}

/**
* Creates a deferred promise that can be resolved/rejected by calling the
* `resolve` or `reject` function.
* Inspired by: https://stackoverflow.com/a/69027809
*/
function createFilesToDeleteAfterUploadPromise(): {
Comment thread
chargome marked this conversation as resolved.
promise: Promise<string | string[] | undefined>;
resolve: (value: string | string[] | undefined) => void;
reject: (reason?: unknown) => void;
} {
let resolve!: (value: string | string[] | undefined) => void;
let reject!: (reason?: unknown) => void;
const promise = new Promise<string | string[] | undefined>((res, rej) => {
resolve = res;
reject = rej;
});
return { resolve, reject, promise };
}
33 changes: 17 additions & 16 deletions packages/sveltekit/test/vite/sourceMaps.test.ts
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { makeCustomSentryVitePlugins } from '../../src/vite/sourceMaps';
import { getUpdatedSourceMapSetting, makeCustomSentryVitePlugins } from '../../src/vite/sourceMaps';

import type { Plugin } from 'vite';

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

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

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

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

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

const plugin = await getSentryViteSubPlugin('sentry-sveltekit-update-source-map-setting-plugin');
// @ts-expect-error this function exists!
const sentryConfig = plugin.config(originalConfig);
const sentryConfig = await plugin.config(originalConfig);
expect(sentryConfig).toEqual({
...originalConfig,
build: {
Expand DownExpand Up@@ -320,22 +320,23 @@ describe('makeCustomSentryVitePlugins()', () => {
describe('changeViteSourceMapSettings()', () => {
const cases = [
{ sourcemap: false, expectedSourcemap: false, expectedPrevious: 'disabled' },
{ sourcemap: 'hidden', expectedSourcemap: 'hidden', expectedPrevious: 'enabled' },
{ sourcemap: 'inline', expectedSourcemap: 'inline', expectedPrevious: 'enabled' },
{ sourcemap: 'hidden' as const, expectedSourcemap: 'hidden', expectedPrevious: 'enabled' },
{ sourcemap: 'inline' as const, expectedSourcemap: 'inline', expectedPrevious: 'enabled' },
{ sourcemap: true, expectedSourcemap: true, expectedPrevious: 'enabled' },
{ sourcemap: undefined, expectedSourcemap: 'hidden', expectedPrevious: 'unset' },
];

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

const { getUpdatedSourceMapSetting } = await import('../../src/vite/sourceMaps');
const result = getUpdatedSourceMapSetting(viteConfig);

const result = getUpdatedSourceMapSetting(viteConfig);

expect(result).toEqual({
updatedSourceMapSetting: expectedSourcemap,
previousSourceMapSetting: expectedPrevious,
});
});
expect(result).toEqual({
updatedSourceMapSetting: expectedSourcemap,
previousSourceMapSetting: expectedPrevious,
});
},
);
});
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions packages/sveltekit/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,9 +9,7 @@
"engines": {
"node": ">=18"
},
"files": [
"/build"
],
"files": ["/build"],
"main": "build/cjs/index.server.js",
"module": "build/esm/index.server.js",
"browser": "build/esm/index.client.js",
Expand DownExpand Up@@ -44,7 +42,7 @@
"@sentry/node": "9.1.0",
"@sentry/opentelemetry": "9.1.0",
"@sentry/svelte": "9.1.0",
"@sentry/vite-plugin": "2.22.6",
"@sentry/vite-plugin": "3.2.0",
"magic-string": "0.30.7",
"magicast": "0.2.8",
"sorcery": "1.0.0"
Expand Down
174 changes: 85 additions & 89 deletions packages/sveltekit/src/vite/sourceMaps.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,13 +24,6 @@ type Sorcery = {
load(filepath: string): Promise<Chain>;
};

type GlobalWithSourceMapSetting = typeof globalThis & {
_sentry_sourceMapSetting?: {
updatedSourceMapSetting?: boolean | 'inline' | 'hidden';
previousSourceMapSetting?: UserSourceMapSetting;
};
};

// storing this in the module scope because `makeCustomSentryVitePlugin` is called multiple times
// and we only want to generate a uuid once in case we have to fall back to it.
const releaseName = detectSentryRelease();
Expand All@@ -57,8 +50,6 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
const usedAdapter = options?.adapter || 'other';
const adapterOutputDir = await getAdapterOutputDir(svelteConfig, usedAdapter);

const globalWithSourceMapSetting = globalThis as GlobalWithSourceMapSetting;

const defaultPluginOptions: SentryVitePluginOptions = {
release: {
name: releaseName,
Expand All@@ -70,61 +61,8 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
},
};

// Including all hidden (`.*`) directories by default so that folders like .vercel,
// .netlify, etc are also cleaned up. Additionally, we include the adapter output
// dir which could be a non-hidden directory, like `build` for the Node adapter.
const defaultFileDeletionGlob = ['./.*/**/*.map', `./${adapterOutputDir}/**/*.map`];

if (!globalWithSourceMapSetting._sentry_sourceMapSetting) {
let configFile: {
path: string;
config: UserConfig;
dependencies: string[];
} | null = null;

try {
// @ts-expect-error - the dynamic import here works fine
const Vite = await import('vite');
configFile = await Vite.loadConfigFromFile({ command: 'build', mode: 'production' });
} catch {
if (options?.debug) {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
'[Sentry] Could not import Vite to load your vite config. Please set `build.sourcemap` to `true` or `hidden` to enable source map generation.',
);
});
}
}

if (configFile) {
globalWithSourceMapSetting._sentry_sourceMapSetting = getUpdatedSourceMapSetting(configFile.config);
} else {
if (options?.debug) {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
'[Sentry] Could not load Vite config with Vite "production" mode. This is needed for Sentry to automatically update source map settings.',
);
});
}
}

if (options?.debug && globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'unset') {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
`[Sentry] Automatically setting \`sourceMapsUploadOptions.sourcemaps.filesToDeleteAfterUpload: [${defaultFileDeletionGlob
.map(file => `"${file}"`)
.join(', ')}]\` to delete generated source maps after they were uploaded to Sentry.`,
);
});
}
}

const shouldDeleteDefaultSourceMaps =
globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'unset' &&
!options?.sourcemaps?.filesToDeleteAfterUpload;
const { promise: filesToDeleteAfterUpload, resolve: resolveFilesToDeleteAfterUpload } =
createFilesToDeleteAfterUploadPromise();

const mergedOptions = {
...defaultPluginOptions,
Expand All@@ -135,9 +73,7 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
},
sourcemaps: {
...options?.sourcemaps,
filesToDeleteAfterUpload: shouldDeleteDefaultSourceMaps
? defaultFileDeletionGlob
: options?.sourcemaps?.filesToDeleteAfterUpload,
filesToDeleteAfterUpload,
},
};

Expand All@@ -163,6 +99,10 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
console.warn(
'sentry-vite-debug-id-upload-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
);

// resolving filesToDeleteAfterUpload here, because we return the original deletion plugin which awaits the promise
resolveFilesToDeleteAfterUpload(undefined);
Comment on lines +103 to +104

@Lms24Lms24Feb 19, 2025

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the ugly part of this PR: We always need to resolve the promise we pass to the vite plugin. So everywhere where we early return, in addition to the expected places.


return sentryPlugins;
}

Expand All@@ -172,6 +112,10 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
console.warn(
'sentry-file-deletion-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
);

// resolving filesToDeleteAfterUpload here, because we return the original deletion plugin which awaits the promise
resolveFilesToDeleteAfterUpload(undefined);

return sentryPlugins;
}

Expand All@@ -181,6 +125,10 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
console.warn(
'sentry-release-management-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
);

// resolving filesToDeleteAfterUpload here, because we return the original deletion plugin which awaits the promise
resolveFilesToDeleteAfterUpload(undefined);

return sentryPlugins;
}

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

if (globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'unset') {
const { updatedSourceMapSetting, previousSourceMapSetting } = getUpdatedSourceMapSetting(config);

const userProvidedFilesToDeleteAfterUpload = await options?.sourcemaps?.filesToDeleteAfterUpload;

if (previousSourceMapSetting === 'unset') {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.log(`[Sentry] Enabled source map generation in the build options with \`${settingKey}: "hidden"\`.`);
});

if (userProvidedFilesToDeleteAfterUpload) {
resolveFilesToDeleteAfterUpload(userProvidedFilesToDeleteAfterUpload);
} else {
// Including all hidden (`.*`) directories by default so that folders like .vercel,
// .netlify, etc are also cleaned up. Additionally, we include the adapter output
// dir which could be a non-hidden directory, like `build` for the Node adapter.
const defaultFileDeletionGlob = ['./.*/**/*.map', `./${adapterOutputDir}/**/*.map`];

consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
`[Sentry] Automatically setting \`sourceMapsUploadOptions.sourcemaps.filesToDeleteAfterUpload: [${defaultFileDeletionGlob
.map(file => `"${file}"`)
.join(', ')}]\` to delete generated source maps after they were uploaded to Sentry.`,
);
});

// In case we enabled source maps and users didn't specify a glob patter to delete, we set a default pattern:
resolveFilesToDeleteAfterUpload(defaultFileDeletionGlob);
}

return {
...config,
build: { ...config.build, sourcemap: 'hidden' },
build: { ...config.build, sourcemap: updatedSourceMapSetting },
};
} else if (globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'disabled') {
}

if (previousSourceMapSetting === 'disabled') {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
`[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\`).`,
);
});
} else if (globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'enabled') {
} else if (previousSourceMapSetting === 'enabled') {
if (mergedOptions?.debug) {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.log(
`[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.`,
`[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.`,
);
});
}
}

resolveFilesToDeleteAfterUpload(userProvidedFilesToDeleteAfterUpload);

return config;
},
};
Expand DownExpand Up@@ -423,7 +400,7 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
/**
* Whether the user enabled (true, 'hidden', 'inline') or disabled (false) source maps
*/
export type UserSourceMapSetting = 'enabled' | 'disabled' | 'unset' | undefined;
type UserSourceMapSetting = 'enabled' | 'disabled' | 'unset' | undefined;

/** There are 3 ways to set up source map generation (https://github.com/getsentry/sentry-javascript/issues/13993)
*
Expand All@@ -445,25 +422,25 @@ export function getUpdatedSourceMapSetting(viteConfig: {
sourcemap?: boolean | 'inline' | 'hidden';
};
}): { updatedSourceMapSetting: boolean | 'inline' | 'hidden'; previousSourceMapSetting: UserSourceMapSetting } {
let previousSourceMapSetting: UserSourceMapSetting;
let updatedSourceMapSetting: boolean | 'inline' | 'hidden' | undefined;

viteConfig.build = viteConfig.build || {};

const viteSourceMap = viteConfig.build.sourcemap;

if (viteSourceMap === false) {
previousSourceMapSetting = 'disabled';
updatedSourceMapSetting = viteSourceMap;
} else if (viteSourceMap && ['hidden', 'inline', true].includes(viteSourceMap)) {
previousSourceMapSetting = 'enabled';
updatedSourceMapSetting = viteSourceMap;
} else {
previousSourceMapSetting = 'unset';
updatedSourceMapSetting = 'hidden';
const originalSourcemapSetting = viteConfig.build.sourcemap;

if (originalSourcemapSetting === false) {
return {
previousSourceMapSetting: 'disabled',
updatedSourceMapSetting: originalSourcemapSetting,
};
}

if (originalSourcemapSetting && ['hidden', 'inline', true].includes(originalSourcemapSetting)) {
return { previousSourceMapSetting: 'enabled', updatedSourceMapSetting: originalSourcemapSetting };
}

return { previousSourceMapSetting, updatedSourceMapSetting };
return {
previousSourceMapSetting: 'unset',
updatedSourceMapSetting: 'hidden',
};
}

function getFiles(dir: string): string[] {
Expand DownExpand Up@@ -499,3 +476,22 @@ function detectSentryRelease(): string {

return release;
}

/**
* Creates a deferred promise that can be resolved/rejected by calling the
* `resolve` or `reject` function.
* Inspired by: https://stackoverflow.com/a/69027809
*/
function createFilesToDeleteAfterUploadPromise(): {
Comment thread
chargome marked this conversation as resolved.
promise: Promise<string | string[] | undefined>;
resolve: (value: string | string[] | undefined) => void;
reject: (reason?: unknown) => void;
} {
let resolve!: (value: string | string[] | undefined) => void;
let reject!: (reason?: unknown) => void;
const promise = new Promise<string | string[] | undefined>((res, rej) => {
resolve = res;
reject = rej;
});
return { resolve, reject, promise };
}
33 changes: 17 additions & 16 deletions packages/sveltekit/test/vite/sourceMaps.test.ts
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { makeCustomSentryVitePlugins } from '../../src/vite/sourceMaps';
import { getUpdatedSourceMapSetting, makeCustomSentryVitePlugins } from '../../src/vite/sourceMaps';

import type { Plugin } from 'vite';

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

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

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

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

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

const plugin = await getSentryViteSubPlugin('sentry-sveltekit-update-source-map-setting-plugin');
// @ts-expect-error this function exists!
const sentryConfig = plugin.config(originalConfig);
const sentryConfig = await plugin.config(originalConfig);
expect(sentryConfig).toEqual({
...originalConfig,
build: {
Expand DownExpand Up@@ -320,22 +320,23 @@ describe('makeCustomSentryVitePlugins()', () => {
describe('changeViteSourceMapSettings()', () => {
const cases = [
{ sourcemap: false, expectedSourcemap: false, expectedPrevious: 'disabled' },
{ sourcemap: 'hidden', expectedSourcemap: 'hidden', expectedPrevious: 'enabled' },
{ sourcemap: 'inline', expectedSourcemap: 'inline', expectedPrevious: 'enabled' },
{ sourcemap: 'hidden' as const, expectedSourcemap: 'hidden', expectedPrevious: 'enabled' },
{ sourcemap: 'inline' as const, expectedSourcemap: 'inline', expectedPrevious: 'enabled' },
{ sourcemap: true, expectedSourcemap: true, expectedPrevious: 'enabled' },
{ sourcemap: undefined, expectedSourcemap: 'hidden', expectedPrevious: 'unset' },
];

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

const { getUpdatedSourceMapSetting } = await import('../../src/vite/sourceMaps');
const result = getUpdatedSourceMapSetting(viteConfig);

const result = getUpdatedSourceMapSetting(viteConfig);

expect(result).toEqual({
updatedSourceMapSetting: expectedSourcemap,
previousSourceMapSetting: expectedPrevious,
});
});
expect(result).toEqual({
updatedSourceMapSetting: expectedSourcemap,
previousSourceMapSetting: expectedPrevious,
});
},
);
});
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions packages/sveltekit/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,9 +9,7 @@
"engines": {
"node": ">=18"
},
"files": [
"/build"
],
"files": ["/build"],
"main": "build/cjs/index.server.js",
"module": "build/esm/index.server.js",
"browser": "build/esm/index.client.js",
Expand DownExpand Up@@ -44,7 +42,7 @@
"@sentry/node": "9.1.0",
"@sentry/opentelemetry": "9.1.0",
"@sentry/svelte": "9.1.0",
"@sentry/vite-plugin": "2.22.6",
"@sentry/vite-plugin": "3.2.0",
"magic-string": "0.30.7",
"magicast": "0.2.8",
"sorcery": "1.0.0"
Expand Down
174 changes: 85 additions & 89 deletions packages/sveltekit/src/vite/sourceMaps.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,13 +24,6 @@ type Sorcery = {
load(filepath: string): Promise<Chain>;
};

type GlobalWithSourceMapSetting = typeof globalThis & {
_sentry_sourceMapSetting?: {
updatedSourceMapSetting?: boolean | 'inline' | 'hidden';
previousSourceMapSetting?: UserSourceMapSetting;
};
};

// storing this in the module scope because `makeCustomSentryVitePlugin` is called multiple times
// and we only want to generate a uuid once in case we have to fall back to it.
const releaseName = detectSentryRelease();
Expand All@@ -57,8 +50,6 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
const usedAdapter = options?.adapter || 'other';
const adapterOutputDir = await getAdapterOutputDir(svelteConfig, usedAdapter);

const globalWithSourceMapSetting = globalThis as GlobalWithSourceMapSetting;

const defaultPluginOptions: SentryVitePluginOptions = {
release: {
name: releaseName,
Expand All@@ -70,61 +61,8 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
},
};

// Including all hidden (`.*`) directories by default so that folders like .vercel,
// .netlify, etc are also cleaned up. Additionally, we include the adapter output
// dir which could be a non-hidden directory, like `build` for the Node adapter.
const defaultFileDeletionGlob = ['./.*/**/*.map', `./${adapterOutputDir}/**/*.map`];

if (!globalWithSourceMapSetting._sentry_sourceMapSetting) {
let configFile: {
path: string;
config: UserConfig;
dependencies: string[];
} | null = null;

try {
// @ts-expect-error - the dynamic import here works fine
const Vite = await import('vite');
configFile = await Vite.loadConfigFromFile({ command: 'build', mode: 'production' });
} catch {
if (options?.debug) {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
'[Sentry] Could not import Vite to load your vite config. Please set `build.sourcemap` to `true` or `hidden` to enable source map generation.',
);
});
}
}

if (configFile) {
globalWithSourceMapSetting._sentry_sourceMapSetting = getUpdatedSourceMapSetting(configFile.config);
} else {
if (options?.debug) {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
'[Sentry] Could not load Vite config with Vite "production" mode. This is needed for Sentry to automatically update source map settings.',
);
});
}
}

if (options?.debug && globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'unset') {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
`[Sentry] Automatically setting \`sourceMapsUploadOptions.sourcemaps.filesToDeleteAfterUpload: [${defaultFileDeletionGlob
.map(file => `"${file}"`)
.join(', ')}]\` to delete generated source maps after they were uploaded to Sentry.`,
);
});
}
}

const shouldDeleteDefaultSourceMaps =
globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'unset' &&
!options?.sourcemaps?.filesToDeleteAfterUpload;
const { promise: filesToDeleteAfterUpload, resolve: resolveFilesToDeleteAfterUpload } =
createFilesToDeleteAfterUploadPromise();

const mergedOptions = {
...defaultPluginOptions,
Expand All@@ -135,9 +73,7 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
},
sourcemaps: {
...options?.sourcemaps,
filesToDeleteAfterUpload: shouldDeleteDefaultSourceMaps
? defaultFileDeletionGlob
: options?.sourcemaps?.filesToDeleteAfterUpload,
filesToDeleteAfterUpload,
},
};

Expand All@@ -163,6 +99,10 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
console.warn(
'sentry-vite-debug-id-upload-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
);

// resolving filesToDeleteAfterUpload here, because we return the original deletion plugin which awaits the promise
resolveFilesToDeleteAfterUpload(undefined);
Comment on lines +103 to +104

@Lms24Lms24Feb 19, 2025

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the ugly part of this PR: We always need to resolve the promise we pass to the vite plugin. So everywhere where we early return, in addition to the expected places.


return sentryPlugins;
}

Expand All@@ -172,6 +112,10 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
console.warn(
'sentry-file-deletion-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
);

// resolving filesToDeleteAfterUpload here, because we return the original deletion plugin which awaits the promise
resolveFilesToDeleteAfterUpload(undefined);

return sentryPlugins;
}

Expand All@@ -181,6 +125,10 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
console.warn(
'sentry-release-management-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
);

// resolving filesToDeleteAfterUpload here, because we return the original deletion plugin which awaits the promise
resolveFilesToDeleteAfterUpload(undefined);

return sentryPlugins;
}

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

if (globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'unset') {
const { updatedSourceMapSetting, previousSourceMapSetting } = getUpdatedSourceMapSetting(config);

const userProvidedFilesToDeleteAfterUpload = await options?.sourcemaps?.filesToDeleteAfterUpload;

if (previousSourceMapSetting === 'unset') {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.log(`[Sentry] Enabled source map generation in the build options with \`${settingKey}: "hidden"\`.`);
});

if (userProvidedFilesToDeleteAfterUpload) {
resolveFilesToDeleteAfterUpload(userProvidedFilesToDeleteAfterUpload);
} else {
// Including all hidden (`.*`) directories by default so that folders like .vercel,
// .netlify, etc are also cleaned up. Additionally, we include the adapter output
// dir which could be a non-hidden directory, like `build` for the Node adapter.
const defaultFileDeletionGlob = ['./.*/**/*.map', `./${adapterOutputDir}/**/*.map`];

consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
`[Sentry] Automatically setting \`sourceMapsUploadOptions.sourcemaps.filesToDeleteAfterUpload: [${defaultFileDeletionGlob
.map(file => `"${file}"`)
.join(', ')}]\` to delete generated source maps after they were uploaded to Sentry.`,
);
});

// In case we enabled source maps and users didn't specify a glob patter to delete, we set a default pattern:
resolveFilesToDeleteAfterUpload(defaultFileDeletionGlob);
}

return {
...config,
build: { ...config.build, sourcemap: 'hidden' },
build: { ...config.build, sourcemap: updatedSourceMapSetting },
};
} else if (globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'disabled') {
}

if (previousSourceMapSetting === 'disabled') {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
`[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\`).`,
);
});
} else if (globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'enabled') {
} else if (previousSourceMapSetting === 'enabled') {
if (mergedOptions?.debug) {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.log(
`[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.`,
`[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.`,
);
});
}
}

resolveFilesToDeleteAfterUpload(userProvidedFilesToDeleteAfterUpload);

return config;
},
};
Expand DownExpand Up@@ -423,7 +400,7 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
/**
* Whether the user enabled (true, 'hidden', 'inline') or disabled (false) source maps
*/
export type UserSourceMapSetting = 'enabled' | 'disabled' | 'unset' | undefined;
type UserSourceMapSetting = 'enabled' | 'disabled' | 'unset' | undefined;

/** There are 3 ways to set up source map generation (https://github.com/getsentry/sentry-javascript/issues/13993)
*
Expand All@@ -445,25 +422,25 @@ export function getUpdatedSourceMapSetting(viteConfig: {
sourcemap?: boolean | 'inline' | 'hidden';
};
}): { updatedSourceMapSetting: boolean | 'inline' | 'hidden'; previousSourceMapSetting: UserSourceMapSetting } {
let previousSourceMapSetting: UserSourceMapSetting;
let updatedSourceMapSetting: boolean | 'inline' | 'hidden' | undefined;

viteConfig.build = viteConfig.build || {};

const viteSourceMap = viteConfig.build.sourcemap;

if (viteSourceMap === false) {
previousSourceMapSetting = 'disabled';
updatedSourceMapSetting = viteSourceMap;
} else if (viteSourceMap && ['hidden', 'inline', true].includes(viteSourceMap)) {
previousSourceMapSetting = 'enabled';
updatedSourceMapSetting = viteSourceMap;
} else {
previousSourceMapSetting = 'unset';
updatedSourceMapSetting = 'hidden';
const originalSourcemapSetting = viteConfig.build.sourcemap;

if (originalSourcemapSetting === false) {
return {
previousSourceMapSetting: 'disabled',
updatedSourceMapSetting: originalSourcemapSetting,
};
}

if (originalSourcemapSetting && ['hidden', 'inline', true].includes(originalSourcemapSetting)) {
return { previousSourceMapSetting: 'enabled', updatedSourceMapSetting: originalSourcemapSetting };
}

return { previousSourceMapSetting, updatedSourceMapSetting };
return {
previousSourceMapSetting: 'unset',
updatedSourceMapSetting: 'hidden',
};
}

function getFiles(dir: string): string[] {
Expand DownExpand Up@@ -499,3 +476,22 @@ function detectSentryRelease(): string {

return release;
}

/**
* Creates a deferred promise that can be resolved/rejected by calling the
* `resolve` or `reject` function.
* Inspired by: https://stackoverflow.com/a/69027809
*/
function createFilesToDeleteAfterUploadPromise(): {
Comment thread
chargome marked this conversation as resolved.
promise: Promise<string | string[] | undefined>;
resolve: (value: string | string[] | undefined) => void;
reject: (reason?: unknown) => void;
} {
let resolve!: (value: string | string[] | undefined) => void;
let reject!: (reason?: unknown) => void;
const promise = new Promise<string | string[] | undefined>((res, rej) => {
resolve = res;
reject = rej;
});
return { resolve, reject, promise };
}
33 changes: 17 additions & 16 deletions packages/sveltekit/test/vite/sourceMaps.test.ts
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { makeCustomSentryVitePlugins } from '../../src/vite/sourceMaps';
import { getUpdatedSourceMapSetting, makeCustomSentryVitePlugins } from '../../src/vite/sourceMaps';

import type { Plugin } from 'vite';

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

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

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

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

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

const plugin = await getSentryViteSubPlugin('sentry-sveltekit-update-source-map-setting-plugin');
// @ts-expect-error this function exists!
const sentryConfig = plugin.config(originalConfig);
const sentryConfig = await plugin.config(originalConfig);
expect(sentryConfig).toEqual({
...originalConfig,
build: {
Expand DownExpand Up@@ -320,22 +320,23 @@ describe('makeCustomSentryVitePlugins()', () => {
describe('changeViteSourceMapSettings()', () => {
const cases = [
{ sourcemap: false, expectedSourcemap: false, expectedPrevious: 'disabled' },
{ sourcemap: 'hidden', expectedSourcemap: 'hidden', expectedPrevious: 'enabled' },
{ sourcemap: 'inline', expectedSourcemap: 'inline', expectedPrevious: 'enabled' },
{ sourcemap: 'hidden' as const, expectedSourcemap: 'hidden', expectedPrevious: 'enabled' },
{ sourcemap: 'inline' as const, expectedSourcemap: 'inline', expectedPrevious: 'enabled' },
{ sourcemap: true, expectedSourcemap: true, expectedPrevious: 'enabled' },
{ sourcemap: undefined, expectedSourcemap: 'hidden', expectedPrevious: 'unset' },
];

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

const { getUpdatedSourceMapSetting } = await import('../../src/vite/sourceMaps');
const result = getUpdatedSourceMapSetting(viteConfig);

const result = getUpdatedSourceMapSetting(viteConfig);

expect(result).toEqual({
updatedSourceMapSetting: expectedSourcemap,
previousSourceMapSetting: expectedPrevious,
});
});
expect(result).toEqual({
updatedSourceMapSetting: expectedSourcemap,
previousSourceMapSetting: expectedPrevious,
});
},
);
});
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions packages/sveltekit/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,9 +9,7 @@
"engines": {
"node": ">=18"
},
"files": [
"/build"
],
"files": ["/build"],
"main": "build/cjs/index.server.js",
"module": "build/esm/index.server.js",
"browser": "build/esm/index.client.js",
Expand DownExpand Up@@ -44,7 +42,7 @@
"@sentry/node": "9.1.0",
"@sentry/opentelemetry": "9.1.0",
"@sentry/svelte": "9.1.0",
"@sentry/vite-plugin": "2.22.6",
"@sentry/vite-plugin": "3.2.0",
"magic-string": "0.30.7",
"magicast": "0.2.8",
"sorcery": "1.0.0"
Expand Down
174 changes: 85 additions & 89 deletions packages/sveltekit/src/vite/sourceMaps.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,13 +24,6 @@ type Sorcery = {
load(filepath: string): Promise<Chain>;
};

type GlobalWithSourceMapSetting = typeof globalThis & {
_sentry_sourceMapSetting?: {
updatedSourceMapSetting?: boolean | 'inline' | 'hidden';
previousSourceMapSetting?: UserSourceMapSetting;
};
};

// storing this in the module scope because `makeCustomSentryVitePlugin` is called multiple times
// and we only want to generate a uuid once in case we have to fall back to it.
const releaseName = detectSentryRelease();
Expand All@@ -57,8 +50,6 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
const usedAdapter = options?.adapter || 'other';
const adapterOutputDir = await getAdapterOutputDir(svelteConfig, usedAdapter);

const globalWithSourceMapSetting = globalThis as GlobalWithSourceMapSetting;

const defaultPluginOptions: SentryVitePluginOptions = {
release: {
name: releaseName,
Expand All@@ -70,61 +61,8 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
},
};

// Including all hidden (`.*`) directories by default so that folders like .vercel,
// .netlify, etc are also cleaned up. Additionally, we include the adapter output
// dir which could be a non-hidden directory, like `build` for the Node adapter.
const defaultFileDeletionGlob = ['./.*/**/*.map', `./${adapterOutputDir}/**/*.map`];

if (!globalWithSourceMapSetting._sentry_sourceMapSetting) {
let configFile: {
path: string;
config: UserConfig;
dependencies: string[];
} | null = null;

try {
// @ts-expect-error - the dynamic import here works fine
const Vite = await import('vite');
configFile = await Vite.loadConfigFromFile({ command: 'build', mode: 'production' });
} catch {
if (options?.debug) {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
'[Sentry] Could not import Vite to load your vite config. Please set `build.sourcemap` to `true` or `hidden` to enable source map generation.',
);
});
}
}

if (configFile) {
globalWithSourceMapSetting._sentry_sourceMapSetting = getUpdatedSourceMapSetting(configFile.config);
} else {
if (options?.debug) {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
'[Sentry] Could not load Vite config with Vite "production" mode. This is needed for Sentry to automatically update source map settings.',
);
});
}
}

if (options?.debug && globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'unset') {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
`[Sentry] Automatically setting \`sourceMapsUploadOptions.sourcemaps.filesToDeleteAfterUpload: [${defaultFileDeletionGlob
.map(file => `"${file}"`)
.join(', ')}]\` to delete generated source maps after they were uploaded to Sentry.`,
);
});
}
}

const shouldDeleteDefaultSourceMaps =
globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'unset' &&
!options?.sourcemaps?.filesToDeleteAfterUpload;
const { promise: filesToDeleteAfterUpload, resolve: resolveFilesToDeleteAfterUpload } =
createFilesToDeleteAfterUploadPromise();

const mergedOptions = {
...defaultPluginOptions,
Expand All@@ -135,9 +73,7 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
},
sourcemaps: {
...options?.sourcemaps,
filesToDeleteAfterUpload: shouldDeleteDefaultSourceMaps
? defaultFileDeletionGlob
: options?.sourcemaps?.filesToDeleteAfterUpload,
filesToDeleteAfterUpload,
},
};

Expand All@@ -163,6 +99,10 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
console.warn(
'sentry-vite-debug-id-upload-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
);

// resolving filesToDeleteAfterUpload here, because we return the original deletion plugin which awaits the promise
resolveFilesToDeleteAfterUpload(undefined);
Comment on lines +103 to +104

@Lms24Lms24Feb 19, 2025

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the ugly part of this PR: We always need to resolve the promise we pass to the vite plugin. So everywhere where we early return, in addition to the expected places.


return sentryPlugins;
}

Expand All@@ -172,6 +112,10 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
console.warn(
'sentry-file-deletion-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
);

// resolving filesToDeleteAfterUpload here, because we return the original deletion plugin which awaits the promise
resolveFilesToDeleteAfterUpload(undefined);

return sentryPlugins;
}

Expand All@@ -181,6 +125,10 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
console.warn(
'sentry-release-management-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
);

// resolving filesToDeleteAfterUpload here, because we return the original deletion plugin which awaits the promise
resolveFilesToDeleteAfterUpload(undefined);

return sentryPlugins;
}

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

if (globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'unset') {
const { updatedSourceMapSetting, previousSourceMapSetting } = getUpdatedSourceMapSetting(config);

const userProvidedFilesToDeleteAfterUpload = await options?.sourcemaps?.filesToDeleteAfterUpload;

if (previousSourceMapSetting === 'unset') {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.log(`[Sentry] Enabled source map generation in the build options with \`${settingKey}: "hidden"\`.`);
});

if (userProvidedFilesToDeleteAfterUpload) {
resolveFilesToDeleteAfterUpload(userProvidedFilesToDeleteAfterUpload);
} else {
// Including all hidden (`.*`) directories by default so that folders like .vercel,
// .netlify, etc are also cleaned up. Additionally, we include the adapter output
// dir which could be a non-hidden directory, like `build` for the Node adapter.
const defaultFileDeletionGlob = ['./.*/**/*.map', `./${adapterOutputDir}/**/*.map`];

consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
`[Sentry] Automatically setting \`sourceMapsUploadOptions.sourcemaps.filesToDeleteAfterUpload: [${defaultFileDeletionGlob
.map(file => `"${file}"`)
.join(', ')}]\` to delete generated source maps after they were uploaded to Sentry.`,
);
});

// In case we enabled source maps and users didn't specify a glob patter to delete, we set a default pattern:
resolveFilesToDeleteAfterUpload(defaultFileDeletionGlob);
}

return {
...config,
build: { ...config.build, sourcemap: 'hidden' },
build: { ...config.build, sourcemap: updatedSourceMapSetting },
};
} else if (globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'disabled') {
}

if (previousSourceMapSetting === 'disabled') {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
`[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\`).`,
);
});
} else if (globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'enabled') {
} else if (previousSourceMapSetting === 'enabled') {
if (mergedOptions?.debug) {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.log(
`[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.`,
`[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.`,
);
});
}
}

resolveFilesToDeleteAfterUpload(userProvidedFilesToDeleteAfterUpload);

return config;
},
};
Expand DownExpand Up@@ -423,7 +400,7 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
/**
* Whether the user enabled (true, 'hidden', 'inline') or disabled (false) source maps
*/
export type UserSourceMapSetting = 'enabled' | 'disabled' | 'unset' | undefined;
type UserSourceMapSetting = 'enabled' | 'disabled' | 'unset' | undefined;

/** There are 3 ways to set up source map generation (https://github.com/getsentry/sentry-javascript/issues/13993)
*
Expand All@@ -445,25 +422,25 @@ export function getUpdatedSourceMapSetting(viteConfig: {
sourcemap?: boolean | 'inline' | 'hidden';
};
}): { updatedSourceMapSetting: boolean | 'inline' | 'hidden'; previousSourceMapSetting: UserSourceMapSetting } {
let previousSourceMapSetting: UserSourceMapSetting;
let updatedSourceMapSetting: boolean | 'inline' | 'hidden' | undefined;

viteConfig.build = viteConfig.build || {};

const viteSourceMap = viteConfig.build.sourcemap;

if (viteSourceMap === false) {
previousSourceMapSetting = 'disabled';
updatedSourceMapSetting = viteSourceMap;
} else if (viteSourceMap && ['hidden', 'inline', true].includes(viteSourceMap)) {
previousSourceMapSetting = 'enabled';
updatedSourceMapSetting = viteSourceMap;
} else {
previousSourceMapSetting = 'unset';
updatedSourceMapSetting = 'hidden';
const originalSourcemapSetting = viteConfig.build.sourcemap;

if (originalSourcemapSetting === false) {
return {
previousSourceMapSetting: 'disabled',
updatedSourceMapSetting: originalSourcemapSetting,
};
}

if (originalSourcemapSetting && ['hidden', 'inline', true].includes(originalSourcemapSetting)) {
return { previousSourceMapSetting: 'enabled', updatedSourceMapSetting: originalSourcemapSetting };
}

return { previousSourceMapSetting, updatedSourceMapSetting };
return {
previousSourceMapSetting: 'unset',
updatedSourceMapSetting: 'hidden',
};
}

function getFiles(dir: string): string[] {
Expand DownExpand Up@@ -499,3 +476,22 @@ function detectSentryRelease(): string {

return release;
}

/**
* Creates a deferred promise that can be resolved/rejected by calling the
* `resolve` or `reject` function.
* Inspired by: https://stackoverflow.com/a/69027809
*/
function createFilesToDeleteAfterUploadPromise(): {
Comment thread
chargome marked this conversation as resolved.
promise: Promise<string | string[] | undefined>;
resolve: (value: string | string[] | undefined) => void;
reject: (reason?: unknown) => void;
} {
let resolve!: (value: string | string[] | undefined) => void;
let reject!: (reason?: unknown) => void;
const promise = new Promise<string | string[] | undefined>((res, rej) => {
resolve = res;
reject = rej;
});
return { resolve, reject, promise };
}
33 changes: 17 additions & 16 deletions packages/sveltekit/test/vite/sourceMaps.test.ts
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { makeCustomSentryVitePlugins } from '../../src/vite/sourceMaps';
import { getUpdatedSourceMapSetting, makeCustomSentryVitePlugins } from '../../src/vite/sourceMaps';

import type { Plugin } from 'vite';

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

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

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

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

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

const plugin = await getSentryViteSubPlugin('sentry-sveltekit-update-source-map-setting-plugin');
// @ts-expect-error this function exists!
const sentryConfig = plugin.config(originalConfig);
const sentryConfig = await plugin.config(originalConfig);
expect(sentryConfig).toEqual({
...originalConfig,
build: {
Expand DownExpand Up@@ -320,22 +320,23 @@ describe('makeCustomSentryVitePlugins()', () => {
describe('changeViteSourceMapSettings()', () => {
const cases = [
{ sourcemap: false, expectedSourcemap: false, expectedPrevious: 'disabled' },
{ sourcemap: 'hidden', expectedSourcemap: 'hidden', expectedPrevious: 'enabled' },
{ sourcemap: 'inline', expectedSourcemap: 'inline', expectedPrevious: 'enabled' },
{ sourcemap: 'hidden' as const, expectedSourcemap: 'hidden', expectedPrevious: 'enabled' },
{ sourcemap: 'inline' as const, expectedSourcemap: 'inline', expectedPrevious: 'enabled' },
{ sourcemap: true, expectedSourcemap: true, expectedPrevious: 'enabled' },
{ sourcemap: undefined, expectedSourcemap: 'hidden', expectedPrevious: 'unset' },
];

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

const { getUpdatedSourceMapSetting } = await import('../../src/vite/sourceMaps');
const result = getUpdatedSourceMapSetting(viteConfig);

const result = getUpdatedSourceMapSetting(viteConfig);

expect(result).toEqual({
updatedSourceMapSetting: expectedSourcemap,
previousSourceMapSetting: expectedPrevious,
});
});
expect(result).toEqual({
updatedSourceMapSetting: expectedSourcemap,
previousSourceMapSetting: expectedPrevious,
});
},
);
});
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions packages/sveltekit/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,9 +9,7 @@
"engines": {
"node": ">=18"
},
"files": [
"/build"
],
"files": ["/build"],
"main": "build/cjs/index.server.js",
"module": "build/esm/index.server.js",
"browser": "build/esm/index.client.js",
Expand DownExpand Up@@ -44,7 +42,7 @@
"@sentry/node": "9.1.0",
"@sentry/opentelemetry": "9.1.0",
"@sentry/svelte": "9.1.0",
"@sentry/vite-plugin": "2.22.6",
"@sentry/vite-plugin": "3.2.0",
"magic-string": "0.30.7",
"magicast": "0.2.8",
"sorcery": "1.0.0"
Expand Down
174 changes: 85 additions & 89 deletions packages/sveltekit/src/vite/sourceMaps.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,13 +24,6 @@ type Sorcery = {
load(filepath: string): Promise<Chain>;
};

type GlobalWithSourceMapSetting = typeof globalThis & {
_sentry_sourceMapSetting?: {
updatedSourceMapSetting?: boolean | 'inline' | 'hidden';
previousSourceMapSetting?: UserSourceMapSetting;
};
};

// storing this in the module scope because `makeCustomSentryVitePlugin` is called multiple times
// and we only want to generate a uuid once in case we have to fall back to it.
const releaseName = detectSentryRelease();
Expand All@@ -57,8 +50,6 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
const usedAdapter = options?.adapter || 'other';
const adapterOutputDir = await getAdapterOutputDir(svelteConfig, usedAdapter);

const globalWithSourceMapSetting = globalThis as GlobalWithSourceMapSetting;

const defaultPluginOptions: SentryVitePluginOptions = {
release: {
name: releaseName,
Expand All@@ -70,61 +61,8 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
},
};

// Including all hidden (`.*`) directories by default so that folders like .vercel,
// .netlify, etc are also cleaned up. Additionally, we include the adapter output
// dir which could be a non-hidden directory, like `build` for the Node adapter.
const defaultFileDeletionGlob = ['./.*/**/*.map', `./${adapterOutputDir}/**/*.map`];

if (!globalWithSourceMapSetting._sentry_sourceMapSetting) {
let configFile: {
path: string;
config: UserConfig;
dependencies: string[];
} | null = null;

try {
// @ts-expect-error - the dynamic import here works fine
const Vite = await import('vite');
configFile = await Vite.loadConfigFromFile({ command: 'build', mode: 'production' });
} catch {
if (options?.debug) {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
'[Sentry] Could not import Vite to load your vite config. Please set `build.sourcemap` to `true` or `hidden` to enable source map generation.',
);
});
}
}

if (configFile) {
globalWithSourceMapSetting._sentry_sourceMapSetting = getUpdatedSourceMapSetting(configFile.config);
} else {
if (options?.debug) {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
'[Sentry] Could not load Vite config with Vite "production" mode. This is needed for Sentry to automatically update source map settings.',
);
});
}
}

if (options?.debug && globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'unset') {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
`[Sentry] Automatically setting \`sourceMapsUploadOptions.sourcemaps.filesToDeleteAfterUpload: [${defaultFileDeletionGlob
.map(file => `"${file}"`)
.join(', ')}]\` to delete generated source maps after they were uploaded to Sentry.`,
);
});
}
}

const shouldDeleteDefaultSourceMaps =
globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'unset' &&
!options?.sourcemaps?.filesToDeleteAfterUpload;
const { promise: filesToDeleteAfterUpload, resolve: resolveFilesToDeleteAfterUpload } =
createFilesToDeleteAfterUploadPromise();

const mergedOptions = {
...defaultPluginOptions,
Expand All@@ -135,9 +73,7 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
},
sourcemaps: {
...options?.sourcemaps,
filesToDeleteAfterUpload: shouldDeleteDefaultSourceMaps
? defaultFileDeletionGlob
: options?.sourcemaps?.filesToDeleteAfterUpload,
filesToDeleteAfterUpload,
},
};

Expand All@@ -163,6 +99,10 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
console.warn(
'sentry-vite-debug-id-upload-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
);

// resolving filesToDeleteAfterUpload here, because we return the original deletion plugin which awaits the promise
resolveFilesToDeleteAfterUpload(undefined);
Comment on lines +103 to +104

@Lms24Lms24Feb 19, 2025

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the ugly part of this PR: We always need to resolve the promise we pass to the vite plugin. So everywhere where we early return, in addition to the expected places.


return sentryPlugins;
}

Expand All@@ -172,6 +112,10 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
console.warn(
'sentry-file-deletion-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
);

// resolving filesToDeleteAfterUpload here, because we return the original deletion plugin which awaits the promise
resolveFilesToDeleteAfterUpload(undefined);

return sentryPlugins;
}

Expand All@@ -181,6 +125,10 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
console.warn(
'sentry-release-management-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
);

// resolving filesToDeleteAfterUpload here, because we return the original deletion plugin which awaits the promise
resolveFilesToDeleteAfterUpload(undefined);

return sentryPlugins;
}

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

if (globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'unset') {
const { updatedSourceMapSetting, previousSourceMapSetting } = getUpdatedSourceMapSetting(config);

const userProvidedFilesToDeleteAfterUpload = await options?.sourcemaps?.filesToDeleteAfterUpload;

if (previousSourceMapSetting === 'unset') {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.log(`[Sentry] Enabled source map generation in the build options with \`${settingKey}: "hidden"\`.`);
});

if (userProvidedFilesToDeleteAfterUpload) {
resolveFilesToDeleteAfterUpload(userProvidedFilesToDeleteAfterUpload);
} else {
// Including all hidden (`.*`) directories by default so that folders like .vercel,
// .netlify, etc are also cleaned up. Additionally, we include the adapter output
// dir which could be a non-hidden directory, like `build` for the Node adapter.
const defaultFileDeletionGlob = ['./.*/**/*.map', `./${adapterOutputDir}/**/*.map`];

consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
`[Sentry] Automatically setting \`sourceMapsUploadOptions.sourcemaps.filesToDeleteAfterUpload: [${defaultFileDeletionGlob
.map(file => `"${file}"`)
.join(', ')}]\` to delete generated source maps after they were uploaded to Sentry.`,
);
});

// In case we enabled source maps and users didn't specify a glob patter to delete, we set a default pattern:
resolveFilesToDeleteAfterUpload(defaultFileDeletionGlob);
}

return {
...config,
build: { ...config.build, sourcemap: 'hidden' },
build: { ...config.build, sourcemap: updatedSourceMapSetting },
};
} else if (globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'disabled') {
}

if (previousSourceMapSetting === 'disabled') {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
`[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\`).`,
);
});
} else if (globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'enabled') {
} else if (previousSourceMapSetting === 'enabled') {
if (mergedOptions?.debug) {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.log(
`[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.`,
`[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.`,
);
});
}
}

resolveFilesToDeleteAfterUpload(userProvidedFilesToDeleteAfterUpload);

return config;
},
};
Expand DownExpand Up@@ -423,7 +400,7 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
/**
* Whether the user enabled (true, 'hidden', 'inline') or disabled (false) source maps
*/
export type UserSourceMapSetting = 'enabled' | 'disabled' | 'unset' | undefined;
type UserSourceMapSetting = 'enabled' | 'disabled' | 'unset' | undefined;

/** There are 3 ways to set up source map generation (https://github.com/getsentry/sentry-javascript/issues/13993)
*
Expand All@@ -445,25 +422,25 @@ export function getUpdatedSourceMapSetting(viteConfig: {
sourcemap?: boolean | 'inline' | 'hidden';
};
}): { updatedSourceMapSetting: boolean | 'inline' | 'hidden'; previousSourceMapSetting: UserSourceMapSetting } {
let previousSourceMapSetting: UserSourceMapSetting;
let updatedSourceMapSetting: boolean | 'inline' | 'hidden' | undefined;

viteConfig.build = viteConfig.build || {};

const viteSourceMap = viteConfig.build.sourcemap;

if (viteSourceMap === false) {
previousSourceMapSetting = 'disabled';
updatedSourceMapSetting = viteSourceMap;
} else if (viteSourceMap && ['hidden', 'inline', true].includes(viteSourceMap)) {
previousSourceMapSetting = 'enabled';
updatedSourceMapSetting = viteSourceMap;
} else {
previousSourceMapSetting = 'unset';
updatedSourceMapSetting = 'hidden';
const originalSourcemapSetting = viteConfig.build.sourcemap;

if (originalSourcemapSetting === false) {
return {
previousSourceMapSetting: 'disabled',
updatedSourceMapSetting: originalSourcemapSetting,
};
}

if (originalSourcemapSetting && ['hidden', 'inline', true].includes(originalSourcemapSetting)) {
return { previousSourceMapSetting: 'enabled', updatedSourceMapSetting: originalSourcemapSetting };
}

return { previousSourceMapSetting, updatedSourceMapSetting };
return {
previousSourceMapSetting: 'unset',
updatedSourceMapSetting: 'hidden',
};
}

function getFiles(dir: string): string[] {
Expand DownExpand Up@@ -499,3 +476,22 @@ function detectSentryRelease(): string {

return release;
}

/**
* Creates a deferred promise that can be resolved/rejected by calling the
* `resolve` or `reject` function.
* Inspired by: https://stackoverflow.com/a/69027809
*/
function createFilesToDeleteAfterUploadPromise(): {
Comment thread
chargome marked this conversation as resolved.
promise: Promise<string | string[] | undefined>;
resolve: (value: string | string[] | undefined) => void;
reject: (reason?: unknown) => void;
} {
let resolve!: (value: string | string[] | undefined) => void;
let reject!: (reason?: unknown) => void;
const promise = new Promise<string | string[] | undefined>((res, rej) => {
resolve = res;
reject = rej;
});
return { resolve, reject, promise };
}
33 changes: 17 additions & 16 deletions packages/sveltekit/test/vite/sourceMaps.test.ts
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { makeCustomSentryVitePlugins } from '../../src/vite/sourceMaps';
import { getUpdatedSourceMapSetting, makeCustomSentryVitePlugins } from '../../src/vite/sourceMaps';

import type { Plugin } from 'vite';

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

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

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

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

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

const plugin = await getSentryViteSubPlugin('sentry-sveltekit-update-source-map-setting-plugin');
// @ts-expect-error this function exists!
const sentryConfig = plugin.config(originalConfig);
const sentryConfig = await plugin.config(originalConfig);
expect(sentryConfig).toEqual({
...originalConfig,
build: {
Expand DownExpand Up@@ -320,22 +320,23 @@ describe('makeCustomSentryVitePlugins()', () => {
describe('changeViteSourceMapSettings()', () => {
const cases = [
{ sourcemap: false, expectedSourcemap: false, expectedPrevious: 'disabled' },
{ sourcemap: 'hidden', expectedSourcemap: 'hidden', expectedPrevious: 'enabled' },
{ sourcemap: 'inline', expectedSourcemap: 'inline', expectedPrevious: 'enabled' },
{ sourcemap: 'hidden' as const, expectedSourcemap: 'hidden', expectedPrevious: 'enabled' },
{ sourcemap: 'inline' as const, expectedSourcemap: 'inline', expectedPrevious: 'enabled' },
{ sourcemap: true, expectedSourcemap: true, expectedPrevious: 'enabled' },
{ sourcemap: undefined, expectedSourcemap: 'hidden', expectedPrevious: 'unset' },
];

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

const { getUpdatedSourceMapSetting } = await import('../../src/vite/sourceMaps');
const result = getUpdatedSourceMapSetting(viteConfig);

const result = getUpdatedSourceMapSetting(viteConfig);

expect(result).toEqual({
updatedSourceMapSetting: expectedSourcemap,
previousSourceMapSetting: expectedPrevious,
});
});
expect(result).toEqual({
updatedSourceMapSetting: expectedSourcemap,
previousSourceMapSetting: expectedPrevious,
});
},
);
});
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions packages/sveltekit/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,9 +9,7 @@
"engines": {
"node": ">=18"
},
"files": [
"/build"
],
"files": ["/build"],
"main": "build/cjs/index.server.js",
"module": "build/esm/index.server.js",
"browser": "build/esm/index.client.js",
Expand DownExpand Up@@ -44,7 +42,7 @@
"@sentry/node": "9.1.0",
"@sentry/opentelemetry": "9.1.0",
"@sentry/svelte": "9.1.0",
"@sentry/vite-plugin": "2.22.6",
"@sentry/vite-plugin": "3.2.0",
"magic-string": "0.30.7",
"magicast": "0.2.8",
"sorcery": "1.0.0"
Expand Down
174 changes: 85 additions & 89 deletions packages/sveltekit/src/vite/sourceMaps.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,13 +24,6 @@ type Sorcery = {
load(filepath: string): Promise<Chain>;
};

type GlobalWithSourceMapSetting = typeof globalThis & {
_sentry_sourceMapSetting?: {
updatedSourceMapSetting?: boolean | 'inline' | 'hidden';
previousSourceMapSetting?: UserSourceMapSetting;
};
};

// storing this in the module scope because `makeCustomSentryVitePlugin` is called multiple times
// and we only want to generate a uuid once in case we have to fall back to it.
const releaseName = detectSentryRelease();
Expand All@@ -57,8 +50,6 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
const usedAdapter = options?.adapter || 'other';
const adapterOutputDir = await getAdapterOutputDir(svelteConfig, usedAdapter);

const globalWithSourceMapSetting = globalThis as GlobalWithSourceMapSetting;

const defaultPluginOptions: SentryVitePluginOptions = {
release: {
name: releaseName,
Expand All@@ -70,61 +61,8 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
},
};

// Including all hidden (`.*`) directories by default so that folders like .vercel,
// .netlify, etc are also cleaned up. Additionally, we include the adapter output
// dir which could be a non-hidden directory, like `build` for the Node adapter.
const defaultFileDeletionGlob = ['./.*/**/*.map', `./${adapterOutputDir}/**/*.map`];

if (!globalWithSourceMapSetting._sentry_sourceMapSetting) {
let configFile: {
path: string;
config: UserConfig;
dependencies: string[];
} | null = null;

try {
// @ts-expect-error - the dynamic import here works fine
const Vite = await import('vite');
configFile = await Vite.loadConfigFromFile({ command: 'build', mode: 'production' });
} catch {
if (options?.debug) {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
'[Sentry] Could not import Vite to load your vite config. Please set `build.sourcemap` to `true` or `hidden` to enable source map generation.',
);
});
}
}

if (configFile) {
globalWithSourceMapSetting._sentry_sourceMapSetting = getUpdatedSourceMapSetting(configFile.config);
} else {
if (options?.debug) {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
'[Sentry] Could not load Vite config with Vite "production" mode. This is needed for Sentry to automatically update source map settings.',
);
});
}
}

if (options?.debug && globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'unset') {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
`[Sentry] Automatically setting \`sourceMapsUploadOptions.sourcemaps.filesToDeleteAfterUpload: [${defaultFileDeletionGlob
.map(file => `"${file}"`)
.join(', ')}]\` to delete generated source maps after they were uploaded to Sentry.`,
);
});
}
}

const shouldDeleteDefaultSourceMaps =
globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'unset' &&
!options?.sourcemaps?.filesToDeleteAfterUpload;
const { promise: filesToDeleteAfterUpload, resolve: resolveFilesToDeleteAfterUpload } =
createFilesToDeleteAfterUploadPromise();

const mergedOptions = {
...defaultPluginOptions,
Expand All@@ -135,9 +73,7 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
},
sourcemaps: {
...options?.sourcemaps,
filesToDeleteAfterUpload: shouldDeleteDefaultSourceMaps
? defaultFileDeletionGlob
: options?.sourcemaps?.filesToDeleteAfterUpload,
filesToDeleteAfterUpload,
},
};

Expand All@@ -163,6 +99,10 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
console.warn(
'sentry-vite-debug-id-upload-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
);

// resolving filesToDeleteAfterUpload here, because we return the original deletion plugin which awaits the promise
resolveFilesToDeleteAfterUpload(undefined);
Comment on lines +103 to +104

@Lms24Lms24Feb 19, 2025

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the ugly part of this PR: We always need to resolve the promise we pass to the vite plugin. So everywhere where we early return, in addition to the expected places.


return sentryPlugins;
}

Expand All@@ -172,6 +112,10 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
console.warn(
'sentry-file-deletion-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
);

// resolving filesToDeleteAfterUpload here, because we return the original deletion plugin which awaits the promise
resolveFilesToDeleteAfterUpload(undefined);

return sentryPlugins;
}

Expand All@@ -181,6 +125,10 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
console.warn(
'sentry-release-management-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
);

// resolving filesToDeleteAfterUpload here, because we return the original deletion plugin which awaits the promise
resolveFilesToDeleteAfterUpload(undefined);

return sentryPlugins;
}

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

if (globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'unset') {
const { updatedSourceMapSetting, previousSourceMapSetting } = getUpdatedSourceMapSetting(config);

const userProvidedFilesToDeleteAfterUpload = await options?.sourcemaps?.filesToDeleteAfterUpload;

if (previousSourceMapSetting === 'unset') {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.log(`[Sentry] Enabled source map generation in the build options with \`${settingKey}: "hidden"\`.`);
});

if (userProvidedFilesToDeleteAfterUpload) {
resolveFilesToDeleteAfterUpload(userProvidedFilesToDeleteAfterUpload);
} else {
// Including all hidden (`.*`) directories by default so that folders like .vercel,
// .netlify, etc are also cleaned up. Additionally, we include the adapter output
// dir which could be a non-hidden directory, like `build` for the Node adapter.
const defaultFileDeletionGlob = ['./.*/**/*.map', `./${adapterOutputDir}/**/*.map`];

consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
`[Sentry] Automatically setting \`sourceMapsUploadOptions.sourcemaps.filesToDeleteAfterUpload: [${defaultFileDeletionGlob
.map(file => `"${file}"`)
.join(', ')}]\` to delete generated source maps after they were uploaded to Sentry.`,
);
});

// In case we enabled source maps and users didn't specify a glob patter to delete, we set a default pattern:
resolveFilesToDeleteAfterUpload(defaultFileDeletionGlob);
}

return {
...config,
build: { ...config.build, sourcemap: 'hidden' },
build: { ...config.build, sourcemap: updatedSourceMapSetting },
};
} else if (globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'disabled') {
}

if (previousSourceMapSetting === 'disabled') {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
`[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\`).`,
);
});
} else if (globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'enabled') {
} else if (previousSourceMapSetting === 'enabled') {
if (mergedOptions?.debug) {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.log(
`[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.`,
`[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.`,
);
});
}
}

resolveFilesToDeleteAfterUpload(userProvidedFilesToDeleteAfterUpload);

return config;
},
};
Expand DownExpand Up@@ -423,7 +400,7 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
/**
* Whether the user enabled (true, 'hidden', 'inline') or disabled (false) source maps
*/
export type UserSourceMapSetting = 'enabled' | 'disabled' | 'unset' | undefined;
type UserSourceMapSetting = 'enabled' | 'disabled' | 'unset' | undefined;

/** There are 3 ways to set up source map generation (https://github.com/getsentry/sentry-javascript/issues/13993)
*
Expand All@@ -445,25 +422,25 @@ export function getUpdatedSourceMapSetting(viteConfig: {
sourcemap?: boolean | 'inline' | 'hidden';
};
}): { updatedSourceMapSetting: boolean | 'inline' | 'hidden'; previousSourceMapSetting: UserSourceMapSetting } {
let previousSourceMapSetting: UserSourceMapSetting;
let updatedSourceMapSetting: boolean | 'inline' | 'hidden' | undefined;

viteConfig.build = viteConfig.build || {};

const viteSourceMap = viteConfig.build.sourcemap;

if (viteSourceMap === false) {
previousSourceMapSetting = 'disabled';
updatedSourceMapSetting = viteSourceMap;
} else if (viteSourceMap && ['hidden', 'inline', true].includes(viteSourceMap)) {
previousSourceMapSetting = 'enabled';
updatedSourceMapSetting = viteSourceMap;
} else {
previousSourceMapSetting = 'unset';
updatedSourceMapSetting = 'hidden';
const originalSourcemapSetting = viteConfig.build.sourcemap;

if (originalSourcemapSetting === false) {
return {
previousSourceMapSetting: 'disabled',
updatedSourceMapSetting: originalSourcemapSetting,
};
}

if (originalSourcemapSetting && ['hidden', 'inline', true].includes(originalSourcemapSetting)) {
return { previousSourceMapSetting: 'enabled', updatedSourceMapSetting: originalSourcemapSetting };
}

return { previousSourceMapSetting, updatedSourceMapSetting };
return {
previousSourceMapSetting: 'unset',
updatedSourceMapSetting: 'hidden',
};
}

function getFiles(dir: string): string[] {
Expand DownExpand Up@@ -499,3 +476,22 @@ function detectSentryRelease(): string {

return release;
}

/**
* Creates a deferred promise that can be resolved/rejected by calling the
* `resolve` or `reject` function.
* Inspired by: https://stackoverflow.com/a/69027809
*/
function createFilesToDeleteAfterUploadPromise(): {
Comment thread
chargome marked this conversation as resolved.
promise: Promise<string | string[] | undefined>;
resolve: (value: string | string[] | undefined) => void;
reject: (reason?: unknown) => void;
} {
let resolve!: (value: string | string[] | undefined) => void;
let reject!: (reason?: unknown) => void;
const promise = new Promise<string | string[] | undefined>((res, rej) => {
resolve = res;
reject = rej;
});
return { resolve, reject, promise };
}
33 changes: 17 additions & 16 deletions packages/sveltekit/test/vite/sourceMaps.test.ts
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { makeCustomSentryVitePlugins } from '../../src/vite/sourceMaps';
import { getUpdatedSourceMapSetting, makeCustomSentryVitePlugins } from '../../src/vite/sourceMaps';

import type { Plugin } from 'vite';

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

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

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

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

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

const plugin = await getSentryViteSubPlugin('sentry-sveltekit-update-source-map-setting-plugin');
// @ts-expect-error this function exists!
const sentryConfig = plugin.config(originalConfig);
const sentryConfig = await plugin.config(originalConfig);
expect(sentryConfig).toEqual({
...originalConfig,
build: {
Expand DownExpand Up@@ -320,22 +320,23 @@ describe('makeCustomSentryVitePlugins()', () => {
describe('changeViteSourceMapSettings()', () => {
const cases = [
{ sourcemap: false, expectedSourcemap: false, expectedPrevious: 'disabled' },
{ sourcemap: 'hidden', expectedSourcemap: 'hidden', expectedPrevious: 'enabled' },
{ sourcemap: 'inline', expectedSourcemap: 'inline', expectedPrevious: 'enabled' },
{ sourcemap: 'hidden' as const, expectedSourcemap: 'hidden', expectedPrevious: 'enabled' },
{ sourcemap: 'inline' as const, expectedSourcemap: 'inline', expectedPrevious: 'enabled' },
{ sourcemap: true, expectedSourcemap: true, expectedPrevious: 'enabled' },
{ sourcemap: undefined, expectedSourcemap: 'hidden', expectedPrevious: 'unset' },
];

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

const { getUpdatedSourceMapSetting } = await import('../../src/vite/sourceMaps');
const result = getUpdatedSourceMapSetting(viteConfig);

const result = getUpdatedSourceMapSetting(viteConfig);

expect(result).toEqual({
updatedSourceMapSetting: expectedSourcemap,
previousSourceMapSetting: expectedPrevious,
});
});
expect(result).toEqual({
updatedSourceMapSetting: expectedSourcemap,
previousSourceMapSetting: expectedPrevious,
});
},
);
});
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions packages/sveltekit/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,9 +9,7 @@
"engines": {
"node": ">=18"
},
"files": [
"/build"
],
"files": ["/build"],
"main": "build/cjs/index.server.js",
"module": "build/esm/index.server.js",
"browser": "build/esm/index.client.js",
Expand DownExpand Up@@ -44,7 +42,7 @@
"@sentry/node": "9.1.0",
"@sentry/opentelemetry": "9.1.0",
"@sentry/svelte": "9.1.0",
"@sentry/vite-plugin": "2.22.6",
"@sentry/vite-plugin": "3.2.0",
"magic-string": "0.30.7",
"magicast": "0.2.8",
"sorcery": "1.0.0"
Expand Down
174 changes: 85 additions & 89 deletions packages/sveltekit/src/vite/sourceMaps.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,13 +24,6 @@ type Sorcery = {
load(filepath: string): Promise<Chain>;
};

type GlobalWithSourceMapSetting = typeof globalThis & {
_sentry_sourceMapSetting?: {
updatedSourceMapSetting?: boolean | 'inline' | 'hidden';
previousSourceMapSetting?: UserSourceMapSetting;
};
};

// storing this in the module scope because `makeCustomSentryVitePlugin` is called multiple times
// and we only want to generate a uuid once in case we have to fall back to it.
const releaseName = detectSentryRelease();
Expand All@@ -57,8 +50,6 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
const usedAdapter = options?.adapter || 'other';
const adapterOutputDir = await getAdapterOutputDir(svelteConfig, usedAdapter);

const globalWithSourceMapSetting = globalThis as GlobalWithSourceMapSetting;

const defaultPluginOptions: SentryVitePluginOptions = {
release: {
name: releaseName,
Expand All@@ -70,61 +61,8 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
},
};

// Including all hidden (`.*`) directories by default so that folders like .vercel,
// .netlify, etc are also cleaned up. Additionally, we include the adapter output
// dir which could be a non-hidden directory, like `build` for the Node adapter.
const defaultFileDeletionGlob = ['./.*/**/*.map', `./${adapterOutputDir}/**/*.map`];

if (!globalWithSourceMapSetting._sentry_sourceMapSetting) {
let configFile: {
path: string;
config: UserConfig;
dependencies: string[];
} | null = null;

try {
// @ts-expect-error - the dynamic import here works fine
const Vite = await import('vite');
configFile = await Vite.loadConfigFromFile({ command: 'build', mode: 'production' });
} catch {
if (options?.debug) {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
'[Sentry] Could not import Vite to load your vite config. Please set `build.sourcemap` to `true` or `hidden` to enable source map generation.',
);
});
}
}

if (configFile) {
globalWithSourceMapSetting._sentry_sourceMapSetting = getUpdatedSourceMapSetting(configFile.config);
} else {
if (options?.debug) {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
'[Sentry] Could not load Vite config with Vite "production" mode. This is needed for Sentry to automatically update source map settings.',
);
});
}
}

if (options?.debug && globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'unset') {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
`[Sentry] Automatically setting \`sourceMapsUploadOptions.sourcemaps.filesToDeleteAfterUpload: [${defaultFileDeletionGlob
.map(file => `"${file}"`)
.join(', ')}]\` to delete generated source maps after they were uploaded to Sentry.`,
);
});
}
}

const shouldDeleteDefaultSourceMaps =
globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'unset' &&
!options?.sourcemaps?.filesToDeleteAfterUpload;
const { promise: filesToDeleteAfterUpload, resolve: resolveFilesToDeleteAfterUpload } =
createFilesToDeleteAfterUploadPromise();

const mergedOptions = {
...defaultPluginOptions,
Expand All@@ -135,9 +73,7 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
},
sourcemaps: {
...options?.sourcemaps,
filesToDeleteAfterUpload: shouldDeleteDefaultSourceMaps
? defaultFileDeletionGlob
: options?.sourcemaps?.filesToDeleteAfterUpload,
filesToDeleteAfterUpload,
},
};

Expand All@@ -163,6 +99,10 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
console.warn(
'sentry-vite-debug-id-upload-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
);

// resolving filesToDeleteAfterUpload here, because we return the original deletion plugin which awaits the promise
resolveFilesToDeleteAfterUpload(undefined);
Comment on lines +103 to +104

@Lms24Lms24Feb 19, 2025

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the ugly part of this PR: We always need to resolve the promise we pass to the vite plugin. So everywhere where we early return, in addition to the expected places.


return sentryPlugins;
}

Expand All@@ -172,6 +112,10 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
console.warn(
'sentry-file-deletion-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
);

// resolving filesToDeleteAfterUpload here, because we return the original deletion plugin which awaits the promise
resolveFilesToDeleteAfterUpload(undefined);

return sentryPlugins;
}

Expand All@@ -181,6 +125,10 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
console.warn(
'sentry-release-management-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
);

// resolving filesToDeleteAfterUpload here, because we return the original deletion plugin which awaits the promise
resolveFilesToDeleteAfterUpload(undefined);

return sentryPlugins;
}

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

if (globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'unset') {
const { updatedSourceMapSetting, previousSourceMapSetting } = getUpdatedSourceMapSetting(config);

const userProvidedFilesToDeleteAfterUpload = await options?.sourcemaps?.filesToDeleteAfterUpload;

if (previousSourceMapSetting === 'unset') {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.log(`[Sentry] Enabled source map generation in the build options with \`${settingKey}: "hidden"\`.`);
});

if (userProvidedFilesToDeleteAfterUpload) {
resolveFilesToDeleteAfterUpload(userProvidedFilesToDeleteAfterUpload);
} else {
// Including all hidden (`.*`) directories by default so that folders like .vercel,
// .netlify, etc are also cleaned up. Additionally, we include the adapter output
// dir which could be a non-hidden directory, like `build` for the Node adapter.
const defaultFileDeletionGlob = ['./.*/**/*.map', `./${adapterOutputDir}/**/*.map`];

consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
`[Sentry] Automatically setting \`sourceMapsUploadOptions.sourcemaps.filesToDeleteAfterUpload: [${defaultFileDeletionGlob
.map(file => `"${file}"`)
.join(', ')}]\` to delete generated source maps after they were uploaded to Sentry.`,
);
});

// In case we enabled source maps and users didn't specify a glob patter to delete, we set a default pattern:
resolveFilesToDeleteAfterUpload(defaultFileDeletionGlob);
}

return {
...config,
build: { ...config.build, sourcemap: 'hidden' },
build: { ...config.build, sourcemap: updatedSourceMapSetting },
};
} else if (globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'disabled') {
}

if (previousSourceMapSetting === 'disabled') {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.warn(
`[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\`).`,
);
});
} else if (globalWithSourceMapSetting._sentry_sourceMapSetting?.previousSourceMapSetting === 'enabled') {
} else if (previousSourceMapSetting === 'enabled') {
if (mergedOptions?.debug) {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.log(
`[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.`,
`[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.`,
);
});
}
}

resolveFilesToDeleteAfterUpload(userProvidedFilesToDeleteAfterUpload);

return config;
},
};
Expand DownExpand Up@@ -423,7 +400,7 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
/**
* Whether the user enabled (true, 'hidden', 'inline') or disabled (false) source maps
*/
export type UserSourceMapSetting = 'enabled' | 'disabled' | 'unset' | undefined;
type UserSourceMapSetting = 'enabled' | 'disabled' | 'unset' | undefined;

/** There are 3 ways to set up source map generation (https://github.com/getsentry/sentry-javascript/issues/13993)
*
Expand All@@ -445,25 +422,25 @@ export function getUpdatedSourceMapSetting(viteConfig: {
sourcemap?: boolean | 'inline' | 'hidden';
};
}): { updatedSourceMapSetting: boolean | 'inline' | 'hidden'; previousSourceMapSetting: UserSourceMapSetting } {
let previousSourceMapSetting: UserSourceMapSetting;
let updatedSourceMapSetting: boolean | 'inline' | 'hidden' | undefined;

viteConfig.build = viteConfig.build || {};

const viteSourceMap = viteConfig.build.sourcemap;

if (viteSourceMap === false) {
previousSourceMapSetting = 'disabled';
updatedSourceMapSetting = viteSourceMap;
} else if (viteSourceMap && ['hidden', 'inline', true].includes(viteSourceMap)) {
previousSourceMapSetting = 'enabled';
updatedSourceMapSetting = viteSourceMap;
} else {
previousSourceMapSetting = 'unset';
updatedSourceMapSetting = 'hidden';
const originalSourcemapSetting = viteConfig.build.sourcemap;

if (originalSourcemapSetting === false) {
return {
previousSourceMapSetting: 'disabled',
updatedSourceMapSetting: originalSourcemapSetting,
};
}

if (originalSourcemapSetting && ['hidden', 'inline', true].includes(originalSourcemapSetting)) {
return { previousSourceMapSetting: 'enabled', updatedSourceMapSetting: originalSourcemapSetting };
}

return { previousSourceMapSetting, updatedSourceMapSetting };
return {
previousSourceMapSetting: 'unset',
updatedSourceMapSetting: 'hidden',
};
}

function getFiles(dir: string): string[] {
Expand DownExpand Up@@ -499,3 +476,22 @@ function detectSentryRelease(): string {

return release;
}

/**
* Creates a deferred promise that can be resolved/rejected by calling the
* `resolve` or `reject` function.
* Inspired by: https://stackoverflow.com/a/69027809
*/
function createFilesToDeleteAfterUploadPromise(): {
Comment thread
chargome marked this conversation as resolved.
promise: Promise<string | string[] | undefined>;
resolve: (value: string | string[] | undefined) => void;
reject: (reason?: unknown) => void;
} {
let resolve!: (value: string | string[] | undefined) => void;
let reject!: (reason?: unknown) => void;
const promise = new Promise<string | string[] | undefined>((res, rej) => {
resolve = res;
reject = rej;
});
return { resolve, reject, promise };
}
33 changes: 17 additions & 16 deletions packages/sveltekit/test/vite/sourceMaps.test.ts
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { makeCustomSentryVitePlugins } from '../../src/vite/sourceMaps';
import { getUpdatedSourceMapSetting, makeCustomSentryVitePlugins } from '../../src/vite/sourceMaps';

import type { Plugin } from 'vite';

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

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

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

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

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

const plugin = await getSentryViteSubPlugin('sentry-sveltekit-update-source-map-setting-plugin');
// @ts-expect-error this function exists!
const sentryConfig = plugin.config(originalConfig);
const sentryConfig = await plugin.config(originalConfig);
expect(sentryConfig).toEqual({
...originalConfig,
build: {
Expand DownExpand Up@@ -320,22 +320,23 @@ describe('makeCustomSentryVitePlugins()', () => {
describe('changeViteSourceMapSettings()', () => {
const cases = [
{ sourcemap: false, expectedSourcemap: false, expectedPrevious: 'disabled' },
{ sourcemap: 'hidden', expectedSourcemap: 'hidden', expectedPrevious: 'enabled' },
{ sourcemap: 'inline', expectedSourcemap: 'inline', expectedPrevious: 'enabled' },
{ sourcemap: 'hidden' as const, expectedSourcemap: 'hidden', expectedPrevious: 'enabled' },
{ sourcemap: 'inline' as const, expectedSourcemap: 'inline', expectedPrevious: 'enabled' },
{ sourcemap: true, expectedSourcemap: true, expectedPrevious: 'enabled' },
{ sourcemap: undefined, expectedSourcemap: 'hidden', expectedPrevious: 'unset' },
];

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

const { getUpdatedSourceMapSetting } = await import('../../src/vite/sourceMaps');
const result = getUpdatedSourceMapSetting(viteConfig);

const result = getUpdatedSourceMapSetting(viteConfig);

expect(result).toEqual({
updatedSourceMapSetting: expectedSourcemap,
previousSourceMapSetting: expectedPrevious,
});
});
expect(result).toEqual({
updatedSourceMapSetting: expectedSourcemap,
previousSourceMapSetting: expectedPrevious,
});
},
);
});
Loading