Skip to content

Commit ef6f4bb

Browse files
authored
fix(rollup): Prevent double-injection of debug ID (#827)
* fix(rollup): Prevent double-injection of debug ID * prettier * fix linting issues * use matchInlineSnapshot fn * increase boundary to 6000 chars
1 parent 777850f commit ef6f4bb

File tree

2 files changed

+104
-1
lines changed

2 files changed

+104
-1
lines changed

packages/bundler-plugin-core/src/index.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,17 @@ export function createRollupDebugIdInjectionHooks(): {
269269
stripQueryAndHashFromPath(chunk.fileName).endsWith(ending)
270270
)
271271
) {
272+
// Check if a debug ID has already been injected to avoid duplicate injection (e.g. by another plugin or Sentry CLI)
273+
const chunkStartSnippet = code.slice(0, 6000);
274+
const chunkEndSnippet = code.slice(-500);
275+
276+
if (
277+
chunkStartSnippet.includes("_sentryDebugIdIdentifier") ||
278+
chunkEndSnippet.includes("//# debugId=")
279+
) {
280+
return null; // Debug ID already present, skip injection
281+
}
282+
272283
const debugId = stringToUUID(code); // generate a deterministic debug ID
273284
const codeToInject = getDebugIdSnippet(debugId);
274285

packages/bundler-plugin-core/test/index.test.ts

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { Compiler } from "webpack";
2-
import { getDebugIdSnippet, sentryUnpluginFactory } from "../src";
2+
import {
3+
getDebugIdSnippet,
4+
sentryUnpluginFactory,
5+
createRollupDebugIdInjectionHooks,
6+
} from "../src";
37

48
describe("getDebugIdSnippet", () => {
59
it("returns the debugId injection snippet for a passed debugId", () => {
@@ -10,6 +14,94 @@ describe("getDebugIdSnippet", () => {
1014
});
1115
});
1216

17+
describe("createRollupDebugIdInjectionHooks", () => {
18+
const hooks = createRollupDebugIdInjectionHooks();
19+
20+
describe("renderChunk", () => {
21+
it("should inject debug ID into clean JavaScript files", () => {
22+
const code = 'console.log("Hello world");';
23+
const result = hooks.renderChunk(code, { fileName: "bundle.js" });
24+
25+
expect(result).not.toBeNull();
26+
expect(result?.code).toMatchInlineSnapshot(
27+
`";{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\\");"`
28+
);
29+
});
30+
31+
it("should inject debug ID after 'use strict'", () => {
32+
const code = '"use strict";\nconsole.log("Hello world");';
33+
const result = hooks.renderChunk(code, { fileName: "bundle.js" });
34+
35+
expect(result).not.toBeNull();
36+
expect(result?.code).toMatchInlineSnapshot(`
37+
"\\"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){}};
38+
console.log(\\"Hello world\\");"
39+
`);
40+
});
41+
42+
it.each([
43+
["bundle.js"],
44+
["bundle.mjs"],
45+
["bundle.cjs"],
46+
["bundle.js?foo=bar"],
47+
["bundle.js#hash"],
48+
])("should process file '%s': %s", (fileName) => {
49+
const code = 'console.log("test");';
50+
const result = hooks.renderChunk(code, { fileName });
51+
52+
expect(result).not.toBeNull();
53+
expect(result?.code).toMatchInlineSnapshot(
54+
`";{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\\");"`
55+
);
56+
});
57+
58+
it.each([["index.html"], ["styles.css"]])("should NOT process file '%s': %s", (fileName) => {
59+
const code = 'console.log("test");';
60+
const result = hooks.renderChunk(code, { fileName });
61+
62+
expect(result).toBeNull();
63+
});
64+
65+
it.each([
66+
[
67+
"inline format at start",
68+
';{try{(function(){var e="undefined"!=typeof window?window:e._sentryDebugIdIdentifier="sentry-dbid-existing-id");})();}catch(e){}};console.log("test");',
69+
],
70+
[
71+
"comment format at end",
72+
'console.log("test");\n//# debugId=f6ccd6f4-7ea0-4854-8384-1c9f8340af81\n//# sourceMappingURL=bundle.js.map',
73+
],
74+
[
75+
"inline format with large file",
76+
'"use strict";\n' +
77+
"// comment\n".repeat(10) +
78+
';{try{(function(){var e="undefined"!=typeof window?window:e._sentryDebugIdIdentifier="sentry-dbid-existing-id");})();}catch(e){}};' +
79+
'\nconsole.log("line");\n'.repeat(100),
80+
],
81+
])("should NOT inject when debug ID already exists (%s)", (_description, code) => {
82+
const result = hooks.renderChunk(code, { fileName: "bundle.js" });
83+
expect(result).toBeNull();
84+
});
85+
86+
it("should only check boundaries for performance (not entire file)", () => {
87+
// Inline format beyond first 6KB boundary
88+
const codeWithInlineBeyond6KB =
89+
"a".repeat(6100) +
90+
';{try{(function(){var e="undefined"!=typeof window?window:e._sentryDebugIdIdentifier="sentry-dbid-existing-id");})();}catch(e){}};';
91+
92+
expect(hooks.renderChunk(codeWithInlineBeyond6KB, { fileName: "bundle.js" })).not.toBeNull();
93+
94+
// Comment format beyond last 500 bytes boundary
95+
const codeWithCommentBeyond500B =
96+
"//# debugId=f6ccd6f4-7ea0-4854-8384-1c9f8340af81\n" + "a".repeat(600);
97+
98+
expect(
99+
hooks.renderChunk(codeWithCommentBeyond500B, { fileName: "bundle.js" })
100+
).not.toBeNull();
101+
});
102+
});
103+
});
104+
13105
describe("sentryUnpluginFactory sourcemaps.disable behavior", () => {
14106
const mockReleaseInjectionPlugin = jest.fn((_injectionCode: string) => ({
15107
name: "mock-release-injection-plugin",

0 commit comments

Comments
 (0)