From ce967a4a80f428ebde95f1a2058a723f1f1238ba Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Mon, 22 Dec 2025 16:57:17 +0100 Subject: [PATCH 1/5] fix(rollup): Prevent double-injection of debug ID --- packages/bundler-plugin-core/src/index.ts | 8 ++ .../bundler-plugin-core/test/index.test.ts | 81 ++++++++++++++++++- 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/packages/bundler-plugin-core/src/index.ts b/packages/bundler-plugin-core/src/index.ts index 2c38cac7..7281aaac 100644 --- a/packages/bundler-plugin-core/src/index.ts +++ b/packages/bundler-plugin-core/src/index.ts @@ -269,6 +269,14 @@ export function createRollupDebugIdInjectionHooks(): { stripQueryAndHashFromPath(chunk.fileName).endsWith(ending) ) ) { + // Check if a debug ID has already been injected to avoid duplicate injection (e.g. by another plugin or Sentry CLI) + const chunkStartSnippet = code.slice(0, 2000); + const chunkEndSnippet = code.slice(-500); + + if (chunkStartSnippet.includes("_sentryDebugIdIdentifier") || chunkEndSnippet.includes("//# debugId=")) { + return null; // Debug ID already present, skip injection + } + const debugId = stringToUUID(code); // generate a deterministic debug ID const codeToInject = getDebugIdSnippet(debugId); diff --git a/packages/bundler-plugin-core/test/index.test.ts b/packages/bundler-plugin-core/test/index.test.ts index f6c60aa0..ba7e12c8 100644 --- a/packages/bundler-plugin-core/test/index.test.ts +++ b/packages/bundler-plugin-core/test/index.test.ts @@ -1,5 +1,5 @@ import { Compiler } from "webpack"; -import { getDebugIdSnippet, sentryUnpluginFactory } from "../src"; +import { getDebugIdSnippet, sentryUnpluginFactory, createRollupDebugIdInjectionHooks } from "../src"; describe("getDebugIdSnippet", () => { it("returns the debugId injection snippet for a passed debugId", () => { @@ -10,6 +10,85 @@ describe("getDebugIdSnippet", () => { }); }); +describe("createRollupDebugIdInjectionHooks", () => { + const hooks = createRollupDebugIdInjectionHooks(); + + describe("renderChunk", () => { + it("should inject debug ID into clean JavaScript files", () => { + const code = 'console.log("Hello world");'; + const result = hooks.renderChunk(code, { fileName: "bundle.js" }); + + expect(result).not.toBeNull(); + expect(result?.code).toContain("_sentryDebugIdIdentifier"); + expect(result?.code).toContain('console.log("Hello world");'); + }); + + it("should inject debug ID after 'use strict'", () => { + const code = '"use strict";\nconsole.log("Hello world");'; + const result = hooks.renderChunk(code, { fileName: "bundle.js" }); + + expect(result).not.toBeNull(); + expect(result?.code).toMatch(/^"use strict";.*;{try/); + }); + + it.each([ + ["bundle.js", true], + ["bundle.mjs", true], + ["bundle.cjs", true], + ["bundle.js?foo=bar", true], + ["bundle.js#hash", true], + ["index.html", false], + ["styles.css", false], + ])("should process file '%s': %s", (fileName, shouldProcess) => { + const code = 'console.log("test");'; + const result = hooks.renderChunk(code, { fileName }); + + if (shouldProcess) { + expect(result).not.toBeNull(); + expect(result?.code).toContain("_sentryDebugIdIdentifier"); + } else { + expect(result).toBeNull(); + } + }); + + it.each([ + [ + "inline format at start", + ';{try{(function(){var e="undefined"!=typeof window?window:e._sentryDebugIdIdentifier="sentry-dbid-existing-id");})();}catch(e){}};console.log("test");', + ], + [ + "comment format at end", + 'console.log("test");\n//# debugId=f6ccd6f4-7ea0-4854-8384-1c9f8340af81\n//# sourceMappingURL=bundle.js.map', + ], + [ + "inline format with large file", + '"use strict";\n' + + "// comment\n".repeat(10) + + ';{try{(function(){var e="undefined"!=typeof window?window:e._sentryDebugIdIdentifier="sentry-dbid-existing-id");})();}catch(e){}};' + + '\nconsole.log("line");\n'.repeat(100), + ], + ])("should NOT inject when debug ID already exists (%s)", (_description, code) => { + const result = hooks.renderChunk(code, { fileName: "bundle.js" }); + expect(result).toBeNull(); + }); + + it("should only check boundaries for performance (not entire file)", () => { + // Inline format beyond first 2KB boundary + const codeWithInlineBeyond2KB = + "a".repeat(2100) + + ';{try{(function(){var e="undefined"!=typeof window?window:e._sentryDebugIdIdentifier="sentry-dbid-existing-id");})();}catch(e){}};'; + + expect(hooks.renderChunk(codeWithInlineBeyond2KB, { fileName: "bundle.js" })).not.toBeNull(); + + // Comment format beyond last 500 bytes boundary + const codeWithCommentBeyond500B = + "//# debugId=f6ccd6f4-7ea0-4854-8384-1c9f8340af81\n" + "a".repeat(600); + + expect(hooks.renderChunk(codeWithCommentBeyond500B, { fileName: "bundle.js" })).not.toBeNull(); + }); + }); +}); + describe("sentryUnpluginFactory sourcemaps.disable behavior", () => { const mockReleaseInjectionPlugin = jest.fn((_injectionCode: string) => ({ name: "mock-release-injection-plugin", From 974aa2f74a4762d3f04edc47edc779ed09d02869 Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Tue, 23 Dec 2025 10:08:01 +0100 Subject: [PATCH 2/5] prettier --- packages/bundler-plugin-core/src/index.ts | 5 ++++- packages/bundler-plugin-core/test/index.test.ts | 10 ++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/bundler-plugin-core/src/index.ts b/packages/bundler-plugin-core/src/index.ts index 7281aaac..be2d24f9 100644 --- a/packages/bundler-plugin-core/src/index.ts +++ b/packages/bundler-plugin-core/src/index.ts @@ -273,7 +273,10 @@ export function createRollupDebugIdInjectionHooks(): { const chunkStartSnippet = code.slice(0, 2000); const chunkEndSnippet = code.slice(-500); - if (chunkStartSnippet.includes("_sentryDebugIdIdentifier") || chunkEndSnippet.includes("//# debugId=")) { + if ( + chunkStartSnippet.includes("_sentryDebugIdIdentifier") || + chunkEndSnippet.includes("//# debugId=") + ) { return null; // Debug ID already present, skip injection } diff --git a/packages/bundler-plugin-core/test/index.test.ts b/packages/bundler-plugin-core/test/index.test.ts index ba7e12c8..0f09e5b4 100644 --- a/packages/bundler-plugin-core/test/index.test.ts +++ b/packages/bundler-plugin-core/test/index.test.ts @@ -1,5 +1,9 @@ import { Compiler } from "webpack"; -import { getDebugIdSnippet, sentryUnpluginFactory, createRollupDebugIdInjectionHooks } from "../src"; +import { + getDebugIdSnippet, + sentryUnpluginFactory, + createRollupDebugIdInjectionHooks, +} from "../src"; describe("getDebugIdSnippet", () => { it("returns the debugId injection snippet for a passed debugId", () => { @@ -84,7 +88,9 @@ describe("createRollupDebugIdInjectionHooks", () => { const codeWithCommentBeyond500B = "//# debugId=f6ccd6f4-7ea0-4854-8384-1c9f8340af81\n" + "a".repeat(600); - expect(hooks.renderChunk(codeWithCommentBeyond500B, { fileName: "bundle.js" })).not.toBeNull(); + expect( + hooks.renderChunk(codeWithCommentBeyond500B, { fileName: "bundle.js" }) + ).not.toBeNull(); }); }); }); From d7cb964b0abd09cce8b5614be56931f38def32d1 Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Tue, 23 Dec 2025 10:12:35 +0100 Subject: [PATCH 3/5] fix linting issues --- .../bundler-plugin-core/test/index.test.ts | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/packages/bundler-plugin-core/test/index.test.ts b/packages/bundler-plugin-core/test/index.test.ts index 0f09e5b4..c3d5f5bb 100644 --- a/packages/bundler-plugin-core/test/index.test.ts +++ b/packages/bundler-plugin-core/test/index.test.ts @@ -36,23 +36,24 @@ describe("createRollupDebugIdInjectionHooks", () => { }); it.each([ - ["bundle.js", true], - ["bundle.mjs", true], - ["bundle.cjs", true], - ["bundle.js?foo=bar", true], - ["bundle.js#hash", true], - ["index.html", false], - ["styles.css", false], - ])("should process file '%s': %s", (fileName, shouldProcess) => { + ["bundle.js"], + ["bundle.mjs"], + ["bundle.cjs"], + ["bundle.js?foo=bar"], + ["bundle.js#hash"], + ])("should process file '%s': %s", (fileName) => { const code = 'console.log("test");'; const result = hooks.renderChunk(code, { fileName }); - if (shouldProcess) { - expect(result).not.toBeNull(); - expect(result?.code).toContain("_sentryDebugIdIdentifier"); - } else { - expect(result).toBeNull(); - } + expect(result).not.toBeNull(); + expect(result?.code).toContain("_sentryDebugIdIdentifier"); + }); + + it.each([["index.html"], ["styles.css"]])("should NOT process file '%s': %s", (fileName) => { + const code = 'console.log("test");'; + const result = hooks.renderChunk(code, { fileName }); + + expect(result).toBeNull(); }); it.each([ From 31d27c40e5b4580f9941c18f72242916892b9d4c Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Mon, 29 Dec 2025 12:00:40 +0100 Subject: [PATCH 4/5] use matchInlineSnapshot fn --- packages/bundler-plugin-core/test/index.test.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/bundler-plugin-core/test/index.test.ts b/packages/bundler-plugin-core/test/index.test.ts index c3d5f5bb..61613641 100644 --- a/packages/bundler-plugin-core/test/index.test.ts +++ b/packages/bundler-plugin-core/test/index.test.ts @@ -23,8 +23,9 @@ describe("createRollupDebugIdInjectionHooks", () => { const result = hooks.renderChunk(code, { fileName: "bundle.js" }); expect(result).not.toBeNull(); - expect(result?.code).toContain("_sentryDebugIdIdentifier"); - expect(result?.code).toContain('console.log("Hello world");'); + expect(result?.code).toMatchInlineSnapshot( + `";{try{(function(){var e=\\"undefined\\"!=typeof window?window:\\"undefined\\"!=typeof global?global:\\"undefined\\"!=typeof globalThis?globalThis:\\"undefined\\"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]=\\"d4309f93-5358-4ae1-bcf0-3813aa590eb5\\",e._sentryDebugIdIdentifier=\\"sentry-dbid-d4309f93-5358-4ae1-bcf0-3813aa590eb5\\");})();}catch(e){}};console.log(\\"Hello world\\");"` + ); }); it("should inject debug ID after 'use strict'", () => { @@ -32,7 +33,10 @@ describe("createRollupDebugIdInjectionHooks", () => { const result = hooks.renderChunk(code, { fileName: "bundle.js" }); expect(result).not.toBeNull(); - expect(result?.code).toMatch(/^"use strict";.*;{try/); + expect(result?.code).toMatchInlineSnapshot(` + "\\"use strict\\";;{try{(function(){var e=\\"undefined\\"!=typeof window?window:\\"undefined\\"!=typeof global?global:\\"undefined\\"!=typeof globalThis?globalThis:\\"undefined\\"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]=\\"79a86c07-8ecc-4367-82b0-88cf822f2d41\\",e._sentryDebugIdIdentifier=\\"sentry-dbid-79a86c07-8ecc-4367-82b0-88cf822f2d41\\");})();}catch(e){}}; + console.log(\\"Hello world\\");" + `); }); it.each([ @@ -46,7 +50,9 @@ describe("createRollupDebugIdInjectionHooks", () => { const result = hooks.renderChunk(code, { fileName }); expect(result).not.toBeNull(); - expect(result?.code).toContain("_sentryDebugIdIdentifier"); + expect(result?.code).toMatchInlineSnapshot( + `";{try{(function(){var e=\\"undefined\\"!=typeof window?window:\\"undefined\\"!=typeof global?global:\\"undefined\\"!=typeof globalThis?globalThis:\\"undefined\\"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]=\\"b80112c0-6818-486d-96f0-185c023439b4\\",e._sentryDebugIdIdentifier=\\"sentry-dbid-b80112c0-6818-486d-96f0-185c023439b4\\");})();}catch(e){}};console.log(\\"test\\");"` + ); }); it.each([["index.html"], ["styles.css"]])("should NOT process file '%s': %s", (fileName) => { From 71a281bac4f9988547228113d86f37029170c982 Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Mon, 29 Dec 2025 16:14:28 +0100 Subject: [PATCH 5/5] increase boundary to 6000 chars --- packages/bundler-plugin-core/src/index.ts | 2 +- packages/bundler-plugin-core/test/index.test.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/bundler-plugin-core/src/index.ts b/packages/bundler-plugin-core/src/index.ts index be2d24f9..a59eeca0 100644 --- a/packages/bundler-plugin-core/src/index.ts +++ b/packages/bundler-plugin-core/src/index.ts @@ -270,7 +270,7 @@ export function createRollupDebugIdInjectionHooks(): { ) ) { // Check if a debug ID has already been injected to avoid duplicate injection (e.g. by another plugin or Sentry CLI) - const chunkStartSnippet = code.slice(0, 2000); + const chunkStartSnippet = code.slice(0, 6000); const chunkEndSnippet = code.slice(-500); if ( diff --git a/packages/bundler-plugin-core/test/index.test.ts b/packages/bundler-plugin-core/test/index.test.ts index 61613641..9c69694e 100644 --- a/packages/bundler-plugin-core/test/index.test.ts +++ b/packages/bundler-plugin-core/test/index.test.ts @@ -84,12 +84,12 @@ describe("createRollupDebugIdInjectionHooks", () => { }); it("should only check boundaries for performance (not entire file)", () => { - // Inline format beyond first 2KB boundary - const codeWithInlineBeyond2KB = - "a".repeat(2100) + + // Inline format beyond first 6KB boundary + const codeWithInlineBeyond6KB = + "a".repeat(6100) + ';{try{(function(){var e="undefined"!=typeof window?window:e._sentryDebugIdIdentifier="sentry-dbid-existing-id");})();}catch(e){}};'; - expect(hooks.renderChunk(codeWithInlineBeyond2KB, { fileName: "bundle.js" })).not.toBeNull(); + expect(hooks.renderChunk(codeWithInlineBeyond6KB, { fileName: "bundle.js" })).not.toBeNull(); // Comment format beyond last 500 bytes boundary const codeWithCommentBeyond500B =