Skip to content
Open
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
11 changes: 11 additions & 0 deletions packages/bundler-plugin-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,17 @@ 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);

Expand Down
88 changes: 87 additions & 1 deletion packages/bundler-plugin-core/test/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
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", () => {
Expand All @@ -10,6 +14,88 @@ 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"],
["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 });

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([
[
"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",
Expand Down
Loading