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
89 changes: 85 additions & 4 deletions packages/sveltekit/src/vite/sourceMaps.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -76,6 +76,14 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
plugin => plugin.name === 'sentry-vite-debug-id-upload-plugin',
);

const sentryViteFileDeletionPlugin = sentryPlugins.find(plugin => plugin.name === 'sentry-file-deletion-plugin');

const sentryViteReleaseManagementPlugin = sentryPlugins.find(
// sentry-debug-id-upload-plugin was the old (misleading) name of the plugin
// sentry-release-management-plugin is the new name
plugin => plugin.name === 'sentry-debug-id-upload-plugin' || plugin.name === 'sentry-release-management-plugin',
);

if (!sentryViteDebugIdUploadPlugin) {
debug &&
// eslint-disable-next-line no-console
Expand All@@ -85,7 +93,33 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
return sentryPlugins;
}

const restOfSentryVitePlugins = sentryPlugins.filter(plugin => plugin.name !== 'sentry-vite-debug-id-upload-plugin');
if (!sentryViteFileDeletionPlugin) {
debug &&
// eslint-disable-next-line no-console
console.warn(
'sentry-file-deletion-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
);
return sentryPlugins;
}

if (!sentryViteReleaseManagementPlugin) {
debug &&
// eslint-disable-next-line no-console
console.warn(
'sentry-release-management-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
);
return sentryPlugins;
}

const unchangedSentryVitePlugins = sentryPlugins.filter(
plugin =>
![
'sentry-vite-debug-id-upload-plugin',
'sentry-file-deletion-plugin',
'sentry-release-management-plugin', // new name of release management plugin
'sentry-debug-id-upload-plugin', // old name of release management plugin
].includes(plugin.name),
);

let isSSRBuild = true;

Expand All@@ -95,8 +129,8 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
__sentry_sveltekit_output_dir: outputDir,
};

const customPlugin: Plugin = {
name: 'sentry-upload-sveltekit-source-maps',
const customDebugIdUploadPlugin: Plugin = {
name: 'sentry-sveltekit-debug-id-upload-plugin',
apply: 'build', // only apply this plugin at build time
enforce: 'post', // this needs to be set to post, otherwise we don't pick up the output from the SvelteKit adapter

Expand DownExpand Up@@ -248,7 +282,54 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
},
};

return [...restOfSentryVitePlugins, customPlugin];
// The file deletion plugin is originally called in `writeBundle`.
// We need to call it in `closeBundle` though, because we also postpone
// the upload step to `closeBundle`
const customFileDeletionPlugin: Plugin = {
name: 'sentry-sveltekit-file-deletion-plugin',
apply: 'build', // only apply this plugin at build time
enforce: 'post',
closeBundle: async () => {
if (!isSSRBuild) {
return;
}

const writeBundleFn = sentryViteFileDeletionPlugin?.writeBundle;
if (typeof writeBundleFn === 'function') {
// This is fine though, because the original method doesn't consume any arguments in its `writeBundle` callback.
const outDir = path.resolve(process.cwd(), outputDir);
try {
// @ts-expect-error - the writeBundle hook expects two args we can't pass in here (they're only available in `writeBundle`)
await writeBundleFn({ dir: outDir });
} catch (e) {
// eslint-disable-next-line no-console
console.warn('Failed to delete source maps:', e);
}
}
},
};

const customReleaseManagementPlugin: Plugin = {
name: 'sentry-sveltekit-release-management-plugin',
apply: 'build', // only apply this plugin at build time
enforce: 'post',
closeBundle: async () => {
try {
// @ts-expect-error - this hook exists on the plugin!
await sentryViteReleaseManagementPlugin.writeBundle();
} catch (e) {
// eslint-disable-next-line no-console
console.warn('[Source Maps Plugin] Failed to upload release data:', e);
}
},
};

return [
...unchangedSentryVitePlugins,
customReleaseManagementPlugin,
customDebugIdUploadPlugin,
customFileDeletionPlugin,
];
}

function getFiles(dir: string): string[] {
Expand Down
10 changes: 6 additions & 4 deletions packages/sveltekit/test/vite/sentrySvelteKitPlugins.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -55,11 +55,13 @@ describe('sentrySvelteKit()', () => {
// default source maps plugins:
'sentry-telemetry-plugin',
'sentry-vite-release-injection-plugin',
'sentry-debug-id-upload-plugin',
'sentry-vite-debug-id-injection-plugin',
'sentry-file-deletion-plugin',
// custom release plugin:
'sentry-sveltekit-release-management-plugin',
// custom source maps plugin:
'sentry-upload-sveltekit-source-maps',
'sentry-sveltekit-debug-id-upload-plugin',
// custom deletion plugin
'sentry-sveltekit-file-deletion-plugin',
]);
});

Expand All@@ -76,7 +78,7 @@ describe('sentrySvelteKit()', () => {
const instrumentPlugin = plugins[0];

expect(plugins).toHaveLength(1);
expect(instrumentPlugin.name).toEqual('sentry-auto-instrumentation');
expect(instrumentPlugin?.name).toEqual('sentry-auto-instrumentation');

process.env.NODE_ENV = previousEnv;
});
Expand Down
146 changes: 129 additions & 17 deletions packages/sveltekit/test/vite/sourceMaps.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,17 +3,31 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { Plugin } from 'vite';
import { makeCustomSentryVitePlugins } from '../../src/vite/sourceMaps';

const mockedSentryVitePlugin = {
const mockedViteDebugIdUploadPlugin = {
name: 'sentry-vite-debug-id-upload-plugin',
writeBundle: vi.fn(),
};

const mockedViteReleaseManagementPlugin = {
name: 'sentry-release-management-plugin',
writeBundle: vi.fn(),
};

const mockedFileDeletionPlugin = {
name: 'sentry-file-deletion-plugin',
writeBundle: vi.fn(),
};

vi.mock('@sentry/vite-plugin', async () => {
const original = (await vi.importActual('@sentry/vite-plugin')) as any;

return {
...original,
sentryVitePlugin: () => [mockedSentryVitePlugin],
sentryVitePlugin: () => [
mockedViteReleaseManagementPlugin,
mockedViteDebugIdUploadPlugin,
mockedFileDeletionPlugin,
],
};
});

Expand All@@ -30,20 +44,22 @@ beforeEach(() => {
vi.clearAllMocks();
});

async function getCustomSentryViteUploadSourcemapsPlugin(): Promise<Plugin | undefined> {
async function getSentryViteSubPlugin(name: string): Promise<Plugin | undefined> {
const plugins = await makeCustomSentryVitePlugins({
authToken: 'token',
org: 'org',
project: 'project',
adapter: 'other',
});
return plugins.find(plugin => plugin.name === 'sentry-upload-sveltekit-source-maps');

return plugins.find(plugin => plugin.name === name);
}

describe('makeCustomSentryVitePlugin()', () => {
it('returns the custom sentry source maps plugin', async () => {
const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
expect(plugin?.name).toEqual('sentry-upload-sveltekit-source-maps');
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');

expect(plugin?.name).toEqual('sentry-sveltekit-debug-id-upload-plugin');
expect(plugin?.apply).toEqual('build');
expect(plugin?.enforce).toEqual('post');

Expand All@@ -58,9 +74,9 @@ describe('makeCustomSentryVitePlugin()', () => {
expect(plugin?.writeBundle).toBeUndefined();
});

describe('Custom sentry vite plugin', () => {
describe('Custom debug id source maps plugin plugin', () => {
it('enables source map generation', async () => {
const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');
// @ts-expect-error this function exists!
const sentrifiedConfig = plugin.config({ build: { foo: {} }, test: {} });
expect(sentrifiedConfig).toEqual({
Expand All@@ -73,7 +89,7 @@ describe('makeCustomSentryVitePlugin()', () => {
});

it('injects the output dir into the server hooks file', async () => {
const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');
// @ts-expect-error this function exists!
const transformOutput = await plugin.transform('foo', '/src/hooks.server.ts');
const transformedCode = transformOutput.code;
Expand All@@ -84,34 +100,34 @@ describe('makeCustomSentryVitePlugin()', () => {
});

it('uploads source maps during the SSR build', async () => {
const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');
// @ts-expect-error this function exists!
plugin.configResolved({ build: { ssr: true } });
// @ts-expect-error this function exists!
await plugin.closeBundle();
expect(mockedSentryVitePlugin.writeBundle).toHaveBeenCalledTimes(1);
expect(mockedViteDebugIdUploadPlugin.writeBundle).toHaveBeenCalledTimes(1);
});

it("doesn't upload source maps during the non-SSR builds", async () => {
const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');

// @ts-expect-error this function exists!
plugin.configResolved({ build: { ssr: false } });
// @ts-expect-error this function exists!
await plugin.closeBundle();
expect(mockedSentryVitePlugin.writeBundle).not.toHaveBeenCalled();
expect(mockedViteDebugIdUploadPlugin.writeBundle).not.toHaveBeenCalled();
});
});

it('catches errors while uploading source maps', async () => {
mockedSentryVitePlugin.writeBundle.mockImplementationOnce(() => {
mockedViteDebugIdUploadPlugin.writeBundle.mockImplementationOnce(() => {
throw new Error('test error');
});

const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementationOnce(() => {});
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementationOnce(() => {});

const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');

// @ts-expect-error this function exists!
expect(plugin.closeBundle).not.toThrow();
Expand All@@ -124,4 +140,100 @@ describe('makeCustomSentryVitePlugin()', () => {
expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining('Failed to upload source maps'));
expect(consoleLogSpy).toHaveBeenCalled();
});

describe('Custom release management plugin', () => {
it('has the expected hooks and properties', async () => {
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-release-management-plugin');

expect(plugin).toEqual({
name: 'sentry-sveltekit-release-management-plugin',
apply: 'build',
enforce: 'post',
closeBundle: expect.any(Function),
});
});

it('calls the original release management plugin to start the release creation pipeline', async () => {
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-release-management-plugin');
// @ts-expect-error this function exists!
await plugin.closeBundle();
expect(mockedViteReleaseManagementPlugin.writeBundle).toHaveBeenCalledTimes(1);
});

it('catches errors during release creation', async () => {
mockedViteReleaseManagementPlugin.writeBundle.mockImplementationOnce(() => {
throw new Error('test error');
});

const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementationOnce(() => {});

const plugin = await getSentryViteSubPlugin('sentry-sveltekit-release-management-plugin');

// @ts-expect-error this function exists!
expect(plugin.closeBundle).not.toThrow();

// @ts-expect-error this function exists!
await plugin.closeBundle();

expect(consoleWarnSpy).toHaveBeenCalledWith(
expect.stringContaining('Failed to upload release data'),
expect.any(Error),
);
});

it('also works correctly if the original release management plugin has its old name', async () => {
const currentName = mockedViteReleaseManagementPlugin.name;
mockedViteReleaseManagementPlugin.name = 'sentry-debug-id-upload-plugin';

const plugin = await getSentryViteSubPlugin('sentry-sveltekit-release-management-plugin');

// @ts-expect-error this function exists!
await plugin.closeBundle();

expect(mockedViteReleaseManagementPlugin.writeBundle).toHaveBeenCalledTimes(1);

mockedViteReleaseManagementPlugin.name = currentName;
});
});

describe('Custom file deletion plugin', () => {
it('has the expected hooks and properties', async () => {
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-file-deletion-plugin');

expect(plugin).toEqual({
name: 'sentry-sveltekit-file-deletion-plugin',
apply: 'build',
enforce: 'post',
closeBundle: expect.any(Function),
});
});

it('calls the original file deletion plugin to delete files', async () => {
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-file-deletion-plugin');
// @ts-expect-error this function exists!
await plugin.closeBundle();
expect(mockedFileDeletionPlugin.writeBundle).toHaveBeenCalledTimes(1);
});

it('catches errors during file deletion', async () => {
mockedFileDeletionPlugin.writeBundle.mockImplementationOnce(() => {
throw new Error('test error');
});

const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementationOnce(() => {});

const plugin = await getSentryViteSubPlugin('sentry-sveltekit-file-deletion-plugin');

// @ts-expect-error this function exists!
expect(plugin.closeBundle).not.toThrow();

// @ts-expect-error this function exists!
await plugin.closeBundle();

expect(consoleWarnSpy).toHaveBeenCalledWith(
expect.stringContaining('Failed to delete source maps'),
expect.any(Error),
);
});
});
});
, '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
89 changes: 85 additions & 4 deletions packages/sveltekit/src/vite/sourceMaps.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -76,6 +76,14 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
plugin => plugin.name === 'sentry-vite-debug-id-upload-plugin',
);

const sentryViteFileDeletionPlugin = sentryPlugins.find(plugin => plugin.name === 'sentry-file-deletion-plugin');

const sentryViteReleaseManagementPlugin = sentryPlugins.find(
// sentry-debug-id-upload-plugin was the old (misleading) name of the plugin
// sentry-release-management-plugin is the new name
plugin => plugin.name === 'sentry-debug-id-upload-plugin' || plugin.name === 'sentry-release-management-plugin',
);

if (!sentryViteDebugIdUploadPlugin) {
debug &&
// eslint-disable-next-line no-console
Expand All@@ -85,7 +93,33 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
return sentryPlugins;
}

const restOfSentryVitePlugins = sentryPlugins.filter(plugin => plugin.name !== 'sentry-vite-debug-id-upload-plugin');
if (!sentryViteFileDeletionPlugin) {
debug &&
// eslint-disable-next-line no-console
console.warn(
'sentry-file-deletion-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
);
return sentryPlugins;
}

if (!sentryViteReleaseManagementPlugin) {
debug &&
// eslint-disable-next-line no-console
console.warn(
'sentry-release-management-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
);
return sentryPlugins;
}

const unchangedSentryVitePlugins = sentryPlugins.filter(
plugin =>
![
'sentry-vite-debug-id-upload-plugin',
'sentry-file-deletion-plugin',
'sentry-release-management-plugin', // new name of release management plugin
'sentry-debug-id-upload-plugin', // old name of release management plugin
].includes(plugin.name),
);

let isSSRBuild = true;

Expand All@@ -95,8 +129,8 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
__sentry_sveltekit_output_dir: outputDir,
};

const customPlugin: Plugin = {
name: 'sentry-upload-sveltekit-source-maps',
const customDebugIdUploadPlugin: Plugin = {
name: 'sentry-sveltekit-debug-id-upload-plugin',
apply: 'build', // only apply this plugin at build time
enforce: 'post', // this needs to be set to post, otherwise we don't pick up the output from the SvelteKit adapter

Expand DownExpand Up@@ -248,7 +282,54 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
},
};

return [...restOfSentryVitePlugins, customPlugin];
// The file deletion plugin is originally called in `writeBundle`.
// We need to call it in `closeBundle` though, because we also postpone
// the upload step to `closeBundle`
const customFileDeletionPlugin: Plugin = {
name: 'sentry-sveltekit-file-deletion-plugin',
apply: 'build', // only apply this plugin at build time
enforce: 'post',
closeBundle: async () => {
if (!isSSRBuild) {
return;
}

const writeBundleFn = sentryViteFileDeletionPlugin?.writeBundle;
if (typeof writeBundleFn === 'function') {
// This is fine though, because the original method doesn't consume any arguments in its `writeBundle` callback.
const outDir = path.resolve(process.cwd(), outputDir);
try {
// @ts-expect-error - the writeBundle hook expects two args we can't pass in here (they're only available in `writeBundle`)
await writeBundleFn({ dir: outDir });
} catch (e) {
// eslint-disable-next-line no-console
console.warn('Failed to delete source maps:', e);
}
}
},
};

const customReleaseManagementPlugin: Plugin = {
name: 'sentry-sveltekit-release-management-plugin',
apply: 'build', // only apply this plugin at build time
enforce: 'post',
closeBundle: async () => {
try {
// @ts-expect-error - this hook exists on the plugin!
await sentryViteReleaseManagementPlugin.writeBundle();
} catch (e) {
// eslint-disable-next-line no-console
console.warn('[Source Maps Plugin] Failed to upload release data:', e);
}
},
};

return [
...unchangedSentryVitePlugins,
customReleaseManagementPlugin,
customDebugIdUploadPlugin,
customFileDeletionPlugin,
];
}

function getFiles(dir: string): string[] {
Expand Down
10 changes: 6 additions & 4 deletions packages/sveltekit/test/vite/sentrySvelteKitPlugins.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -55,11 +55,13 @@ describe('sentrySvelteKit()', () => {
// default source maps plugins:
'sentry-telemetry-plugin',
'sentry-vite-release-injection-plugin',
'sentry-debug-id-upload-plugin',
'sentry-vite-debug-id-injection-plugin',
'sentry-file-deletion-plugin',
// custom release plugin:
'sentry-sveltekit-release-management-plugin',
// custom source maps plugin:
'sentry-upload-sveltekit-source-maps',
'sentry-sveltekit-debug-id-upload-plugin',
// custom deletion plugin
'sentry-sveltekit-file-deletion-plugin',
]);
});

Expand All@@ -76,7 +78,7 @@ describe('sentrySvelteKit()', () => {
const instrumentPlugin = plugins[0];

expect(plugins).toHaveLength(1);
expect(instrumentPlugin.name).toEqual('sentry-auto-instrumentation');
expect(instrumentPlugin?.name).toEqual('sentry-auto-instrumentation');

process.env.NODE_ENV = previousEnv;
});
Expand Down
146 changes: 129 additions & 17 deletions packages/sveltekit/test/vite/sourceMaps.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,17 +3,31 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { Plugin } from 'vite';
import { makeCustomSentryVitePlugins } from '../../src/vite/sourceMaps';

const mockedSentryVitePlugin = {
const mockedViteDebugIdUploadPlugin = {
name: 'sentry-vite-debug-id-upload-plugin',
writeBundle: vi.fn(),
};

const mockedViteReleaseManagementPlugin = {
name: 'sentry-release-management-plugin',
writeBundle: vi.fn(),
};

const mockedFileDeletionPlugin = {
name: 'sentry-file-deletion-plugin',
writeBundle: vi.fn(),
};

vi.mock('@sentry/vite-plugin', async () => {
const original = (await vi.importActual('@sentry/vite-plugin')) as any;

return {
...original,
sentryVitePlugin: () => [mockedSentryVitePlugin],
sentryVitePlugin: () => [
mockedViteReleaseManagementPlugin,
mockedViteDebugIdUploadPlugin,
mockedFileDeletionPlugin,
],
};
});

Expand All@@ -30,20 +44,22 @@ beforeEach(() => {
vi.clearAllMocks();
});

async function getCustomSentryViteUploadSourcemapsPlugin(): Promise<Plugin | undefined> {
async function getSentryViteSubPlugin(name: string): Promise<Plugin | undefined> {
const plugins = await makeCustomSentryVitePlugins({
authToken: 'token',
org: 'org',
project: 'project',
adapter: 'other',
});
return plugins.find(plugin => plugin.name === 'sentry-upload-sveltekit-source-maps');

return plugins.find(plugin => plugin.name === name);
}

describe('makeCustomSentryVitePlugin()', () => {
it('returns the custom sentry source maps plugin', async () => {
const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
expect(plugin?.name).toEqual('sentry-upload-sveltekit-source-maps');
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');

expect(plugin?.name).toEqual('sentry-sveltekit-debug-id-upload-plugin');
expect(plugin?.apply).toEqual('build');
expect(plugin?.enforce).toEqual('post');

Expand All@@ -58,9 +74,9 @@ describe('makeCustomSentryVitePlugin()', () => {
expect(plugin?.writeBundle).toBeUndefined();
});

describe('Custom sentry vite plugin', () => {
describe('Custom debug id source maps plugin plugin', () => {
it('enables source map generation', async () => {
const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');
// @ts-expect-error this function exists!
const sentrifiedConfig = plugin.config({ build: { foo: {} }, test: {} });
expect(sentrifiedConfig).toEqual({
Expand All@@ -73,7 +89,7 @@ describe('makeCustomSentryVitePlugin()', () => {
});

it('injects the output dir into the server hooks file', async () => {
const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');
// @ts-expect-error this function exists!
const transformOutput = await plugin.transform('foo', '/src/hooks.server.ts');
const transformedCode = transformOutput.code;
Expand All@@ -84,34 +100,34 @@ describe('makeCustomSentryVitePlugin()', () => {
});

it('uploads source maps during the SSR build', async () => {
const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');
// @ts-expect-error this function exists!
plugin.configResolved({ build: { ssr: true } });
// @ts-expect-error this function exists!
await plugin.closeBundle();
expect(mockedSentryVitePlugin.writeBundle).toHaveBeenCalledTimes(1);
expect(mockedViteDebugIdUploadPlugin.writeBundle).toHaveBeenCalledTimes(1);
});

it("doesn't upload source maps during the non-SSR builds", async () => {
const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');

// @ts-expect-error this function exists!
plugin.configResolved({ build: { ssr: false } });
// @ts-expect-error this function exists!
await plugin.closeBundle();
expect(mockedSentryVitePlugin.writeBundle).not.toHaveBeenCalled();
expect(mockedViteDebugIdUploadPlugin.writeBundle).not.toHaveBeenCalled();
});
});

it('catches errors while uploading source maps', async () => {
mockedSentryVitePlugin.writeBundle.mockImplementationOnce(() => {
mockedViteDebugIdUploadPlugin.writeBundle.mockImplementationOnce(() => {
throw new Error('test error');
});

const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementationOnce(() => {});
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementationOnce(() => {});

const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');

// @ts-expect-error this function exists!
expect(plugin.closeBundle).not.toThrow();
Expand All@@ -124,4 +140,100 @@ describe('makeCustomSentryVitePlugin()', () => {
expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining('Failed to upload source maps'));
expect(consoleLogSpy).toHaveBeenCalled();
});

describe('Custom release management plugin', () => {
it('has the expected hooks and properties', async () => {
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-release-management-plugin');

expect(plugin).toEqual({
name: 'sentry-sveltekit-release-management-plugin',
apply: 'build',
enforce: 'post',
closeBundle: expect.any(Function),
});
});

it('calls the original release management plugin to start the release creation pipeline', async () => {
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-release-management-plugin');
// @ts-expect-error this function exists!
await plugin.closeBundle();
expect(mockedViteReleaseManagementPlugin.writeBundle).toHaveBeenCalledTimes(1);
});

it('catches errors during release creation', async () => {
mockedViteReleaseManagementPlugin.writeBundle.mockImplementationOnce(() => {
throw new Error('test error');
});

const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementationOnce(() => {});

const plugin = await getSentryViteSubPlugin('sentry-sveltekit-release-management-plugin');

// @ts-expect-error this function exists!
expect(plugin.closeBundle).not.toThrow();

// @ts-expect-error this function exists!
await plugin.closeBundle();

expect(consoleWarnSpy).toHaveBeenCalledWith(
expect.stringContaining('Failed to upload release data'),
expect.any(Error),
);
});

it('also works correctly if the original release management plugin has its old name', async () => {
const currentName = mockedViteReleaseManagementPlugin.name;
mockedViteReleaseManagementPlugin.name = 'sentry-debug-id-upload-plugin';

const plugin = await getSentryViteSubPlugin('sentry-sveltekit-release-management-plugin');

// @ts-expect-error this function exists!
await plugin.closeBundle();

expect(mockedViteReleaseManagementPlugin.writeBundle).toHaveBeenCalledTimes(1);

mockedViteReleaseManagementPlugin.name = currentName;
});
});

describe('Custom file deletion plugin', () => {
it('has the expected hooks and properties', async () => {
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-file-deletion-plugin');

expect(plugin).toEqual({
name: 'sentry-sveltekit-file-deletion-plugin',
apply: 'build',
enforce: 'post',
closeBundle: expect.any(Function),
});
});

it('calls the original file deletion plugin to delete files', async () => {
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-file-deletion-plugin');
// @ts-expect-error this function exists!
await plugin.closeBundle();
expect(mockedFileDeletionPlugin.writeBundle).toHaveBeenCalledTimes(1);
});

it('catches errors during file deletion', async () => {
mockedFileDeletionPlugin.writeBundle.mockImplementationOnce(() => {
throw new Error('test error');
});

const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementationOnce(() => {});

const plugin = await getSentryViteSubPlugin('sentry-sveltekit-file-deletion-plugin');

// @ts-expect-error this function exists!
expect(plugin.closeBundle).not.toThrow();

// @ts-expect-error this function exists!
await plugin.closeBundle();

expect(consoleWarnSpy).toHaveBeenCalledWith(
expect.stringContaining('Failed to delete source maps'),
expect.any(Error),
);
});
});
});
, '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
89 changes: 85 additions & 4 deletions packages/sveltekit/src/vite/sourceMaps.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -76,6 +76,14 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
plugin => plugin.name === 'sentry-vite-debug-id-upload-plugin',
);

const sentryViteFileDeletionPlugin = sentryPlugins.find(plugin => plugin.name === 'sentry-file-deletion-plugin');

const sentryViteReleaseManagementPlugin = sentryPlugins.find(
// sentry-debug-id-upload-plugin was the old (misleading) name of the plugin
// sentry-release-management-plugin is the new name
plugin => plugin.name === 'sentry-debug-id-upload-plugin' || plugin.name === 'sentry-release-management-plugin',
);

if (!sentryViteDebugIdUploadPlugin) {
debug &&
// eslint-disable-next-line no-console
Expand All@@ -85,7 +93,33 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
return sentryPlugins;
}

const restOfSentryVitePlugins = sentryPlugins.filter(plugin => plugin.name !== 'sentry-vite-debug-id-upload-plugin');
if (!sentryViteFileDeletionPlugin) {
debug &&
// eslint-disable-next-line no-console
console.warn(
'sentry-file-deletion-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
);
return sentryPlugins;
}

if (!sentryViteReleaseManagementPlugin) {
debug &&
// eslint-disable-next-line no-console
console.warn(
'sentry-release-management-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
);
return sentryPlugins;
}

const unchangedSentryVitePlugins = sentryPlugins.filter(
plugin =>
![
'sentry-vite-debug-id-upload-plugin',
'sentry-file-deletion-plugin',
'sentry-release-management-plugin', // new name of release management plugin
'sentry-debug-id-upload-plugin', // old name of release management plugin
].includes(plugin.name),
);

let isSSRBuild = true;

Expand All@@ -95,8 +129,8 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
__sentry_sveltekit_output_dir: outputDir,
};

const customPlugin: Plugin = {
name: 'sentry-upload-sveltekit-source-maps',
const customDebugIdUploadPlugin: Plugin = {
name: 'sentry-sveltekit-debug-id-upload-plugin',
apply: 'build', // only apply this plugin at build time
enforce: 'post', // this needs to be set to post, otherwise we don't pick up the output from the SvelteKit adapter

Expand DownExpand Up@@ -248,7 +282,54 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
},
};

return [...restOfSentryVitePlugins, customPlugin];
// The file deletion plugin is originally called in `writeBundle`.
// We need to call it in `closeBundle` though, because we also postpone
// the upload step to `closeBundle`
const customFileDeletionPlugin: Plugin = {
name: 'sentry-sveltekit-file-deletion-plugin',
apply: 'build', // only apply this plugin at build time
enforce: 'post',
closeBundle: async () => {
if (!isSSRBuild) {
return;
}

const writeBundleFn = sentryViteFileDeletionPlugin?.writeBundle;
if (typeof writeBundleFn === 'function') {
// This is fine though, because the original method doesn't consume any arguments in its `writeBundle` callback.
const outDir = path.resolve(process.cwd(), outputDir);
try {
// @ts-expect-error - the writeBundle hook expects two args we can't pass in here (they're only available in `writeBundle`)
await writeBundleFn({ dir: outDir });
} catch (e) {
// eslint-disable-next-line no-console
console.warn('Failed to delete source maps:', e);
}
}
},
};

const customReleaseManagementPlugin: Plugin = {
name: 'sentry-sveltekit-release-management-plugin',
apply: 'build', // only apply this plugin at build time
enforce: 'post',
closeBundle: async () => {
try {
// @ts-expect-error - this hook exists on the plugin!
await sentryViteReleaseManagementPlugin.writeBundle();
} catch (e) {
// eslint-disable-next-line no-console
console.warn('[Source Maps Plugin] Failed to upload release data:', e);
}
},
};

return [
...unchangedSentryVitePlugins,
customReleaseManagementPlugin,
customDebugIdUploadPlugin,
customFileDeletionPlugin,
];
}

function getFiles(dir: string): string[] {
Expand Down
10 changes: 6 additions & 4 deletions packages/sveltekit/test/vite/sentrySvelteKitPlugins.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -55,11 +55,13 @@ describe('sentrySvelteKit()', () => {
// default source maps plugins:
'sentry-telemetry-plugin',
'sentry-vite-release-injection-plugin',
'sentry-debug-id-upload-plugin',
'sentry-vite-debug-id-injection-plugin',
'sentry-file-deletion-plugin',
// custom release plugin:
'sentry-sveltekit-release-management-plugin',
// custom source maps plugin:
'sentry-upload-sveltekit-source-maps',
'sentry-sveltekit-debug-id-upload-plugin',
// custom deletion plugin
'sentry-sveltekit-file-deletion-plugin',
]);
});

Expand All@@ -76,7 +78,7 @@ describe('sentrySvelteKit()', () => {
const instrumentPlugin = plugins[0];

expect(plugins).toHaveLength(1);
expect(instrumentPlugin.name).toEqual('sentry-auto-instrumentation');
expect(instrumentPlugin?.name).toEqual('sentry-auto-instrumentation');

process.env.NODE_ENV = previousEnv;
});
Expand Down
146 changes: 129 additions & 17 deletions packages/sveltekit/test/vite/sourceMaps.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,17 +3,31 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { Plugin } from 'vite';
import { makeCustomSentryVitePlugins } from '../../src/vite/sourceMaps';

const mockedSentryVitePlugin = {
const mockedViteDebugIdUploadPlugin = {
name: 'sentry-vite-debug-id-upload-plugin',
writeBundle: vi.fn(),
};

const mockedViteReleaseManagementPlugin = {
name: 'sentry-release-management-plugin',
writeBundle: vi.fn(),
};

const mockedFileDeletionPlugin = {
name: 'sentry-file-deletion-plugin',
writeBundle: vi.fn(),
};

vi.mock('@sentry/vite-plugin', async () => {
const original = (await vi.importActual('@sentry/vite-plugin')) as any;

return {
...original,
sentryVitePlugin: () => [mockedSentryVitePlugin],
sentryVitePlugin: () => [
mockedViteReleaseManagementPlugin,
mockedViteDebugIdUploadPlugin,
mockedFileDeletionPlugin,
],
};
});

Expand All@@ -30,20 +44,22 @@ beforeEach(() => {
vi.clearAllMocks();
});

async function getCustomSentryViteUploadSourcemapsPlugin(): Promise<Plugin | undefined> {
async function getSentryViteSubPlugin(name: string): Promise<Plugin | undefined> {
const plugins = await makeCustomSentryVitePlugins({
authToken: 'token',
org: 'org',
project: 'project',
adapter: 'other',
});
return plugins.find(plugin => plugin.name === 'sentry-upload-sveltekit-source-maps');

return plugins.find(plugin => plugin.name === name);
}

describe('makeCustomSentryVitePlugin()', () => {
it('returns the custom sentry source maps plugin', async () => {
const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
expect(plugin?.name).toEqual('sentry-upload-sveltekit-source-maps');
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');

expect(plugin?.name).toEqual('sentry-sveltekit-debug-id-upload-plugin');
expect(plugin?.apply).toEqual('build');
expect(plugin?.enforce).toEqual('post');

Expand All@@ -58,9 +74,9 @@ describe('makeCustomSentryVitePlugin()', () => {
expect(plugin?.writeBundle).toBeUndefined();
});

describe('Custom sentry vite plugin', () => {
describe('Custom debug id source maps plugin plugin', () => {
it('enables source map generation', async () => {
const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');
// @ts-expect-error this function exists!
const sentrifiedConfig = plugin.config({ build: { foo: {} }, test: {} });
expect(sentrifiedConfig).toEqual({
Expand All@@ -73,7 +89,7 @@ describe('makeCustomSentryVitePlugin()', () => {
});

it('injects the output dir into the server hooks file', async () => {
const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');
// @ts-expect-error this function exists!
const transformOutput = await plugin.transform('foo', '/src/hooks.server.ts');
const transformedCode = transformOutput.code;
Expand All@@ -84,34 +100,34 @@ describe('makeCustomSentryVitePlugin()', () => {
});

it('uploads source maps during the SSR build', async () => {
const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');
// @ts-expect-error this function exists!
plugin.configResolved({ build: { ssr: true } });
// @ts-expect-error this function exists!
await plugin.closeBundle();
expect(mockedSentryVitePlugin.writeBundle).toHaveBeenCalledTimes(1);
expect(mockedViteDebugIdUploadPlugin.writeBundle).toHaveBeenCalledTimes(1);
});

it("doesn't upload source maps during the non-SSR builds", async () => {
const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');

// @ts-expect-error this function exists!
plugin.configResolved({ build: { ssr: false } });
// @ts-expect-error this function exists!
await plugin.closeBundle();
expect(mockedSentryVitePlugin.writeBundle).not.toHaveBeenCalled();
expect(mockedViteDebugIdUploadPlugin.writeBundle).not.toHaveBeenCalled();
});
});

it('catches errors while uploading source maps', async () => {
mockedSentryVitePlugin.writeBundle.mockImplementationOnce(() => {
mockedViteDebugIdUploadPlugin.writeBundle.mockImplementationOnce(() => {
throw new Error('test error');
});

const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementationOnce(() => {});
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementationOnce(() => {});

const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');

// @ts-expect-error this function exists!
expect(plugin.closeBundle).not.toThrow();
Expand All@@ -124,4 +140,100 @@ describe('makeCustomSentryVitePlugin()', () => {
expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining('Failed to upload source maps'));
expect(consoleLogSpy).toHaveBeenCalled();
});

describe('Custom release management plugin', () => {
it('has the expected hooks and properties', async () => {
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-release-management-plugin');

expect(plugin).toEqual({
name: 'sentry-sveltekit-release-management-plugin',
apply: 'build',
enforce: 'post',
closeBundle: expect.any(Function),
});
});

it('calls the original release management plugin to start the release creation pipeline', async () => {
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-release-management-plugin');
// @ts-expect-error this function exists!
await plugin.closeBundle();
expect(mockedViteReleaseManagementPlugin.writeBundle).toHaveBeenCalledTimes(1);
});

it('catches errors during release creation', async () => {
mockedViteReleaseManagementPlugin.writeBundle.mockImplementationOnce(() => {
throw new Error('test error');
});

const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementationOnce(() => {});

const plugin = await getSentryViteSubPlugin('sentry-sveltekit-release-management-plugin');

// @ts-expect-error this function exists!
expect(plugin.closeBundle).not.toThrow();

// @ts-expect-error this function exists!
await plugin.closeBundle();

expect(consoleWarnSpy).toHaveBeenCalledWith(
expect.stringContaining('Failed to upload release data'),
expect.any(Error),
);
});

it('also works correctly if the original release management plugin has its old name', async () => {
const currentName = mockedViteReleaseManagementPlugin.name;
mockedViteReleaseManagementPlugin.name = 'sentry-debug-id-upload-plugin';

const plugin = await getSentryViteSubPlugin('sentry-sveltekit-release-management-plugin');

// @ts-expect-error this function exists!
await plugin.closeBundle();

expect(mockedViteReleaseManagementPlugin.writeBundle).toHaveBeenCalledTimes(1);

mockedViteReleaseManagementPlugin.name = currentName;
});
});

describe('Custom file deletion plugin', () => {
it('has the expected hooks and properties', async () => {
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-file-deletion-plugin');

expect(plugin).toEqual({
name: 'sentry-sveltekit-file-deletion-plugin',
apply: 'build',
enforce: 'post',
closeBundle: expect.any(Function),
});
});

it('calls the original file deletion plugin to delete files', async () => {
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-file-deletion-plugin');
// @ts-expect-error this function exists!
await plugin.closeBundle();
expect(mockedFileDeletionPlugin.writeBundle).toHaveBeenCalledTimes(1);
});

it('catches errors during file deletion', async () => {
mockedFileDeletionPlugin.writeBundle.mockImplementationOnce(() => {
throw new Error('test error');
});

const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementationOnce(() => {});

const plugin = await getSentryViteSubPlugin('sentry-sveltekit-file-deletion-plugin');

// @ts-expect-error this function exists!
expect(plugin.closeBundle).not.toThrow();

// @ts-expect-error this function exists!
await plugin.closeBundle();

expect(consoleWarnSpy).toHaveBeenCalledWith(
expect.stringContaining('Failed to delete source maps'),
expect.any(Error),
);
});
});
});
, '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
89 changes: 85 additions & 4 deletions packages/sveltekit/src/vite/sourceMaps.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -76,6 +76,14 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
plugin => plugin.name === 'sentry-vite-debug-id-upload-plugin',
);

const sentryViteFileDeletionPlugin = sentryPlugins.find(plugin => plugin.name === 'sentry-file-deletion-plugin');

const sentryViteReleaseManagementPlugin = sentryPlugins.find(
// sentry-debug-id-upload-plugin was the old (misleading) name of the plugin
// sentry-release-management-plugin is the new name
plugin => plugin.name === 'sentry-debug-id-upload-plugin' || plugin.name === 'sentry-release-management-plugin',
);

if (!sentryViteDebugIdUploadPlugin) {
debug &&
// eslint-disable-next-line no-console
Expand All@@ -85,7 +93,33 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
return sentryPlugins;
}

const restOfSentryVitePlugins = sentryPlugins.filter(plugin => plugin.name !== 'sentry-vite-debug-id-upload-plugin');
if (!sentryViteFileDeletionPlugin) {
debug &&
// eslint-disable-next-line no-console
console.warn(
'sentry-file-deletion-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
);
return sentryPlugins;
}

if (!sentryViteReleaseManagementPlugin) {
debug &&
// eslint-disable-next-line no-console
console.warn(
'sentry-release-management-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
);
return sentryPlugins;
}

const unchangedSentryVitePlugins = sentryPlugins.filter(
plugin =>
![
'sentry-vite-debug-id-upload-plugin',
'sentry-file-deletion-plugin',
'sentry-release-management-plugin', // new name of release management plugin
'sentry-debug-id-upload-plugin', // old name of release management plugin
].includes(plugin.name),
);

let isSSRBuild = true;

Expand All@@ -95,8 +129,8 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
__sentry_sveltekit_output_dir: outputDir,
};

const customPlugin: Plugin = {
name: 'sentry-upload-sveltekit-source-maps',
const customDebugIdUploadPlugin: Plugin = {
name: 'sentry-sveltekit-debug-id-upload-plugin',
apply: 'build', // only apply this plugin at build time
enforce: 'post', // this needs to be set to post, otherwise we don't pick up the output from the SvelteKit adapter

Expand DownExpand Up@@ -248,7 +282,54 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
},
};

return [...restOfSentryVitePlugins, customPlugin];
// The file deletion plugin is originally called in `writeBundle`.
// We need to call it in `closeBundle` though, because we also postpone
// the upload step to `closeBundle`
const customFileDeletionPlugin: Plugin = {
name: 'sentry-sveltekit-file-deletion-plugin',
apply: 'build', // only apply this plugin at build time
enforce: 'post',
closeBundle: async () => {
if (!isSSRBuild) {
return;
}

const writeBundleFn = sentryViteFileDeletionPlugin?.writeBundle;
if (typeof writeBundleFn === 'function') {
// This is fine though, because the original method doesn't consume any arguments in its `writeBundle` callback.
const outDir = path.resolve(process.cwd(), outputDir);
try {
// @ts-expect-error - the writeBundle hook expects two args we can't pass in here (they're only available in `writeBundle`)
await writeBundleFn({ dir: outDir });
} catch (e) {
// eslint-disable-next-line no-console
console.warn('Failed to delete source maps:', e);
}
}
},
};

const customReleaseManagementPlugin: Plugin = {
name: 'sentry-sveltekit-release-management-plugin',
apply: 'build', // only apply this plugin at build time
enforce: 'post',
closeBundle: async () => {
try {
// @ts-expect-error - this hook exists on the plugin!
await sentryViteReleaseManagementPlugin.writeBundle();
} catch (e) {
// eslint-disable-next-line no-console
console.warn('[Source Maps Plugin] Failed to upload release data:', e);
}
},
};

return [
...unchangedSentryVitePlugins,
customReleaseManagementPlugin,
customDebugIdUploadPlugin,
customFileDeletionPlugin,
];
}

function getFiles(dir: string): string[] {
Expand Down
10 changes: 6 additions & 4 deletions packages/sveltekit/test/vite/sentrySvelteKitPlugins.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -55,11 +55,13 @@ describe('sentrySvelteKit()', () => {
// default source maps plugins:
'sentry-telemetry-plugin',
'sentry-vite-release-injection-plugin',
'sentry-debug-id-upload-plugin',
'sentry-vite-debug-id-injection-plugin',
'sentry-file-deletion-plugin',
// custom release plugin:
'sentry-sveltekit-release-management-plugin',
// custom source maps plugin:
'sentry-upload-sveltekit-source-maps',
'sentry-sveltekit-debug-id-upload-plugin',
// custom deletion plugin
'sentry-sveltekit-file-deletion-plugin',
]);
});

Expand All@@ -76,7 +78,7 @@ describe('sentrySvelteKit()', () => {
const instrumentPlugin = plugins[0];

expect(plugins).toHaveLength(1);
expect(instrumentPlugin.name).toEqual('sentry-auto-instrumentation');
expect(instrumentPlugin?.name).toEqual('sentry-auto-instrumentation');

process.env.NODE_ENV = previousEnv;
});
Expand Down
146 changes: 129 additions & 17 deletions packages/sveltekit/test/vite/sourceMaps.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,17 +3,31 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { Plugin } from 'vite';
import { makeCustomSentryVitePlugins } from '../../src/vite/sourceMaps';

const mockedSentryVitePlugin = {
const mockedViteDebugIdUploadPlugin = {
name: 'sentry-vite-debug-id-upload-plugin',
writeBundle: vi.fn(),
};

const mockedViteReleaseManagementPlugin = {
name: 'sentry-release-management-plugin',
writeBundle: vi.fn(),
};

const mockedFileDeletionPlugin = {
name: 'sentry-file-deletion-plugin',
writeBundle: vi.fn(),
};

vi.mock('@sentry/vite-plugin', async () => {
const original = (await vi.importActual('@sentry/vite-plugin')) as any;

return {
...original,
sentryVitePlugin: () => [mockedSentryVitePlugin],
sentryVitePlugin: () => [
mockedViteReleaseManagementPlugin,
mockedViteDebugIdUploadPlugin,
mockedFileDeletionPlugin,
],
};
});

Expand All@@ -30,20 +44,22 @@ beforeEach(() => {
vi.clearAllMocks();
});

async function getCustomSentryViteUploadSourcemapsPlugin(): Promise<Plugin | undefined> {
async function getSentryViteSubPlugin(name: string): Promise<Plugin | undefined> {
const plugins = await makeCustomSentryVitePlugins({
authToken: 'token',
org: 'org',
project: 'project',
adapter: 'other',
});
return plugins.find(plugin => plugin.name === 'sentry-upload-sveltekit-source-maps');

return plugins.find(plugin => plugin.name === name);
}

describe('makeCustomSentryVitePlugin()', () => {
it('returns the custom sentry source maps plugin', async () => {
const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
expect(plugin?.name).toEqual('sentry-upload-sveltekit-source-maps');
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');

expect(plugin?.name).toEqual('sentry-sveltekit-debug-id-upload-plugin');
expect(plugin?.apply).toEqual('build');
expect(plugin?.enforce).toEqual('post');

Expand All@@ -58,9 +74,9 @@ describe('makeCustomSentryVitePlugin()', () => {
expect(plugin?.writeBundle).toBeUndefined();
});

describe('Custom sentry vite plugin', () => {
describe('Custom debug id source maps plugin plugin', () => {
it('enables source map generation', async () => {
const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');
// @ts-expect-error this function exists!
const sentrifiedConfig = plugin.config({ build: { foo: {} }, test: {} });
expect(sentrifiedConfig).toEqual({
Expand All@@ -73,7 +89,7 @@ describe('makeCustomSentryVitePlugin()', () => {
});

it('injects the output dir into the server hooks file', async () => {
const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');
// @ts-expect-error this function exists!
const transformOutput = await plugin.transform('foo', '/src/hooks.server.ts');
const transformedCode = transformOutput.code;
Expand All@@ -84,34 +100,34 @@ describe('makeCustomSentryVitePlugin()', () => {
});

it('uploads source maps during the SSR build', async () => {
const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');
// @ts-expect-error this function exists!
plugin.configResolved({ build: { ssr: true } });
// @ts-expect-error this function exists!
await plugin.closeBundle();
expect(mockedSentryVitePlugin.writeBundle).toHaveBeenCalledTimes(1);
expect(mockedViteDebugIdUploadPlugin.writeBundle).toHaveBeenCalledTimes(1);
});

it("doesn't upload source maps during the non-SSR builds", async () => {
const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');

// @ts-expect-error this function exists!
plugin.configResolved({ build: { ssr: false } });
// @ts-expect-error this function exists!
await plugin.closeBundle();
expect(mockedSentryVitePlugin.writeBundle).not.toHaveBeenCalled();
expect(mockedViteDebugIdUploadPlugin.writeBundle).not.toHaveBeenCalled();
});
});

it('catches errors while uploading source maps', async () => {
mockedSentryVitePlugin.writeBundle.mockImplementationOnce(() => {
mockedViteDebugIdUploadPlugin.writeBundle.mockImplementationOnce(() => {
throw new Error('test error');
});

const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementationOnce(() => {});
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementationOnce(() => {});

const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');

// @ts-expect-error this function exists!
expect(plugin.closeBundle).not.toThrow();
Expand All@@ -124,4 +140,100 @@ describe('makeCustomSentryVitePlugin()', () => {
expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining('Failed to upload source maps'));
expect(consoleLogSpy).toHaveBeenCalled();
});

describe('Custom release management plugin', () => {
it('has the expected hooks and properties', async () => {
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-release-management-plugin');

expect(plugin).toEqual({
name: 'sentry-sveltekit-release-management-plugin',
apply: 'build',
enforce: 'post',
closeBundle: expect.any(Function),
});
});

it('calls the original release management plugin to start the release creation pipeline', async () => {
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-release-management-plugin');
// @ts-expect-error this function exists!
await plugin.closeBundle();
expect(mockedViteReleaseManagementPlugin.writeBundle).toHaveBeenCalledTimes(1);
});

it('catches errors during release creation', async () => {
mockedViteReleaseManagementPlugin.writeBundle.mockImplementationOnce(() => {
throw new Error('test error');
});

const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementationOnce(() => {});

const plugin = await getSentryViteSubPlugin('sentry-sveltekit-release-management-plugin');

// @ts-expect-error this function exists!
expect(plugin.closeBundle).not.toThrow();

// @ts-expect-error this function exists!
await plugin.closeBundle();

expect(consoleWarnSpy).toHaveBeenCalledWith(
expect.stringContaining('Failed to upload release data'),
expect.any(Error),
);
});

it('also works correctly if the original release management plugin has its old name', async () => {
const currentName = mockedViteReleaseManagementPlugin.name;
mockedViteReleaseManagementPlugin.name = 'sentry-debug-id-upload-plugin';

const plugin = await getSentryViteSubPlugin('sentry-sveltekit-release-management-plugin');

// @ts-expect-error this function exists!
await plugin.closeBundle();

expect(mockedViteReleaseManagementPlugin.writeBundle).toHaveBeenCalledTimes(1);

mockedViteReleaseManagementPlugin.name = currentName;
});
});

describe('Custom file deletion plugin', () => {
it('has the expected hooks and properties', async () => {
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-file-deletion-plugin');

expect(plugin).toEqual({
name: 'sentry-sveltekit-file-deletion-plugin',
apply: 'build',
enforce: 'post',
closeBundle: expect.any(Function),
});
});

it('calls the original file deletion plugin to delete files', async () => {
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-file-deletion-plugin');
// @ts-expect-error this function exists!
await plugin.closeBundle();
expect(mockedFileDeletionPlugin.writeBundle).toHaveBeenCalledTimes(1);
});

it('catches errors during file deletion', async () => {
mockedFileDeletionPlugin.writeBundle.mockImplementationOnce(() => {
throw new Error('test error');
});

const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementationOnce(() => {});

const plugin = await getSentryViteSubPlugin('sentry-sveltekit-file-deletion-plugin');

// @ts-expect-error this function exists!
expect(plugin.closeBundle).not.toThrow();

// @ts-expect-error this function exists!
await plugin.closeBundle();

expect(consoleWarnSpy).toHaveBeenCalledWith(
expect.stringContaining('Failed to delete source maps'),
expect.any(Error),
);
});
});
});
, '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
89 changes: 85 additions & 4 deletions packages/sveltekit/src/vite/sourceMaps.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -76,6 +76,14 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
plugin => plugin.name === 'sentry-vite-debug-id-upload-plugin',
);

const sentryViteFileDeletionPlugin = sentryPlugins.find(plugin => plugin.name === 'sentry-file-deletion-plugin');

const sentryViteReleaseManagementPlugin = sentryPlugins.find(
// sentry-debug-id-upload-plugin was the old (misleading) name of the plugin
// sentry-release-management-plugin is the new name
plugin => plugin.name === 'sentry-debug-id-upload-plugin' || plugin.name === 'sentry-release-management-plugin',
);

if (!sentryViteDebugIdUploadPlugin) {
debug &&
// eslint-disable-next-line no-console
Expand All@@ -85,7 +93,33 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
return sentryPlugins;
}

const restOfSentryVitePlugins = sentryPlugins.filter(plugin => plugin.name !== 'sentry-vite-debug-id-upload-plugin');
if (!sentryViteFileDeletionPlugin) {
debug &&
// eslint-disable-next-line no-console
console.warn(
'sentry-file-deletion-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
);
return sentryPlugins;
}

if (!sentryViteReleaseManagementPlugin) {
debug &&
// eslint-disable-next-line no-console
console.warn(
'sentry-release-management-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
);
return sentryPlugins;
}

const unchangedSentryVitePlugins = sentryPlugins.filter(
plugin =>
![
'sentry-vite-debug-id-upload-plugin',
'sentry-file-deletion-plugin',
'sentry-release-management-plugin', // new name of release management plugin
'sentry-debug-id-upload-plugin', // old name of release management plugin
].includes(plugin.name),
);

let isSSRBuild = true;

Expand All@@ -95,8 +129,8 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
__sentry_sveltekit_output_dir: outputDir,
};

const customPlugin: Plugin = {
name: 'sentry-upload-sveltekit-source-maps',
const customDebugIdUploadPlugin: Plugin = {
name: 'sentry-sveltekit-debug-id-upload-plugin',
apply: 'build', // only apply this plugin at build time
enforce: 'post', // this needs to be set to post, otherwise we don't pick up the output from the SvelteKit adapter

Expand DownExpand Up@@ -248,7 +282,54 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
},
};

return [...restOfSentryVitePlugins, customPlugin];
// The file deletion plugin is originally called in `writeBundle`.
// We need to call it in `closeBundle` though, because we also postpone
// the upload step to `closeBundle`
const customFileDeletionPlugin: Plugin = {
name: 'sentry-sveltekit-file-deletion-plugin',
apply: 'build', // only apply this plugin at build time
enforce: 'post',
closeBundle: async () => {
if (!isSSRBuild) {
return;
}

const writeBundleFn = sentryViteFileDeletionPlugin?.writeBundle;
if (typeof writeBundleFn === 'function') {
// This is fine though, because the original method doesn't consume any arguments in its `writeBundle` callback.
const outDir = path.resolve(process.cwd(), outputDir);
try {
// @ts-expect-error - the writeBundle hook expects two args we can't pass in here (they're only available in `writeBundle`)
await writeBundleFn({ dir: outDir });
} catch (e) {
// eslint-disable-next-line no-console
console.warn('Failed to delete source maps:', e);
}
}
},
};

const customReleaseManagementPlugin: Plugin = {
name: 'sentry-sveltekit-release-management-plugin',
apply: 'build', // only apply this plugin at build time
enforce: 'post',
closeBundle: async () => {
try {
// @ts-expect-error - this hook exists on the plugin!
await sentryViteReleaseManagementPlugin.writeBundle();
} catch (e) {
// eslint-disable-next-line no-console
console.warn('[Source Maps Plugin] Failed to upload release data:', e);
}
},
};

return [
...unchangedSentryVitePlugins,
customReleaseManagementPlugin,
customDebugIdUploadPlugin,
customFileDeletionPlugin,
];
}

function getFiles(dir: string): string[] {
Expand Down
10 changes: 6 additions & 4 deletions packages/sveltekit/test/vite/sentrySvelteKitPlugins.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -55,11 +55,13 @@ describe('sentrySvelteKit()', () => {
// default source maps plugins:
'sentry-telemetry-plugin',
'sentry-vite-release-injection-plugin',
'sentry-debug-id-upload-plugin',
'sentry-vite-debug-id-injection-plugin',
'sentry-file-deletion-plugin',
// custom release plugin:
'sentry-sveltekit-release-management-plugin',
// custom source maps plugin:
'sentry-upload-sveltekit-source-maps',
'sentry-sveltekit-debug-id-upload-plugin',
// custom deletion plugin
'sentry-sveltekit-file-deletion-plugin',
]);
});

Expand All@@ -76,7 +78,7 @@ describe('sentrySvelteKit()', () => {
const instrumentPlugin = plugins[0];

expect(plugins).toHaveLength(1);
expect(instrumentPlugin.name).toEqual('sentry-auto-instrumentation');
expect(instrumentPlugin?.name).toEqual('sentry-auto-instrumentation');

process.env.NODE_ENV = previousEnv;
});
Expand Down
146 changes: 129 additions & 17 deletions packages/sveltekit/test/vite/sourceMaps.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,17 +3,31 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { Plugin } from 'vite';
import { makeCustomSentryVitePlugins } from '../../src/vite/sourceMaps';

const mockedSentryVitePlugin = {
const mockedViteDebugIdUploadPlugin = {
name: 'sentry-vite-debug-id-upload-plugin',
writeBundle: vi.fn(),
};

const mockedViteReleaseManagementPlugin = {
name: 'sentry-release-management-plugin',
writeBundle: vi.fn(),
};

const mockedFileDeletionPlugin = {
name: 'sentry-file-deletion-plugin',
writeBundle: vi.fn(),
};

vi.mock('@sentry/vite-plugin', async () => {
const original = (await vi.importActual('@sentry/vite-plugin')) as any;

return {
...original,
sentryVitePlugin: () => [mockedSentryVitePlugin],
sentryVitePlugin: () => [
mockedViteReleaseManagementPlugin,
mockedViteDebugIdUploadPlugin,
mockedFileDeletionPlugin,
],
};
});

Expand All@@ -30,20 +44,22 @@ beforeEach(() => {
vi.clearAllMocks();
});

async function getCustomSentryViteUploadSourcemapsPlugin(): Promise<Plugin | undefined> {
async function getSentryViteSubPlugin(name: string): Promise<Plugin | undefined> {
const plugins = await makeCustomSentryVitePlugins({
authToken: 'token',
org: 'org',
project: 'project',
adapter: 'other',
});
return plugins.find(plugin => plugin.name === 'sentry-upload-sveltekit-source-maps');

return plugins.find(plugin => plugin.name === name);
}

describe('makeCustomSentryVitePlugin()', () => {
it('returns the custom sentry source maps plugin', async () => {
const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
expect(plugin?.name).toEqual('sentry-upload-sveltekit-source-maps');
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');

expect(plugin?.name).toEqual('sentry-sveltekit-debug-id-upload-plugin');
expect(plugin?.apply).toEqual('build');
expect(plugin?.enforce).toEqual('post');

Expand All@@ -58,9 +74,9 @@ describe('makeCustomSentryVitePlugin()', () => {
expect(plugin?.writeBundle).toBeUndefined();
});

describe('Custom sentry vite plugin', () => {
describe('Custom debug id source maps plugin plugin', () => {
it('enables source map generation', async () => {
const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');
// @ts-expect-error this function exists!
const sentrifiedConfig = plugin.config({ build: { foo: {} }, test: {} });
expect(sentrifiedConfig).toEqual({
Expand All@@ -73,7 +89,7 @@ describe('makeCustomSentryVitePlugin()', () => {
});

it('injects the output dir into the server hooks file', async () => {
const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');
// @ts-expect-error this function exists!
const transformOutput = await plugin.transform('foo', '/src/hooks.server.ts');
const transformedCode = transformOutput.code;
Expand All@@ -84,34 +100,34 @@ describe('makeCustomSentryVitePlugin()', () => {
});

it('uploads source maps during the SSR build', async () => {
const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');
// @ts-expect-error this function exists!
plugin.configResolved({ build: { ssr: true } });
// @ts-expect-error this function exists!
await plugin.closeBundle();
expect(mockedSentryVitePlugin.writeBundle).toHaveBeenCalledTimes(1);
expect(mockedViteDebugIdUploadPlugin.writeBundle).toHaveBeenCalledTimes(1);
});

it("doesn't upload source maps during the non-SSR builds", async () => {
const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');

// @ts-expect-error this function exists!
plugin.configResolved({ build: { ssr: false } });
// @ts-expect-error this function exists!
await plugin.closeBundle();
expect(mockedSentryVitePlugin.writeBundle).not.toHaveBeenCalled();
expect(mockedViteDebugIdUploadPlugin.writeBundle).not.toHaveBeenCalled();
});
});

it('catches errors while uploading source maps', async () => {
mockedSentryVitePlugin.writeBundle.mockImplementationOnce(() => {
mockedViteDebugIdUploadPlugin.writeBundle.mockImplementationOnce(() => {
throw new Error('test error');
});

const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementationOnce(() => {});
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementationOnce(() => {});

const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');

// @ts-expect-error this function exists!
expect(plugin.closeBundle).not.toThrow();
Expand All@@ -124,4 +140,100 @@ describe('makeCustomSentryVitePlugin()', () => {
expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining('Failed to upload source maps'));
expect(consoleLogSpy).toHaveBeenCalled();
});

describe('Custom release management plugin', () => {
it('has the expected hooks and properties', async () => {
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-release-management-plugin');

expect(plugin).toEqual({
name: 'sentry-sveltekit-release-management-plugin',
apply: 'build',
enforce: 'post',
closeBundle: expect.any(Function),
});
});

it('calls the original release management plugin to start the release creation pipeline', async () => {
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-release-management-plugin');
// @ts-expect-error this function exists!
await plugin.closeBundle();
expect(mockedViteReleaseManagementPlugin.writeBundle).toHaveBeenCalledTimes(1);
});

it('catches errors during release creation', async () => {
mockedViteReleaseManagementPlugin.writeBundle.mockImplementationOnce(() => {
throw new Error('test error');
});

const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementationOnce(() => {});

const plugin = await getSentryViteSubPlugin('sentry-sveltekit-release-management-plugin');

// @ts-expect-error this function exists!
expect(plugin.closeBundle).not.toThrow();

// @ts-expect-error this function exists!
await plugin.closeBundle();

expect(consoleWarnSpy).toHaveBeenCalledWith(
expect.stringContaining('Failed to upload release data'),
expect.any(Error),
);
});

it('also works correctly if the original release management plugin has its old name', async () => {
const currentName = mockedViteReleaseManagementPlugin.name;
mockedViteReleaseManagementPlugin.name = 'sentry-debug-id-upload-plugin';

const plugin = await getSentryViteSubPlugin('sentry-sveltekit-release-management-plugin');

// @ts-expect-error this function exists!
await plugin.closeBundle();

expect(mockedViteReleaseManagementPlugin.writeBundle).toHaveBeenCalledTimes(1);

mockedViteReleaseManagementPlugin.name = currentName;
});
});

describe('Custom file deletion plugin', () => {
it('has the expected hooks and properties', async () => {
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-file-deletion-plugin');

expect(plugin).toEqual({
name: 'sentry-sveltekit-file-deletion-plugin',
apply: 'build',
enforce: 'post',
closeBundle: expect.any(Function),
});
});

it('calls the original file deletion plugin to delete files', async () => {
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-file-deletion-plugin');
// @ts-expect-error this function exists!
await plugin.closeBundle();
expect(mockedFileDeletionPlugin.writeBundle).toHaveBeenCalledTimes(1);
});

it('catches errors during file deletion', async () => {
mockedFileDeletionPlugin.writeBundle.mockImplementationOnce(() => {
throw new Error('test error');
});

const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementationOnce(() => {});

const plugin = await getSentryViteSubPlugin('sentry-sveltekit-file-deletion-plugin');

// @ts-expect-error this function exists!
expect(plugin.closeBundle).not.toThrow();

// @ts-expect-error this function exists!
await plugin.closeBundle();

expect(consoleWarnSpy).toHaveBeenCalledWith(
expect.stringContaining('Failed to delete source maps'),
expect.any(Error),
);
});
});
});
, '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
89 changes: 85 additions & 4 deletions packages/sveltekit/src/vite/sourceMaps.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -76,6 +76,14 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
plugin => plugin.name === 'sentry-vite-debug-id-upload-plugin',
);

const sentryViteFileDeletionPlugin = sentryPlugins.find(plugin => plugin.name === 'sentry-file-deletion-plugin');

const sentryViteReleaseManagementPlugin = sentryPlugins.find(
// sentry-debug-id-upload-plugin was the old (misleading) name of the plugin
// sentry-release-management-plugin is the new name
plugin => plugin.name === 'sentry-debug-id-upload-plugin' || plugin.name === 'sentry-release-management-plugin',
);

if (!sentryViteDebugIdUploadPlugin) {
debug &&
// eslint-disable-next-line no-console
Expand All@@ -85,7 +93,33 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
return sentryPlugins;
}

const restOfSentryVitePlugins = sentryPlugins.filter(plugin => plugin.name !== 'sentry-vite-debug-id-upload-plugin');
if (!sentryViteFileDeletionPlugin) {
debug &&
// eslint-disable-next-line no-console
console.warn(
'sentry-file-deletion-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
);
return sentryPlugins;
}

if (!sentryViteReleaseManagementPlugin) {
debug &&
// eslint-disable-next-line no-console
console.warn(
'sentry-release-management-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
);
return sentryPlugins;
}

const unchangedSentryVitePlugins = sentryPlugins.filter(
plugin =>
![
'sentry-vite-debug-id-upload-plugin',
'sentry-file-deletion-plugin',
'sentry-release-management-plugin', // new name of release management plugin
'sentry-debug-id-upload-plugin', // old name of release management plugin
].includes(plugin.name),
);

let isSSRBuild = true;

Expand All@@ -95,8 +129,8 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
__sentry_sveltekit_output_dir: outputDir,
};

const customPlugin: Plugin = {
name: 'sentry-upload-sveltekit-source-maps',
const customDebugIdUploadPlugin: Plugin = {
name: 'sentry-sveltekit-debug-id-upload-plugin',
apply: 'build', // only apply this plugin at build time
enforce: 'post', // this needs to be set to post, otherwise we don't pick up the output from the SvelteKit adapter

Expand DownExpand Up@@ -248,7 +282,54 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
},
};

return [...restOfSentryVitePlugins, customPlugin];
// The file deletion plugin is originally called in `writeBundle`.
// We need to call it in `closeBundle` though, because we also postpone
// the upload step to `closeBundle`
const customFileDeletionPlugin: Plugin = {
name: 'sentry-sveltekit-file-deletion-plugin',
apply: 'build', // only apply this plugin at build time
enforce: 'post',
closeBundle: async () => {
if (!isSSRBuild) {
return;
}

const writeBundleFn = sentryViteFileDeletionPlugin?.writeBundle;
if (typeof writeBundleFn === 'function') {
// This is fine though, because the original method doesn't consume any arguments in its `writeBundle` callback.
const outDir = path.resolve(process.cwd(), outputDir);
try {
// @ts-expect-error - the writeBundle hook expects two args we can't pass in here (they're only available in `writeBundle`)
await writeBundleFn({ dir: outDir });
} catch (e) {
// eslint-disable-next-line no-console
console.warn('Failed to delete source maps:', e);
}
}
},
};

const customReleaseManagementPlugin: Plugin = {
name: 'sentry-sveltekit-release-management-plugin',
apply: 'build', // only apply this plugin at build time
enforce: 'post',
closeBundle: async () => {
try {
// @ts-expect-error - this hook exists on the plugin!
await sentryViteReleaseManagementPlugin.writeBundle();
} catch (e) {
// eslint-disable-next-line no-console
console.warn('[Source Maps Plugin] Failed to upload release data:', e);
}
},
};

return [
...unchangedSentryVitePlugins,
customReleaseManagementPlugin,
customDebugIdUploadPlugin,
customFileDeletionPlugin,
];
}

function getFiles(dir: string): string[] {
Expand Down
10 changes: 6 additions & 4 deletions packages/sveltekit/test/vite/sentrySvelteKitPlugins.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -55,11 +55,13 @@ describe('sentrySvelteKit()', () => {
// default source maps plugins:
'sentry-telemetry-plugin',
'sentry-vite-release-injection-plugin',
'sentry-debug-id-upload-plugin',
'sentry-vite-debug-id-injection-plugin',
'sentry-file-deletion-plugin',
// custom release plugin:
'sentry-sveltekit-release-management-plugin',
// custom source maps plugin:
'sentry-upload-sveltekit-source-maps',
'sentry-sveltekit-debug-id-upload-plugin',
// custom deletion plugin
'sentry-sveltekit-file-deletion-plugin',
]);
});

Expand All@@ -76,7 +78,7 @@ describe('sentrySvelteKit()', () => {
const instrumentPlugin = plugins[0];

expect(plugins).toHaveLength(1);
expect(instrumentPlugin.name).toEqual('sentry-auto-instrumentation');
expect(instrumentPlugin?.name).toEqual('sentry-auto-instrumentation');

process.env.NODE_ENV = previousEnv;
});
Expand Down
146 changes: 129 additions & 17 deletions packages/sveltekit/test/vite/sourceMaps.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,17 +3,31 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { Plugin } from 'vite';
import { makeCustomSentryVitePlugins } from '../../src/vite/sourceMaps';

const mockedSentryVitePlugin = {
const mockedViteDebugIdUploadPlugin = {
name: 'sentry-vite-debug-id-upload-plugin',
writeBundle: vi.fn(),
};

const mockedViteReleaseManagementPlugin = {
name: 'sentry-release-management-plugin',
writeBundle: vi.fn(),
};

const mockedFileDeletionPlugin = {
name: 'sentry-file-deletion-plugin',
writeBundle: vi.fn(),
};

vi.mock('@sentry/vite-plugin', async () => {
const original = (await vi.importActual('@sentry/vite-plugin')) as any;

return {
...original,
sentryVitePlugin: () => [mockedSentryVitePlugin],
sentryVitePlugin: () => [
mockedViteReleaseManagementPlugin,
mockedViteDebugIdUploadPlugin,
mockedFileDeletionPlugin,
],
};
});

Expand All@@ -30,20 +44,22 @@ beforeEach(() => {
vi.clearAllMocks();
});

async function getCustomSentryViteUploadSourcemapsPlugin(): Promise<Plugin | undefined> {
async function getSentryViteSubPlugin(name: string): Promise<Plugin | undefined> {
const plugins = await makeCustomSentryVitePlugins({
authToken: 'token',
org: 'org',
project: 'project',
adapter: 'other',
});
return plugins.find(plugin => plugin.name === 'sentry-upload-sveltekit-source-maps');

return plugins.find(plugin => plugin.name === name);
}

describe('makeCustomSentryVitePlugin()', () => {
it('returns the custom sentry source maps plugin', async () => {
const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
expect(plugin?.name).toEqual('sentry-upload-sveltekit-source-maps');
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');

expect(plugin?.name).toEqual('sentry-sveltekit-debug-id-upload-plugin');
expect(plugin?.apply).toEqual('build');
expect(plugin?.enforce).toEqual('post');

Expand All@@ -58,9 +74,9 @@ describe('makeCustomSentryVitePlugin()', () => {
expect(plugin?.writeBundle).toBeUndefined();
});

describe('Custom sentry vite plugin', () => {
describe('Custom debug id source maps plugin plugin', () => {
it('enables source map generation', async () => {
const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');
// @ts-expect-error this function exists!
const sentrifiedConfig = plugin.config({ build: { foo: {} }, test: {} });
expect(sentrifiedConfig).toEqual({
Expand All@@ -73,7 +89,7 @@ describe('makeCustomSentryVitePlugin()', () => {
});

it('injects the output dir into the server hooks file', async () => {
const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');
// @ts-expect-error this function exists!
const transformOutput = await plugin.transform('foo', '/src/hooks.server.ts');
const transformedCode = transformOutput.code;
Expand All@@ -84,34 +100,34 @@ describe('makeCustomSentryVitePlugin()', () => {
});

it('uploads source maps during the SSR build', async () => {
const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');
// @ts-expect-error this function exists!
plugin.configResolved({ build: { ssr: true } });
// @ts-expect-error this function exists!
await plugin.closeBundle();
expect(mockedSentryVitePlugin.writeBundle).toHaveBeenCalledTimes(1);
expect(mockedViteDebugIdUploadPlugin.writeBundle).toHaveBeenCalledTimes(1);
});

it("doesn't upload source maps during the non-SSR builds", async () => {
const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');

// @ts-expect-error this function exists!
plugin.configResolved({ build: { ssr: false } });
// @ts-expect-error this function exists!
await plugin.closeBundle();
expect(mockedSentryVitePlugin.writeBundle).not.toHaveBeenCalled();
expect(mockedViteDebugIdUploadPlugin.writeBundle).not.toHaveBeenCalled();
});
});

it('catches errors while uploading source maps', async () => {
mockedSentryVitePlugin.writeBundle.mockImplementationOnce(() => {
mockedViteDebugIdUploadPlugin.writeBundle.mockImplementationOnce(() => {
throw new Error('test error');
});

const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementationOnce(() => {});
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementationOnce(() => {});

const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');

// @ts-expect-error this function exists!
expect(plugin.closeBundle).not.toThrow();
Expand All@@ -124,4 +140,100 @@ describe('makeCustomSentryVitePlugin()', () => {
expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining('Failed to upload source maps'));
expect(consoleLogSpy).toHaveBeenCalled();
});

describe('Custom release management plugin', () => {
it('has the expected hooks and properties', async () => {
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-release-management-plugin');

expect(plugin).toEqual({
name: 'sentry-sveltekit-release-management-plugin',
apply: 'build',
enforce: 'post',
closeBundle: expect.any(Function),
});
});

it('calls the original release management plugin to start the release creation pipeline', async () => {
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-release-management-plugin');
// @ts-expect-error this function exists!
await plugin.closeBundle();
expect(mockedViteReleaseManagementPlugin.writeBundle).toHaveBeenCalledTimes(1);
});

it('catches errors during release creation', async () => {
mockedViteReleaseManagementPlugin.writeBundle.mockImplementationOnce(() => {
throw new Error('test error');
});

const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementationOnce(() => {});

const plugin = await getSentryViteSubPlugin('sentry-sveltekit-release-management-plugin');

// @ts-expect-error this function exists!
expect(plugin.closeBundle).not.toThrow();

// @ts-expect-error this function exists!
await plugin.closeBundle();

expect(consoleWarnSpy).toHaveBeenCalledWith(
expect.stringContaining('Failed to upload release data'),
expect.any(Error),
);
});

it('also works correctly if the original release management plugin has its old name', async () => {
const currentName = mockedViteReleaseManagementPlugin.name;
mockedViteReleaseManagementPlugin.name = 'sentry-debug-id-upload-plugin';

const plugin = await getSentryViteSubPlugin('sentry-sveltekit-release-management-plugin');

// @ts-expect-error this function exists!
await plugin.closeBundle();

expect(mockedViteReleaseManagementPlugin.writeBundle).toHaveBeenCalledTimes(1);

mockedViteReleaseManagementPlugin.name = currentName;
});
});

describe('Custom file deletion plugin', () => {
it('has the expected hooks and properties', async () => {
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-file-deletion-plugin');

expect(plugin).toEqual({
name: 'sentry-sveltekit-file-deletion-plugin',
apply: 'build',
enforce: 'post',
closeBundle: expect.any(Function),
});
});

it('calls the original file deletion plugin to delete files', async () => {
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-file-deletion-plugin');
// @ts-expect-error this function exists!
await plugin.closeBundle();
expect(mockedFileDeletionPlugin.writeBundle).toHaveBeenCalledTimes(1);
});

it('catches errors during file deletion', async () => {
mockedFileDeletionPlugin.writeBundle.mockImplementationOnce(() => {
throw new Error('test error');
});

const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementationOnce(() => {});

const plugin = await getSentryViteSubPlugin('sentry-sveltekit-file-deletion-plugin');

// @ts-expect-error this function exists!
expect(plugin.closeBundle).not.toThrow();

// @ts-expect-error this function exists!
await plugin.closeBundle();

expect(consoleWarnSpy).toHaveBeenCalledWith(
expect.stringContaining('Failed to delete source maps'),
expect.any(Error),
);
});
});
});
, '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
89 changes: 85 additions & 4 deletions packages/sveltekit/src/vite/sourceMaps.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -76,6 +76,14 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
plugin => plugin.name === 'sentry-vite-debug-id-upload-plugin',
);

const sentryViteFileDeletionPlugin = sentryPlugins.find(plugin => plugin.name === 'sentry-file-deletion-plugin');

const sentryViteReleaseManagementPlugin = sentryPlugins.find(
// sentry-debug-id-upload-plugin was the old (misleading) name of the plugin
// sentry-release-management-plugin is the new name
plugin => plugin.name === 'sentry-debug-id-upload-plugin' || plugin.name === 'sentry-release-management-plugin',
);

if (!sentryViteDebugIdUploadPlugin) {
debug &&
// eslint-disable-next-line no-console
Expand All@@ -85,7 +93,33 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
return sentryPlugins;
}

const restOfSentryVitePlugins = sentryPlugins.filter(plugin => plugin.name !== 'sentry-vite-debug-id-upload-plugin');
if (!sentryViteFileDeletionPlugin) {
debug &&
// eslint-disable-next-line no-console
console.warn(
'sentry-file-deletion-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
);
return sentryPlugins;
}

if (!sentryViteReleaseManagementPlugin) {
debug &&
// eslint-disable-next-line no-console
console.warn(
'sentry-release-management-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
);
return sentryPlugins;
}

const unchangedSentryVitePlugins = sentryPlugins.filter(
plugin =>
![
'sentry-vite-debug-id-upload-plugin',
'sentry-file-deletion-plugin',
'sentry-release-management-plugin', // new name of release management plugin
'sentry-debug-id-upload-plugin', // old name of release management plugin
].includes(plugin.name),
);

let isSSRBuild = true;

Expand All@@ -95,8 +129,8 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
__sentry_sveltekit_output_dir: outputDir,
};

const customPlugin: Plugin = {
name: 'sentry-upload-sveltekit-source-maps',
const customDebugIdUploadPlugin: Plugin = {
name: 'sentry-sveltekit-debug-id-upload-plugin',
apply: 'build', // only apply this plugin at build time
enforce: 'post', // this needs to be set to post, otherwise we don't pick up the output from the SvelteKit adapter

Expand DownExpand Up@@ -248,7 +282,54 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
},
};

return [...restOfSentryVitePlugins, customPlugin];
// The file deletion plugin is originally called in `writeBundle`.
// We need to call it in `closeBundle` though, because we also postpone
// the upload step to `closeBundle`
const customFileDeletionPlugin: Plugin = {
name: 'sentry-sveltekit-file-deletion-plugin',
apply: 'build', // only apply this plugin at build time
enforce: 'post',
closeBundle: async () => {
if (!isSSRBuild) {
return;
}

const writeBundleFn = sentryViteFileDeletionPlugin?.writeBundle;
if (typeof writeBundleFn === 'function') {
// This is fine though, because the original method doesn't consume any arguments in its `writeBundle` callback.
const outDir = path.resolve(process.cwd(), outputDir);
try {
// @ts-expect-error - the writeBundle hook expects two args we can't pass in here (they're only available in `writeBundle`)
await writeBundleFn({ dir: outDir });
} catch (e) {
// eslint-disable-next-line no-console
console.warn('Failed to delete source maps:', e);
}
}
},
};

const customReleaseManagementPlugin: Plugin = {
name: 'sentry-sveltekit-release-management-plugin',
apply: 'build', // only apply this plugin at build time
enforce: 'post',
closeBundle: async () => {
try {
// @ts-expect-error - this hook exists on the plugin!
await sentryViteReleaseManagementPlugin.writeBundle();
} catch (e) {
// eslint-disable-next-line no-console
console.warn('[Source Maps Plugin] Failed to upload release data:', e);
}
},
};

return [
...unchangedSentryVitePlugins,
customReleaseManagementPlugin,
customDebugIdUploadPlugin,
customFileDeletionPlugin,
];
}

function getFiles(dir: string): string[] {
Expand Down
10 changes: 6 additions & 4 deletions packages/sveltekit/test/vite/sentrySvelteKitPlugins.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -55,11 +55,13 @@ describe('sentrySvelteKit()', () => {
// default source maps plugins:
'sentry-telemetry-plugin',
'sentry-vite-release-injection-plugin',
'sentry-debug-id-upload-plugin',
'sentry-vite-debug-id-injection-plugin',
'sentry-file-deletion-plugin',
// custom release plugin:
'sentry-sveltekit-release-management-plugin',
// custom source maps plugin:
'sentry-upload-sveltekit-source-maps',
'sentry-sveltekit-debug-id-upload-plugin',
// custom deletion plugin
'sentry-sveltekit-file-deletion-plugin',
]);
});

Expand All@@ -76,7 +78,7 @@ describe('sentrySvelteKit()', () => {
const instrumentPlugin = plugins[0];

expect(plugins).toHaveLength(1);
expect(instrumentPlugin.name).toEqual('sentry-auto-instrumentation');
expect(instrumentPlugin?.name).toEqual('sentry-auto-instrumentation');

process.env.NODE_ENV = previousEnv;
});
Expand Down
146 changes: 129 additions & 17 deletions packages/sveltekit/test/vite/sourceMaps.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,17 +3,31 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { Plugin } from 'vite';
import { makeCustomSentryVitePlugins } from '../../src/vite/sourceMaps';

const mockedSentryVitePlugin = {
const mockedViteDebugIdUploadPlugin = {
name: 'sentry-vite-debug-id-upload-plugin',
writeBundle: vi.fn(),
};

const mockedViteReleaseManagementPlugin = {
name: 'sentry-release-management-plugin',
writeBundle: vi.fn(),
};

const mockedFileDeletionPlugin = {
name: 'sentry-file-deletion-plugin',
writeBundle: vi.fn(),
};

vi.mock('@sentry/vite-plugin', async () => {
const original = (await vi.importActual('@sentry/vite-plugin')) as any;

return {
...original,
sentryVitePlugin: () => [mockedSentryVitePlugin],
sentryVitePlugin: () => [
mockedViteReleaseManagementPlugin,
mockedViteDebugIdUploadPlugin,
mockedFileDeletionPlugin,
],
};
});

Expand All@@ -30,20 +44,22 @@ beforeEach(() => {
vi.clearAllMocks();
});

async function getCustomSentryViteUploadSourcemapsPlugin(): Promise<Plugin | undefined> {
async function getSentryViteSubPlugin(name: string): Promise<Plugin | undefined> {
const plugins = await makeCustomSentryVitePlugins({
authToken: 'token',
org: 'org',
project: 'project',
adapter: 'other',
});
return plugins.find(plugin => plugin.name === 'sentry-upload-sveltekit-source-maps');

return plugins.find(plugin => plugin.name === name);
}

describe('makeCustomSentryVitePlugin()', () => {
it('returns the custom sentry source maps plugin', async () => {
const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
expect(plugin?.name).toEqual('sentry-upload-sveltekit-source-maps');
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');

expect(plugin?.name).toEqual('sentry-sveltekit-debug-id-upload-plugin');
expect(plugin?.apply).toEqual('build');
expect(plugin?.enforce).toEqual('post');

Expand All@@ -58,9 +74,9 @@ describe('makeCustomSentryVitePlugin()', () => {
expect(plugin?.writeBundle).toBeUndefined();
});

describe('Custom sentry vite plugin', () => {
describe('Custom debug id source maps plugin plugin', () => {
it('enables source map generation', async () => {
const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');
// @ts-expect-error this function exists!
const sentrifiedConfig = plugin.config({ build: { foo: {} }, test: {} });
expect(sentrifiedConfig).toEqual({
Expand All@@ -73,7 +89,7 @@ describe('makeCustomSentryVitePlugin()', () => {
});

it('injects the output dir into the server hooks file', async () => {
const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');
// @ts-expect-error this function exists!
const transformOutput = await plugin.transform('foo', '/src/hooks.server.ts');
const transformedCode = transformOutput.code;
Expand All@@ -84,34 +100,34 @@ describe('makeCustomSentryVitePlugin()', () => {
});

it('uploads source maps during the SSR build', async () => {
const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');
// @ts-expect-error this function exists!
plugin.configResolved({ build: { ssr: true } });
// @ts-expect-error this function exists!
await plugin.closeBundle();
expect(mockedSentryVitePlugin.writeBundle).toHaveBeenCalledTimes(1);
expect(mockedViteDebugIdUploadPlugin.writeBundle).toHaveBeenCalledTimes(1);
});

it("doesn't upload source maps during the non-SSR builds", async () => {
const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');

// @ts-expect-error this function exists!
plugin.configResolved({ build: { ssr: false } });
// @ts-expect-error this function exists!
await plugin.closeBundle();
expect(mockedSentryVitePlugin.writeBundle).not.toHaveBeenCalled();
expect(mockedViteDebugIdUploadPlugin.writeBundle).not.toHaveBeenCalled();
});
});

it('catches errors while uploading source maps', async () => {
mockedSentryVitePlugin.writeBundle.mockImplementationOnce(() => {
mockedViteDebugIdUploadPlugin.writeBundle.mockImplementationOnce(() => {
throw new Error('test error');
});

const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementationOnce(() => {});
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementationOnce(() => {});

const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');

// @ts-expect-error this function exists!
expect(plugin.closeBundle).not.toThrow();
Expand All@@ -124,4 +140,100 @@ describe('makeCustomSentryVitePlugin()', () => {
expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining('Failed to upload source maps'));
expect(consoleLogSpy).toHaveBeenCalled();
});

describe('Custom release management plugin', () => {
it('has the expected hooks and properties', async () => {
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-release-management-plugin');

expect(plugin).toEqual({
name: 'sentry-sveltekit-release-management-plugin',
apply: 'build',
enforce: 'post',
closeBundle: expect.any(Function),
});
});

it('calls the original release management plugin to start the release creation pipeline', async () => {
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-release-management-plugin');
// @ts-expect-error this function exists!
await plugin.closeBundle();
expect(mockedViteReleaseManagementPlugin.writeBundle).toHaveBeenCalledTimes(1);
});

it('catches errors during release creation', async () => {
mockedViteReleaseManagementPlugin.writeBundle.mockImplementationOnce(() => {
throw new Error('test error');
});

const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementationOnce(() => {});

const plugin = await getSentryViteSubPlugin('sentry-sveltekit-release-management-plugin');

// @ts-expect-error this function exists!
expect(plugin.closeBundle).not.toThrow();

// @ts-expect-error this function exists!
await plugin.closeBundle();

expect(consoleWarnSpy).toHaveBeenCalledWith(
expect.stringContaining('Failed to upload release data'),
expect.any(Error),
);
});

it('also works correctly if the original release management plugin has its old name', async () => {
const currentName = mockedViteReleaseManagementPlugin.name;
mockedViteReleaseManagementPlugin.name = 'sentry-debug-id-upload-plugin';

const plugin = await getSentryViteSubPlugin('sentry-sveltekit-release-management-plugin');

// @ts-expect-error this function exists!
await plugin.closeBundle();

expect(mockedViteReleaseManagementPlugin.writeBundle).toHaveBeenCalledTimes(1);

mockedViteReleaseManagementPlugin.name = currentName;
});
});

describe('Custom file deletion plugin', () => {
it('has the expected hooks and properties', async () => {
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-file-deletion-plugin');

expect(plugin).toEqual({
name: 'sentry-sveltekit-file-deletion-plugin',
apply: 'build',
enforce: 'post',
closeBundle: expect.any(Function),
});
});

it('calls the original file deletion plugin to delete files', async () => {
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-file-deletion-plugin');
// @ts-expect-error this function exists!
await plugin.closeBundle();
expect(mockedFileDeletionPlugin.writeBundle).toHaveBeenCalledTimes(1);
});

it('catches errors during file deletion', async () => {
mockedFileDeletionPlugin.writeBundle.mockImplementationOnce(() => {
throw new Error('test error');
});

const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementationOnce(() => {});

const plugin = await getSentryViteSubPlugin('sentry-sveltekit-file-deletion-plugin');

// @ts-expect-error this function exists!
expect(plugin.closeBundle).not.toThrow();

// @ts-expect-error this function exists!
await plugin.closeBundle();

expect(consoleWarnSpy).toHaveBeenCalledWith(
expect.stringContaining('Failed to delete source maps'),
expect.any(Error),
);
});
});
});
, '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
89 changes: 85 additions & 4 deletions packages/sveltekit/src/vite/sourceMaps.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -76,6 +76,14 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
plugin => plugin.name === 'sentry-vite-debug-id-upload-plugin',
);

const sentryViteFileDeletionPlugin = sentryPlugins.find(plugin => plugin.name === 'sentry-file-deletion-plugin');

const sentryViteReleaseManagementPlugin = sentryPlugins.find(
// sentry-debug-id-upload-plugin was the old (misleading) name of the plugin
// sentry-release-management-plugin is the new name
plugin => plugin.name === 'sentry-debug-id-upload-plugin' || plugin.name === 'sentry-release-management-plugin',
);

if (!sentryViteDebugIdUploadPlugin) {
debug &&
// eslint-disable-next-line no-console
Expand All@@ -85,7 +93,33 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
return sentryPlugins;
}

const restOfSentryVitePlugins = sentryPlugins.filter(plugin => plugin.name !== 'sentry-vite-debug-id-upload-plugin');
if (!sentryViteFileDeletionPlugin) {
debug &&
// eslint-disable-next-line no-console
console.warn(
'sentry-file-deletion-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
);
return sentryPlugins;
}

if (!sentryViteReleaseManagementPlugin) {
debug &&
// eslint-disable-next-line no-console
console.warn(
'sentry-release-management-plugin not found in sentryPlugins! Cannot modify plugin - returning default Sentry Vite plugins',
);
return sentryPlugins;
}

const unchangedSentryVitePlugins = sentryPlugins.filter(
plugin =>
![
'sentry-vite-debug-id-upload-plugin',
'sentry-file-deletion-plugin',
'sentry-release-management-plugin', // new name of release management plugin
'sentry-debug-id-upload-plugin', // old name of release management plugin
].includes(plugin.name),
);

let isSSRBuild = true;

Expand All@@ -95,8 +129,8 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
__sentry_sveltekit_output_dir: outputDir,
};

const customPlugin: Plugin = {
name: 'sentry-upload-sveltekit-source-maps',
const customDebugIdUploadPlugin: Plugin = {
name: 'sentry-sveltekit-debug-id-upload-plugin',
apply: 'build', // only apply this plugin at build time
enforce: 'post', // this needs to be set to post, otherwise we don't pick up the output from the SvelteKit adapter

Expand DownExpand Up@@ -248,7 +282,54 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug
},
};

return [...restOfSentryVitePlugins, customPlugin];
// The file deletion plugin is originally called in `writeBundle`.
// We need to call it in `closeBundle` though, because we also postpone
// the upload step to `closeBundle`
const customFileDeletionPlugin: Plugin = {
name: 'sentry-sveltekit-file-deletion-plugin',
apply: 'build', // only apply this plugin at build time
enforce: 'post',
closeBundle: async () => {
if (!isSSRBuild) {
return;
}

const writeBundleFn = sentryViteFileDeletionPlugin?.writeBundle;
if (typeof writeBundleFn === 'function') {
// This is fine though, because the original method doesn't consume any arguments in its `writeBundle` callback.
const outDir = path.resolve(process.cwd(), outputDir);
try {
// @ts-expect-error - the writeBundle hook expects two args we can't pass in here (they're only available in `writeBundle`)
await writeBundleFn({ dir: outDir });
} catch (e) {
// eslint-disable-next-line no-console
console.warn('Failed to delete source maps:', e);
}
}
},
};

const customReleaseManagementPlugin: Plugin = {
name: 'sentry-sveltekit-release-management-plugin',
apply: 'build', // only apply this plugin at build time
enforce: 'post',
closeBundle: async () => {
try {
// @ts-expect-error - this hook exists on the plugin!
await sentryViteReleaseManagementPlugin.writeBundle();
} catch (e) {
// eslint-disable-next-line no-console
console.warn('[Source Maps Plugin] Failed to upload release data:', e);
}
},
};

return [
...unchangedSentryVitePlugins,
customReleaseManagementPlugin,
customDebugIdUploadPlugin,
customFileDeletionPlugin,
];
}

function getFiles(dir: string): string[] {
Expand Down
10 changes: 6 additions & 4 deletions packages/sveltekit/test/vite/sentrySvelteKitPlugins.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -55,11 +55,13 @@ describe('sentrySvelteKit()', () => {
// default source maps plugins:
'sentry-telemetry-plugin',
'sentry-vite-release-injection-plugin',
'sentry-debug-id-upload-plugin',
'sentry-vite-debug-id-injection-plugin',
'sentry-file-deletion-plugin',
// custom release plugin:
'sentry-sveltekit-release-management-plugin',
// custom source maps plugin:
'sentry-upload-sveltekit-source-maps',
'sentry-sveltekit-debug-id-upload-plugin',
// custom deletion plugin
'sentry-sveltekit-file-deletion-plugin',
]);
});

Expand All@@ -76,7 +78,7 @@ describe('sentrySvelteKit()', () => {
const instrumentPlugin = plugins[0];

expect(plugins).toHaveLength(1);
expect(instrumentPlugin.name).toEqual('sentry-auto-instrumentation');
expect(instrumentPlugin?.name).toEqual('sentry-auto-instrumentation');

process.env.NODE_ENV = previousEnv;
});
Expand Down
146 changes: 129 additions & 17 deletions packages/sveltekit/test/vite/sourceMaps.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,17 +3,31 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { Plugin } from 'vite';
import { makeCustomSentryVitePlugins } from '../../src/vite/sourceMaps';

const mockedSentryVitePlugin = {
const mockedViteDebugIdUploadPlugin = {
name: 'sentry-vite-debug-id-upload-plugin',
writeBundle: vi.fn(),
};

const mockedViteReleaseManagementPlugin = {
name: 'sentry-release-management-plugin',
writeBundle: vi.fn(),
};

const mockedFileDeletionPlugin = {
name: 'sentry-file-deletion-plugin',
writeBundle: vi.fn(),
};

vi.mock('@sentry/vite-plugin', async () => {
const original = (await vi.importActual('@sentry/vite-plugin')) as any;

return {
...original,
sentryVitePlugin: () => [mockedSentryVitePlugin],
sentryVitePlugin: () => [
mockedViteReleaseManagementPlugin,
mockedViteDebugIdUploadPlugin,
mockedFileDeletionPlugin,
],
};
});

Expand All@@ -30,20 +44,22 @@ beforeEach(() => {
vi.clearAllMocks();
});

async function getCustomSentryViteUploadSourcemapsPlugin(): Promise<Plugin | undefined> {
async function getSentryViteSubPlugin(name: string): Promise<Plugin | undefined> {
const plugins = await makeCustomSentryVitePlugins({
authToken: 'token',
org: 'org',
project: 'project',
adapter: 'other',
});
return plugins.find(plugin => plugin.name === 'sentry-upload-sveltekit-source-maps');

return plugins.find(plugin => plugin.name === name);
}

describe('makeCustomSentryVitePlugin()', () => {
it('returns the custom sentry source maps plugin', async () => {
const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
expect(plugin?.name).toEqual('sentry-upload-sveltekit-source-maps');
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');

expect(plugin?.name).toEqual('sentry-sveltekit-debug-id-upload-plugin');
expect(plugin?.apply).toEqual('build');
expect(plugin?.enforce).toEqual('post');

Expand All@@ -58,9 +74,9 @@ describe('makeCustomSentryVitePlugin()', () => {
expect(plugin?.writeBundle).toBeUndefined();
});

describe('Custom sentry vite plugin', () => {
describe('Custom debug id source maps plugin plugin', () => {
it('enables source map generation', async () => {
const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');
// @ts-expect-error this function exists!
const sentrifiedConfig = plugin.config({ build: { foo: {} }, test: {} });
expect(sentrifiedConfig).toEqual({
Expand All@@ -73,7 +89,7 @@ describe('makeCustomSentryVitePlugin()', () => {
});

it('injects the output dir into the server hooks file', async () => {
const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');
// @ts-expect-error this function exists!
const transformOutput = await plugin.transform('foo', '/src/hooks.server.ts');
const transformedCode = transformOutput.code;
Expand All@@ -84,34 +100,34 @@ describe('makeCustomSentryVitePlugin()', () => {
});

it('uploads source maps during the SSR build', async () => {
const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');
// @ts-expect-error this function exists!
plugin.configResolved({ build: { ssr: true } });
// @ts-expect-error this function exists!
await plugin.closeBundle();
expect(mockedSentryVitePlugin.writeBundle).toHaveBeenCalledTimes(1);
expect(mockedViteDebugIdUploadPlugin.writeBundle).toHaveBeenCalledTimes(1);
});

it("doesn't upload source maps during the non-SSR builds", async () => {
const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');

// @ts-expect-error this function exists!
plugin.configResolved({ build: { ssr: false } });
// @ts-expect-error this function exists!
await plugin.closeBundle();
expect(mockedSentryVitePlugin.writeBundle).not.toHaveBeenCalled();
expect(mockedViteDebugIdUploadPlugin.writeBundle).not.toHaveBeenCalled();
});
});

it('catches errors while uploading source maps', async () => {
mockedSentryVitePlugin.writeBundle.mockImplementationOnce(() => {
mockedViteDebugIdUploadPlugin.writeBundle.mockImplementationOnce(() => {
throw new Error('test error');
});

const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementationOnce(() => {});
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementationOnce(() => {});

const plugin = await getCustomSentryViteUploadSourcemapsPlugin();
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-debug-id-upload-plugin');

// @ts-expect-error this function exists!
expect(plugin.closeBundle).not.toThrow();
Expand All@@ -124,4 +140,100 @@ describe('makeCustomSentryVitePlugin()', () => {
expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining('Failed to upload source maps'));
expect(consoleLogSpy).toHaveBeenCalled();
});

describe('Custom release management plugin', () => {
it('has the expected hooks and properties', async () => {
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-release-management-plugin');

expect(plugin).toEqual({
name: 'sentry-sveltekit-release-management-plugin',
apply: 'build',
enforce: 'post',
closeBundle: expect.any(Function),
});
});

it('calls the original release management plugin to start the release creation pipeline', async () => {
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-release-management-plugin');
// @ts-expect-error this function exists!
await plugin.closeBundle();
expect(mockedViteReleaseManagementPlugin.writeBundle).toHaveBeenCalledTimes(1);
});

it('catches errors during release creation', async () => {
mockedViteReleaseManagementPlugin.writeBundle.mockImplementationOnce(() => {
throw new Error('test error');
});

const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementationOnce(() => {});

const plugin = await getSentryViteSubPlugin('sentry-sveltekit-release-management-plugin');

// @ts-expect-error this function exists!
expect(plugin.closeBundle).not.toThrow();

// @ts-expect-error this function exists!
await plugin.closeBundle();

expect(consoleWarnSpy).toHaveBeenCalledWith(
expect.stringContaining('Failed to upload release data'),
expect.any(Error),
);
});

it('also works correctly if the original release management plugin has its old name', async () => {
const currentName = mockedViteReleaseManagementPlugin.name;
mockedViteReleaseManagementPlugin.name = 'sentry-debug-id-upload-plugin';

const plugin = await getSentryViteSubPlugin('sentry-sveltekit-release-management-plugin');

// @ts-expect-error this function exists!
await plugin.closeBundle();

expect(mockedViteReleaseManagementPlugin.writeBundle).toHaveBeenCalledTimes(1);

mockedViteReleaseManagementPlugin.name = currentName;
});
});

describe('Custom file deletion plugin', () => {
it('has the expected hooks and properties', async () => {
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-file-deletion-plugin');

expect(plugin).toEqual({
name: 'sentry-sveltekit-file-deletion-plugin',
apply: 'build',
enforce: 'post',
closeBundle: expect.any(Function),
});
});

it('calls the original file deletion plugin to delete files', async () => {
const plugin = await getSentryViteSubPlugin('sentry-sveltekit-file-deletion-plugin');
// @ts-expect-error this function exists!
await plugin.closeBundle();
expect(mockedFileDeletionPlugin.writeBundle).toHaveBeenCalledTimes(1);
});

it('catches errors during file deletion', async () => {
mockedFileDeletionPlugin.writeBundle.mockImplementationOnce(() => {
throw new Error('test error');
});

const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementationOnce(() => {});

const plugin = await getSentryViteSubPlugin('sentry-sveltekit-file-deletion-plugin');

// @ts-expect-error this function exists!
expect(plugin.closeBundle).not.toThrow();

// @ts-expect-error this function exists!
await plugin.closeBundle();

expect(consoleWarnSpy).toHaveBeenCalledWith(
expect.stringContaining('Failed to delete source maps'),
expect.any(Error),
);
});
});
});