diff --git a/packages/bundler-plugin-core/src/index.ts b/packages/bundler-plugin-core/src/index.ts index d382ba10..12bb0325 100644 --- a/packages/bundler-plugin-core/src/index.ts +++ b/packages/bundler-plugin-core/src/index.ts @@ -123,25 +123,27 @@ export function sentryUnpluginFactory({ }, }); - if (!options.sourcemaps?.disable) { + if (options.sourcemaps?.disable !== true) { plugins.push(debugIdInjectionPlugin(logger)); - // This option is only strongly typed for the webpack plugin, where it is used. It has no effect on other plugins - const webpack_forceExitOnBuildComplete = - typeof options._experiments["forceExitOnBuildCompletion"] === "boolean" - ? options._experiments["forceExitOnBuildCompletion"] - : undefined; - - plugins.push( - debugIdUploadPlugin( - createDebugIdUploadFunction({ - sentryBuildPluginManager, - }), - logger, - sentryBuildPluginManager.createDependencyOnBuildArtifacts, - webpack_forceExitOnBuildComplete - ) - ); + if (options.sourcemaps?.disable !== "disable-upload") { + // This option is only strongly typed for the webpack plugin, where it is used. It has no effect on other plugins + const webpack_forceExitOnBuildComplete = + typeof options._experiments["forceExitOnBuildCompletion"] === "boolean" + ? options._experiments["forceExitOnBuildCompletion"] + : undefined; + + plugins.push( + debugIdUploadPlugin( + createDebugIdUploadFunction({ + sentryBuildPluginManager, + }), + logger, + sentryBuildPluginManager.createDependencyOnBuildArtifacts, + webpack_forceExitOnBuildComplete + ) + ); + } } if (options.reactComponentAnnotation) { diff --git a/packages/bundler-plugin-core/src/options-mapping.ts b/packages/bundler-plugin-core/src/options-mapping.ts index 1ab3a532..3057c8af 100644 --- a/packages/bundler-plugin-core/src/options-mapping.ts +++ b/packages/bundler-plugin-core/src/options-mapping.ts @@ -23,7 +23,7 @@ export type NormalizedOptions = { disable: boolean; sourcemaps: | { - disable?: boolean; + disable?: boolean | "disable-upload"; assets?: string | string[]; ignore?: string | string[]; rewriteSources?: RewriteSourcesHook; diff --git a/packages/bundler-plugin-core/src/types.ts b/packages/bundler-plugin-core/src/types.ts index 92a5e1c8..81862a52 100644 --- a/packages/bundler-plugin-core/src/types.ts +++ b/packages/bundler-plugin-core/src/types.ts @@ -95,11 +95,14 @@ export interface Options { */ sourcemaps?: { /** - * If this flag is `true`, any functionality related to source maps will be disabled. + * Disables all functionality related to sourcemaps if set to `true`. + * + * If set to `"disable-upload"`, the plugin will not upload sourcemaps to Sentry, but will inject debug IDs into the build artifacts. + * This is useful if you want to manually upload sourcemaps to Sentry at a later point in time. * * @default false */ - disable?: boolean; + disable?: boolean | "disable-upload"; /** * A glob or an array of globs that specify the build artifacts and source maps that will be uploaded to Sentry. diff --git a/packages/bundler-plugin-core/test/index.test.ts b/packages/bundler-plugin-core/test/index.test.ts index 26087169..f6c60aa0 100644 --- a/packages/bundler-plugin-core/test/index.test.ts +++ b/packages/bundler-plugin-core/test/index.test.ts @@ -1,4 +1,5 @@ -import { getDebugIdSnippet } from "../src"; +import { Compiler } from "webpack"; +import { getDebugIdSnippet, sentryUnpluginFactory } from "../src"; describe("getDebugIdSnippet", () => { it("returns the debugId injection snippet for a passed debugId", () => { @@ -8,3 +9,185 @@ describe("getDebugIdSnippet", () => { ); }); }); + +describe("sentryUnpluginFactory sourcemaps.disable behavior", () => { + const mockReleaseInjectionPlugin = jest.fn((_injectionCode: string) => ({ + name: "mock-release-injection-plugin", + })); + + const mockComponentNameAnnotatePlugin = jest.fn(() => ({ + name: "mock-component-name-annotate-plugin", + })); + + const mockModuleMetadataInjectionPlugin = jest.fn((_injectionCode: string) => ({ + name: "mock-module-metadata-injection-plugin", + })); + + const mockDebugIdInjectionPlugin = jest.fn(() => ({ + name: "mock-debug-id-injection-plugin", + })); + + const mockDebugIdUploadPlugin = jest.fn(() => ({ + name: "mock-debug-id-upload-plugin", + })); + + const mockBundleSizeOptimizationsPlugin = jest.fn(() => ({ + name: "mock-bundle-size-optimizations-plugin", + })); + + const createUnpluginInstance = (): ReturnType => { + return sentryUnpluginFactory({ + releaseInjectionPlugin: mockReleaseInjectionPlugin, + componentNameAnnotatePlugin: mockComponentNameAnnotatePlugin, + moduleMetadataInjectionPlugin: mockModuleMetadataInjectionPlugin, + debugIdInjectionPlugin: mockDebugIdInjectionPlugin, + debugIdUploadPlugin: mockDebugIdUploadPlugin, + bundleSizeOptimizationsPlugin: mockBundleSizeOptimizationsPlugin, + }); + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe("when sourcemaps.disable is true", () => { + it("should not include debug ID injection or upload plugins", () => { + const unpluginInstance = createUnpluginInstance(); + + const plugins = unpluginInstance.raw( + { + authToken: "test-token", + org: "test-org", + project: "test-project", + sourcemaps: { + disable: true, + }, + }, + { framework: "webpack", webpack: { compiler: {} as Compiler } } + ); + + const pluginNames = plugins.map((plugin) => plugin.name); + + // Should not include debug ID related plugins + expect(pluginNames).not.toContain("mock-debug-id-injection-plugin"); + expect(pluginNames).not.toContain("mock-debug-id-upload-plugin"); + + // Should still include other core plugins + expect(pluginNames).toContain("sentry-telemetry-plugin"); + expect(pluginNames).toContain("sentry-release-management-plugin"); + expect(pluginNames).toContain("sentry-file-deletion-plugin"); + }); + }); + + describe('when sourcemaps.disable is "disable-upload"', () => { + it("should include debug ID injection plugin but not upload plugin", () => { + const unpluginInstance = createUnpluginInstance(); + + const plugins = unpluginInstance.raw( + { + authToken: "test-token", + org: "test-org", + project: "test-project", + sourcemaps: { + disable: "disable-upload", + }, + }, + { framework: "webpack", webpack: { compiler: {} as Compiler } } + ); + + const pluginNames = plugins.map((plugin) => plugin.name); + + // Should include debug ID injection but not upload + expect(pluginNames).toContain("mock-debug-id-injection-plugin"); + expect(pluginNames).not.toContain("mock-debug-id-upload-plugin"); + + // Should still include other core plugins + expect(pluginNames).toContain("sentry-telemetry-plugin"); + expect(pluginNames).toContain("sentry-release-management-plugin"); + expect(pluginNames).toContain("sentry-file-deletion-plugin"); + }); + }); + + describe("when sourcemaps.disable is false", () => { + it("should include both debug ID injection and upload plugins", () => { + const unpluginInstance = createUnpluginInstance(); + + const plugins = unpluginInstance.raw( + { + authToken: "test-token", + org: "test-org", + project: "test-project", + sourcemaps: { + disable: false, + }, + }, + { framework: "webpack", webpack: { compiler: {} as Compiler } } + ); + + const pluginNames = plugins.map((plugin) => plugin.name); + + // Should include both debug ID related plugins + expect(pluginNames).toContain("mock-debug-id-injection-plugin"); + expect(pluginNames).toContain("mock-debug-id-upload-plugin"); + + // Should include other core plugins + expect(pluginNames).toContain("sentry-telemetry-plugin"); + expect(pluginNames).toContain("sentry-release-management-plugin"); + expect(pluginNames).toContain("sentry-file-deletion-plugin"); + }); + }); + + describe("when sourcemaps.disable is undefined (default)", () => { + it("should include both debug ID injection and upload plugins", () => { + const unpluginInstance = createUnpluginInstance(); + + const plugins = unpluginInstance.raw( + { + authToken: "test-token", + org: "test-org", + project: "test-project", + // sourcemaps.disable not specified (undefined) + }, + { framework: "webpack", webpack: { compiler: {} as Compiler } } + ); + + const pluginNames = plugins.map((plugin) => plugin.name); + + // Should include both debug ID related plugins by default + expect(pluginNames).toContain("mock-debug-id-injection-plugin"); + expect(pluginNames).toContain("mock-debug-id-upload-plugin"); + + // Should include other core plugins + expect(pluginNames).toContain("sentry-telemetry-plugin"); + expect(pluginNames).toContain("sentry-release-management-plugin"); + expect(pluginNames).toContain("sentry-file-deletion-plugin"); + }); + }); + + describe("when entire sourcemaps option is undefined", () => { + it("should include both debug ID injection and upload plugins", () => { + const unpluginInstance = createUnpluginInstance(); + + const plugins = unpluginInstance.raw( + { + authToken: "test-token", + org: "test-org", + project: "test-project", + // sourcemaps option not specified at all + }, + { framework: "webpack", webpack: { compiler: {} as Compiler } } + ); + + const pluginNames = plugins.map((plugin) => plugin.name); + + // Should include both debug ID related plugins by default + expect(pluginNames).toContain("mock-debug-id-injection-plugin"); + expect(pluginNames).toContain("mock-debug-id-upload-plugin"); + + // Should include other core plugins + expect(pluginNames).toContain("sentry-telemetry-plugin"); + expect(pluginNames).toContain("sentry-release-management-plugin"); + expect(pluginNames).toContain("sentry-file-deletion-plugin"); + }); + }); +});