diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/basic-cjs.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/basic-cjs.test.ts index 06d3ccb3dccd..1c17c400cc29 100644 --- a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/basic-cjs.test.ts +++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/basic-cjs.test.ts @@ -19,6 +19,7 @@ test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => { "sentry-cli-mock.json": "["release","create","CURRENT_SHA","--project","fake-project"], ["release","set-commits","CURRENT_SHA","--auto"], ["release","finalize","CURRENT_SHA"], + ["sourcemap","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path"], ", } `); diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/basic.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/basic.test.ts index 06d3ccb3dccd..1c17c400cc29 100644 --- a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/basic.test.ts +++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/basic.test.ts @@ -19,6 +19,7 @@ test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => { "sentry-cli-mock.json": "["release","create","CURRENT_SHA","--project","fake-project"], ["release","set-commits","CURRENT_SHA","--auto"], ["release","finalize","CURRENT_SHA"], + ["sourcemap","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path"], ", } `); diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/release-disabled.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/release-disabled.test.ts index 36d836956df6..8a6ce51205b1 100644 --- a/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/release-disabled.test.ts +++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite4/release-disabled.test.ts @@ -18,6 +18,7 @@ test(import.meta.url, ({ runBundler, readOutputFiles }) => { ", "sentry-cli-mock.json": "["release","set-commits","CURRENT_SHA","--auto"], ["release","finalize","CURRENT_SHA"], + ["sourcemap","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path"], ", } `); diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/basic-cjs.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/basic-cjs.test.ts index 29e892eae928..c043197a4ac9 100644 --- a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/basic-cjs.test.ts +++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/basic-cjs.test.ts @@ -19,6 +19,7 @@ test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => { "sentry-cli-mock.json": "["release","create","CURRENT_SHA","--project","fake-project"], ["release","set-commits","CURRENT_SHA","--auto"], ["release","finalize","CURRENT_SHA"], + ["sourcemap","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path"], ", } `); diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/basic.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/basic.test.ts index 29e892eae928..c043197a4ac9 100644 --- a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/basic.test.ts +++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/basic.test.ts @@ -19,6 +19,7 @@ test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => { "sentry-cli-mock.json": "["release","create","CURRENT_SHA","--project","fake-project"], ["release","set-commits","CURRENT_SHA","--auto"], ["release","finalize","CURRENT_SHA"], + ["sourcemap","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path"], ", } `); diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/release-disabled.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/release-disabled.test.ts index a6e4dfac1179..eb67721a1582 100644 --- a/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/release-disabled.test.ts +++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite7/release-disabled.test.ts @@ -18,6 +18,7 @@ test(import.meta.url, ({ runBundler, readOutputFiles }) => { ", "sentry-cli-mock.json": "["release","set-commits","CURRENT_SHA","--auto"], ["release","finalize","CURRENT_SHA"], + ["sourcemap","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path"], ", } `); diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/basic-cjs.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/basic-cjs.test.ts index b648cee7c415..9b23588858ac 100644 --- a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/basic-cjs.test.ts +++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/basic-cjs.test.ts @@ -20,6 +20,7 @@ test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => { "sentry-cli-mock.json": "["release","create","CURRENT_SHA","--project","fake-project"], ["release","set-commits","CURRENT_SHA","--auto"], ["release","finalize","CURRENT_SHA"], + ["sourcemap","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path"], ", } `); diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/basic.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/basic.test.ts index b648cee7c415..9b23588858ac 100644 --- a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/basic.test.ts +++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/basic.test.ts @@ -20,6 +20,7 @@ test(import.meta.url, ({ runBundler, readOutputFiles, runFileInNode }) => { "sentry-cli-mock.json": "["release","create","CURRENT_SHA","--project","fake-project"], ["release","set-commits","CURRENT_SHA","--auto"], ["release","finalize","CURRENT_SHA"], + ["sourcemap","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path"], ", } `); diff --git a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/release-disabled.test.ts b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/release-disabled.test.ts index cd5480b81387..44729ef41b57 100644 --- a/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/release-disabled.test.ts +++ b/dev-packages/bundler-plugin-integration-tests/fixtures/vite8/release-disabled.test.ts @@ -19,6 +19,7 @@ test(import.meta.url, ({ runBundler, readOutputFiles }) => { ", "sentry-cli-mock.json": "["release","set-commits","CURRENT_SHA","--auto"], ["release","finalize","CURRENT_SHA"], + ["sourcemap","upload","-p","fake-project","--release","CURRENT_SHA","sentry-bundler-plugin-upload-path"], ", } `); diff --git a/packages/bundler-plugins/src/rollup/index.ts b/packages/bundler-plugins/src/rollup/index.ts index 29badf550597..216304efe553 100644 --- a/packages/bundler-plugins/src/rollup/index.ts +++ b/packages/bundler-plugins/src/rollup/index.ts @@ -22,6 +22,7 @@ import { createRequire } from 'node:module'; import { finalizeRolldownDebugIds, getDebugIdForChunk, hasExistingDebugID } from './debug-id-injection'; import { stampDebugIds, type OutputBundle } from './debug-id-stamping'; import { createSourceMapHandling, type OutputDestinationOptions } from './source-map-handling'; +import { createViteSourceMapHooks } from '../vite/source-map-hooks'; // The subset of Rollup's `TransformResult` that this plugin's `transform` // hook actually returns. Defined locally instead of imported from `rollup` @@ -316,15 +317,29 @@ export function _createRollupPluginInternals( ...(shouldTransform ? { transform: transformHook } : {}), // Vite warns about `rollupOptions.output.sourcemap`, so the Vite plugin // decides in its `config` hook instead. - ...(buildTool === 'rollup' ? { outputOptions: sourceMapHandling.outputOptions } : {}), + ...(buildTool === 'rollup' + ? { outputOptions: sourceMapHandling.outputOptions } + : createViteSourceMapHooks(sentryBuildPluginManager, sourceMapHandling)), renderChunk, generateBundle: { order: 'pre' as const, handler: generateBundle }, writeBundle, }; const post = { - name: `sentry-${buildTool}-upload-staging`, - generateBundle: { order: 'post' as const, handler: sourceMapHandling.stageSourceMaps }, + name: buildTool === 'vite' ? 'sentry-vite-debug-id-sourcemaps' : 'sentry-rollup-upload-staging', + generateBundle: { + order: 'post' as const, + async handler(outputOptions: OutputDestinationOptions, bundle: OutputBundle, isWrite?: boolean): Promise { + if (buildTool === 'vite' && options.sourcemaps?.disable === 'disable-upload') { + // Vite's module preload rewrite in `generateBundle` regenerates the source map of every + // chunk with a dynamic import, which drops the debug ID the main plugin stamped in its + // `pre` hook. Restore it without touching the chunk code, so file hashes stay valid. + stampDebugIds(bundle, false); + } + + await sourceMapHandling.stageSourceMaps(outputOptions, bundle, isWrite); + }, + }, }; return { main, post }; diff --git a/packages/bundler-plugins/src/vite/index.ts b/packages/bundler-plugins/src/vite/index.ts index d9b06efd9c25..9c8d5437fe03 100644 --- a/packages/bundler-plugins/src/vite/index.ts +++ b/packages/bundler-plugins/src/vite/index.ts @@ -1,15 +1,11 @@ import type { SentryRollupPluginOptions } from '../rollup'; -import { _rollupPluginInternal } from '../rollup'; +import { _createRollupPluginInternals } from '../rollup'; import { createRequire } from 'node:module'; -import { stampDebugIds, type OutputBundle } from '../rollup/debug-id-stamping'; interface SentryVitePlugin { name: string; enforce: 'pre' | 'post'; - generateBundle?: { - order: 'pre' | 'post'; - handler: (_outputOptions: unknown, bundle: OutputBundle) => void; - }; + [hook: string]: unknown; } function getViteMajorVersion(): string | undefined { @@ -27,27 +23,12 @@ function getViteMajorVersion(): string | undefined { } export const sentryVitePlugin = (options?: SentryRollupPluginOptions): SentryVitePlugin[] => { - const plugins: SentryVitePlugin[] = [ - { - enforce: 'pre', - ..._rollupPluginInternal(options, 'vite', getViteMajorVersion()), - }, - ]; - - if (!options?.disable && options?.sourcemaps?.disable === 'disable-upload') { - plugins.push({ - name: 'sentry-vite-debug-id-sourcemaps', - enforce: 'post', - generateBundle: { - order: 'post', - handler(_outputOptions, bundle) { - // Vite's module preload rewrite in `generateBundle` regenerates the source map of every - // chunk with a dynamic import, which drops the debug ID the main plugin stamped in its - // `pre` hook. Restore it without touching the chunk code, so file hashes stay valid. - stampDebugIds(bundle, false); - }, - }, - }); + const { main, post } = _createRollupPluginInternals(options, 'vite', getViteMajorVersion()); + + const plugins: SentryVitePlugin[] = [{ enforce: 'pre', ...main }]; + + if (post) { + plugins.push({ enforce: 'post', ...post }); } return plugins; diff --git a/packages/bundler-plugins/src/vite/source-map-hooks.ts b/packages/bundler-plugins/src/vite/source-map-hooks.ts new file mode 100644 index 000000000000..09ff65071bb1 --- /dev/null +++ b/packages/bundler-plugins/src/vite/source-map-hooks.ts @@ -0,0 +1,110 @@ +import * as path from 'node:path'; +import type { SentryBuildPluginManager, SourceMapDecision } from '../core'; +import type { RollupSourceMapSetting, SourceMapHandling } from '../rollup/source-map-handling'; +import { sourceMapKindFromRollupSetting } from '../rollup/source-map-handling'; + +type BundlerOutputOptions = { dir?: string } | { dir?: string }[] | undefined; + +type ViteBuildOptions = { + sourcemap?: RollupSourceMapSetting; + outDir?: string; + rollupOptions?: { output?: BundlerOutputOptions }; + rolldownOptions?: { output?: BundlerOutputOptions }; +}; + +export type ViteUserConfig = { + build?: ViteBuildOptions; + environments?: Record; +}; + +export type ViteResolvedConfig = { + root: string; + build: ViteBuildOptions; + environments?: Record; +}; + +export type ViteSourceMapHooks = { + config: (userConfig: ViteUserConfig, env: { command: string }) => ViteUserConfig | null; + configResolved: (config: ViteResolvedConfig) => void; +}; + +const NO_CHANGE: SourceMapDecision = { enableHidden: false, deleteAfterUpload: false }; + +/** + * The directories an environment writes to: `build.outDir` (relative to the root) unless an output + * in `rollupOptions` or `rolldownOptions` sets its own `dir`, which the bundler resolves from the + * working directory. + */ +function getOutputDestinations(root: string, build: ViteBuildOptions): string[] { + const outputs = build.rolldownOptions?.output ?? build.rollupOptions?.output; + const outputDirs = (Array.isArray(outputs) ? outputs : [outputs]) + .map(output => output?.dir) + .filter((dir): dir is string => typeof dir === 'string') + .map(dir => path.resolve(dir)); + + return outputDirs.length > 0 ? outputDirs : [path.resolve(root, build.outDir ?? 'dist')]; +} + +/** + * Vite warns about `rollupOptions.output.sourcemap`, so the Vite plugin decides on `build.sourcemap` + * in its `config` hook. The output directories are only known in `configResolved`, which then + * records the decision for each of them. + */ +export function createViteSourceMapHooks( + sentryBuildPluginManager: SentryBuildPluginManager, + sourceMapHandling: SourceMapHandling, +): ViteSourceMapHooks { + // `'*'` for the top-level setting, otherwise the name of an environment with its own. + const decisions = new Map(); + + return { + config: (userConfig, env) => { + if (env.command !== 'build') { + return null; + } + + const decision = sentryBuildPluginManager.sourceMaps.decide( + sourceMapKindFromRollupSetting(userConfig.build?.sourcemap), + ); + decisions.set('*', decision); + + const result: ViteUserConfig = decision.enableHidden ? { build: { sourcemap: 'hidden' } } : {}; + + for (const [name, environment] of Object.entries(userConfig.environments ?? {})) { + const setting = environment?.build?.sourcemap; + if (setting === undefined) { + continue; + } + + const environmentDecision = sentryBuildPluginManager.sourceMaps.decide(sourceMapKindFromRollupSetting(setting)); + decisions.set(name, environmentDecision); + + if (environmentDecision.enableHidden) { + result.environments = { ...result.environments, [name]: { build: { sourcemap: 'hidden' } } }; + } + } + + return result; + }, + + configResolved: config => { + const environments = config.environments ?? { client: { build: config.build } }; + + for (const [name, environment] of Object.entries(environments)) { + let decision = decisions.get(name) ?? decisions.get('*') ?? NO_CHANGE; + + // A plugin that ran after ours may have changed the setting again. + if (decision.deleteAfterUpload && environment.build.sourcemap !== 'hidden') { + sentryBuildPluginManager.logger.debug( + `Source maps of the "${name}" environment are set to ${JSON.stringify(environment.build.sourcemap)}. Not removing them after upload.`, + ); + decision = NO_CHANGE; + } + + for (const destination of getOutputDestinations(config.root, environment.build)) { + sourceMapHandling.setDecision(destination, decision); + } + } + }, + }; +} diff --git a/packages/bundler-plugins/test/vite/delete-sourcemaps-after-upload.test.ts b/packages/bundler-plugins/test/vite/delete-sourcemaps-after-upload.test.ts new file mode 100644 index 000000000000..a295f06882b3 --- /dev/null +++ b/packages/bundler-plugins/test/vite/delete-sourcemaps-after-upload.test.ts @@ -0,0 +1,242 @@ +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { build, createBuilder } from 'vite'; +import type { Plugin, ResolvedConfig } from 'vite'; +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import { sentryVitePlugin } from '../../src/vite'; +import type { Options } from '../../src/core'; + +const { mockSourcemapUpload } = vi.hoisted(() => ({ mockSourcemapUpload: vi.fn() })); + +vi.mock('sentry', () => ({ + createSentrySDK: () => ({ + release: { create: vi.fn(), finalize: vi.fn(), 'set-commits': vi.fn(), deploy: vi.fn() }, + sourcemap: { upload: mockSourcemapUpload, inject: vi.fn() }, + run: vi.fn(), + }), +})); + +const credentials: Options = { + authToken: 't', + org: 'o', + project: 'p', + release: { name: 'test-release', create: false, finalize: false }, + telemetry: false, + silent: true, +}; + +type Upload = { directory: string; files: Record }; + +function listFiles(dir: string): string[] { + return fs + .readdirSync(dir, { recursive: true, encoding: 'utf8' }) + .filter(file => fs.statSync(path.join(dir, file)).isFile()); +} + +function readChunks(dir: string): string[] { + return listFiles(dir) + .filter(file => file.endsWith('.js')) + .map(file => fs.readFileSync(path.join(dir, file), 'utf8')); +} + +describe('sourcemaps.deleteSourcemapsAfterUpload (vite)', () => { + let root: string; + let entry: string; + let outDir: string; + let uploads: Upload[]; + + beforeEach(() => { + vi.clearAllMocks(); + uploads = []; + mockSourcemapUpload.mockImplementation(async ({ directory }: { directory: string }) => { + const files: Record = {}; + for (const file of fs.readdirSync(directory)) { + files[file] = fs.readFileSync(path.join(directory, file), 'utf8'); + } + uploads.push({ directory, files }); + }); + + root = fs.mkdtempSync(path.join(os.tmpdir(), 'sentry-vite-delete-maps-')); + entry = path.join(root, 'main.js'); + outDir = path.join(root, 'dist'); + // A dynamic import makes Vite's import analysis regenerate the source map in `generateBundle`. + fs.writeFileSync(entry, "import('./lazy.js').then(m => console.log(m.answer));\n"); + fs.writeFileSync(path.join(root, 'lazy.js'), 'export const answer = 42;\n'); + }); + + afterEach(() => { + vi.restoreAllMocks(); + fs.rmSync(root, { recursive: true, force: true }); + }); + + function viteConfig(pluginOptions: Options, plugins: Plugin[] = []) { + return { + configFile: false as const, + logLevel: 'silent' as const, + root, + plugins: [...sentryVitePlugin({ ...credentials, ...pluginOptions }), ...plugins], + build: { outDir, minify: false as const, rollupOptions: { input: entry } }, + }; + } + + function expectUploadedPairs(upload: Upload, chunks: string[]): void { + const uploadedNames = Object.keys(upload.files); + expect(uploadedNames.filter(name => name.endsWith('.js'))).toHaveLength(chunks.length); + + for (const chunk of chunks) { + const debugId = chunk.match(/sentry-dbid-([0-9a-f-]{36})/)?.[1]; + expect(debugId).toBeDefined(); + const mapName = uploadedNames.find(name => name.startsWith(`${debugId}-`) && name.endsWith('.map')); + expect(mapName).toBeDefined(); + expect(JSON.parse(upload.files[mapName!]!)).toMatchObject({ debug_id: debugId }); + } + + expect(fs.existsSync(upload.directory)).toBe(false); + } + + it('enables hidden source maps, uploads them and leaves none in the output when the setting is unset', async () => { + let resolvedSourcemap: unknown; + const spy: Plugin = { + name: 'spy', + configResolved(config: ResolvedConfig) { + resolvedSourcemap = config.build.sourcemap; + }, + }; + + await build(viteConfig({}, [spy])); + + expect(resolvedSourcemap).toBe('hidden'); + const files = listFiles(outDir); + expect(files.filter(file => file.endsWith('.map'))).toEqual([]); + const chunks = readChunks(outDir); + expect(chunks).toHaveLength(2); + for (const chunk of chunks) { + expect(chunk).not.toContain('sourceMappingURL'); + } + + expect(uploads).toHaveLength(1); + expectUploadedPairs(uploads[0]!, chunks); + }); + + it('switches visible source maps to hidden and removes them when the option is true', async () => { + const config = viteConfig({ sourcemaps: { deleteSourcemapsAfterUpload: true } }); + await build({ ...config, build: { ...config.build, sourcemap: true } }); + + expect(listFiles(outDir).filter(file => file.endsWith('.map'))).toEqual([]); + const chunks = readChunks(outDir); + for (const chunk of chunks) { + expect(chunk).not.toContain('sourceMappingURL'); + } + expect(uploads).toHaveLength(1); + expectUploadedPairs(uploads[0]!, chunks); + }); + + it('stages chunks whose debug ID the bundler injects itself', async () => { + const config = viteConfig({}); + await build({ + ...config, + build: { ...config.build, rollupOptions: { input: entry, output: { sourcemapDebugIds: true } } }, + }); + + expect(listFiles(outDir).filter(file => file.endsWith('.map'))).toEqual([]); + const chunks = readChunks(outDir); + for (const chunk of chunks) { + expect(chunk).toContain('//# debugId='); + } + expect(uploads).toHaveLength(1); + expectUploadedPairs(uploads[0]!, chunks); + }); + + it('removes source maps from an output directory set through rollupOptions', async () => { + const rollupOutDir = path.join(root, 'out', 'rollup'); + const config = viteConfig({}); + await build({ + ...config, + build: { ...config.build, outDir: 'dist', rollupOptions: { input: entry, output: { dir: rollupOutDir } } }, + }); + + expect(listFiles(rollupOutDir).filter(file => file.endsWith('.map'))).toEqual([]); + expect(uploads).toHaveLength(1); + expectUploadedPairs(uploads[0]!, readChunks(rollupOutDir)); + }); + + it('keeps hidden source maps the user enabled and uploads them from disk', async () => { + const config = viteConfig({}); + await build({ ...config, build: { ...config.build, sourcemap: 'hidden' } }); + + expect(listFiles(outDir).filter(file => file.endsWith('.map'))).toHaveLength(2); + expect(uploads).toHaveLength(1); + expect(Object.keys(uploads[0]!.files)).toHaveLength(4); + }); + + it('does not create a staging folder when the build does not write', async () => { + const mkdtempSpy = vi.spyOn(fs.promises, 'mkdtemp'); + const config = viteConfig({}); + await build({ ...config, build: { ...config.build, write: false } }); + + expect(mkdtempSpy).not.toHaveBeenCalled(); + expect(uploads).toHaveLength(0); + }); + + it('does not remove source maps when a later plugin turns them off', async () => { + const later: Plugin = { name: 'later', config: () => ({ build: { sourcemap: false } }) }; + + await build(viteConfig({}, [later])); + + expect(listFiles(outDir).filter(file => file.endsWith('.map'))).toEqual([]); + expect(uploads).toHaveLength(0); + }); + + it('uploads a client build and an SSR build of the same plugin instances on their own', async () => { + const plugins = sentryVitePlugin(credentials); + const serverOutDir = path.join(root, 'dist-server'); + + await build({ ...viteConfig({}), plugins }); + await build({ + ...viteConfig({}), + plugins, + build: { outDir: serverOutDir, minify: false, ssr: entry }, + }); + + expect(listFiles(outDir).filter(file => file.endsWith('.map'))).toEqual([]); + expect(listFiles(serverOutDir).filter(file => file.endsWith('.map'))).toEqual([]); + expect(uploads).toHaveLength(2); + }); + + it('keeps source maps in an environment that sets them itself', async () => { + const serverOutDir = path.join(root, 'dist-server'); + const builder = await createBuilder({ + ...viteConfig({}), + environments: { + ssr: { + build: { outDir: serverOutDir, minify: false, sourcemap: 'hidden', rollupOptions: { input: entry } }, + }, + }, + builder: {}, + }); + await builder.buildApp(); + + expect(listFiles(outDir).filter(file => file.endsWith('.map'))).toEqual([]); + expect(listFiles(serverOutDir).filter(file => file.endsWith('.map'))).toHaveLength(2); + expect(uploads).toHaveLength(2); + }); + + it('still restores debug IDs for "disable-upload" builds', async () => { + const config = viteConfig({ sourcemaps: { disable: 'disable-upload' } }); + await build({ ...config, build: { ...config.build, sourcemap: true } }); + + const chunks = readChunks(outDir); + const maps = listFiles(outDir).filter(file => file.endsWith('.map')); + expect(maps).toHaveLength(2); + for (const chunk of chunks) { + const debugId = chunk.match(/sentry-dbid-([0-9a-f-]{36})/)?.[1]; + expect(debugId).toBeDefined(); + const map = maps + .map(file => JSON.parse(fs.readFileSync(path.join(outDir, file), 'utf8'))) + .find(m => m.debug_id === debugId); + expect(map).toBeDefined(); + } + expect(uploads).toHaveLength(0); + }); +}); diff --git a/packages/bundler-plugins/test/vite/public-api.test.ts b/packages/bundler-plugins/test/vite/public-api.test.ts index cade4da1c67a..eba6b7ebb59e 100644 --- a/packages/bundler-plugins/test/vite/public-api.test.ts +++ b/packages/bundler-plugins/test/vite/public-api.test.ts @@ -24,7 +24,7 @@ describe('sentryVitePlugin', () => { const pluginNames = plugins.map(plugin => plugin.name); - expect(pluginNames).toEqual(expect.arrayContaining(['sentry-vite-plugin'])); + expect(pluginNames).toEqual(['sentry-vite-plugin', 'sentry-vite-debug-id-sourcemaps']); }); it('returns an array of Vite pluginswhen unplugin returns a single plugin', () => { diff --git a/packages/solidstart/test/config/withSentry.test.ts b/packages/solidstart/test/config/withSentry.test.ts index 3afba804811d..41f2d3f426e0 100644 --- a/packages/solidstart/test/config/withSentry.test.ts +++ b/packages/solidstart/test/config/withSentry.test.ts @@ -118,6 +118,7 @@ describe('withSentry()', () => { expect(names).toEqual([ 'sentry-solidstart-build-instrumentation-file', 'sentry-vite-plugin', + 'sentry-vite-debug-id-sourcemaps', 'sentry-solidstart-update-source-map-setting', ]); }); @@ -141,6 +142,7 @@ describe('withSentry()', () => { expect(names).toEqual([ 'sentry-solidstart-build-instrumentation-file', 'sentry-vite-plugin', + 'sentry-vite-debug-id-sourcemaps', 'sentry-solidstart-update-source-map-setting', 'my-test-plugin', ]); @@ -168,6 +170,7 @@ describe('withSentry()', () => { expect(names).toEqual([ 'sentry-solidstart-build-instrumentation-file', 'sentry-vite-plugin', + 'sentry-vite-debug-id-sourcemaps', 'sentry-solidstart-update-source-map-setting', 'my-test-plugin', ]); diff --git a/packages/solidstart/test/vite/sentrySolidStart.test.ts b/packages/solidstart/test/vite/sentrySolidStart.test.ts index 75076c3ba99c..2dda39510466 100644 --- a/packages/solidstart/test/vite/sentrySolidStart.test.ts +++ b/packages/solidstart/test/vite/sentrySolidStart.test.ts @@ -54,6 +54,7 @@ describe('sentrySolidStart()', () => { 'code-transformer', 'sentry-solidstart-files-to-delete-after-upload', 'sentry-vite-plugin', + 'sentry-vite-debug-id-sourcemaps', 'sentry-solidstart-update-source-map-setting', ]); }); diff --git a/packages/solidstart/test/vite/sentrySolidStartVite.test.ts b/packages/solidstart/test/vite/sentrySolidStartVite.test.ts index 545638f9e3dc..540854791f97 100644 --- a/packages/solidstart/test/vite/sentrySolidStartVite.test.ts +++ b/packages/solidstart/test/vite/sentrySolidStartVite.test.ts @@ -28,6 +28,7 @@ describe('sentrySolidStartVite()', () => { expect(names).toEqual([ 'sentry-solidstart-build-instrumentation-file', 'sentry-vite-plugin', + 'sentry-vite-debug-id-sourcemaps', 'sentry-solidstart-update-source-map-setting', ]); }); diff --git a/packages/sveltekit/test/vite/sentrySvelteKitPlugins.test.ts b/packages/sveltekit/test/vite/sentrySvelteKitPlugins.test.ts index c78099729998..b5730a4bee7f 100644 --- a/packages/sveltekit/test/vite/sentrySvelteKitPlugins.test.ts +++ b/packages/sveltekit/test/vite/sentrySvelteKitPlugins.test.ts @@ -71,7 +71,7 @@ describe('sentrySvelteKit()', () => { // + 1 auto instrument plugin // + 1 orchestrion plugin + 1 build flag plugin + 1 global values injection plugin + 1 modified main plugin // + 3 custom plugins - expect(plugins).toHaveLength(11); + expect(plugins).toHaveLength(12); }); it('returns the custom sentry source maps upload plugin, unmodified sourcemaps plugins and the auto-instrument plugin by default', async () => { @@ -94,6 +94,7 @@ describe('sentrySvelteKit()', () => { 'sentry-sveltekit-global-values-injection-plugin', // modified main plugin (writeBundle deferred to closeBundle): 'sentry-vite-plugin', + 'sentry-vite-debug-id-sourcemaps', 'sentry-sveltekit-update-source-map-setting-plugin', 'sentry-sveltekit-files-to-delete-after-upload-setting-plugin', // custom source maps plugin (sorcery flattening + deferred upload): @@ -122,7 +123,7 @@ describe('sentrySvelteKit()', () => { it("doesn't return the auto instrument plugin if autoInstrument is `false`", async () => { const plugins = await getSentrySvelteKitPlugins({ autoInstrument: false }); const pluginNames = plugins.map(plugin => plugin.name); - expect(plugins).toHaveLength(10); // kit config resolver + browser-tracing variant resolver + OpenTelemetry API resolver + orchestrion + build flag + global values injection + 1 modified main plugin + 3 custom plugins + expect(plugins).toHaveLength(11); // kit config resolver + browser-tracing variant resolver + OpenTelemetry API resolver + orchestrion + build flag + global values injection + 1 modified main plugin + 1 debug ID plugin + 3 custom plugins expect(pluginNames).not.toContain('sentry-auto-instrumentation'); });