diff --git a/packages/bundler-plugin-core/src/debug-id-upload.ts b/packages/bundler-plugin-core/src/debug-id-upload.ts index 7b35c459..dc672a8a 100644 --- a/packages/bundler-plugin-core/src/debug-id-upload.ts +++ b/packages/bundler-plugin-core/src/debug-id-upload.ts @@ -8,7 +8,7 @@ import { promisify } from "util"; import SentryCli from "@sentry/cli"; import { dynamicSamplingContextToSentryBaggageHeader } from "@sentry/utils"; import { safeFlushTelemetry } from "./sentry/telemetry"; -import { stripQueryAndHashFromPath } from "./utils"; +import { getDebugIdFromMagicComment, stripQueryAndHashFromPath } from "./utils"; import { setMeasurement, spanToTraceHeader, startSpan } from "@sentry/core"; import { getDynamicSamplingContextFromSpan, Scope } from "@sentry/core"; import { Client } from "@sentry/types"; @@ -223,7 +223,8 @@ export async function prepareBundleForDebugIdUpload( return; } - const debugId = determineDebugIdFromBundleSource(bundleContent); + const debugIdFromMagicComment = getDebugIdFromMagicComment(bundleContent); + const debugId = debugIdFromMagicComment || getDebugIdFromPolyfill(bundleContent); if (debugId === undefined) { logger.debug( `Could not determine debug ID from bundle. This can happen if you did not clean your output folder before installing the Sentry plugin. File will not be source mapped: ${bundleFilePath}` @@ -233,12 +234,14 @@ export async function prepareBundleForDebugIdUpload( const uniqueUploadName = `${debugId}-${chunkIndex}`; - bundleContent += `\n//# debugId=${debugId}`; - const writeSourceFilePromise = fs.promises.writeFile( - path.join(uploadFolder, `${uniqueUploadName}.js`), - bundleContent, - "utf-8" - ); + // Only add the debug ID magic comment if source file does not already contain it + const writeSourceFilePromise = debugIdFromMagicComment + ? Promise.resolve() + : fs.promises.writeFile( + path.join(uploadFolder, `${uniqueUploadName}.js`), + bundleContent + `\n//# debugId=${debugId}`, + "utf-8" + ); const writeSourceMapFilePromise = determineSourceMapPathFromBundle( bundleFilePath, @@ -266,7 +269,7 @@ export async function prepareBundleForDebugIdUpload( * * The string pattern is injected via the debug ID injection snipped. */ -function determineDebugIdFromBundleSource(code: string): string | undefined { +function getDebugIdFromPolyfill(code: string): string | undefined { const match = code.match( /sentry-dbid-([0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12})/ ); diff --git a/packages/bundler-plugin-core/src/index.ts b/packages/bundler-plugin-core/src/index.ts index 0dcf2785..57058d6d 100644 --- a/packages/bundler-plugin-core/src/index.ts +++ b/packages/bundler-plugin-core/src/index.ts @@ -16,10 +16,10 @@ import { generateGlobalInjectorCode, generateModuleMetadataInjectorCode, getDependencies, + getDebugIdForCode, getPackageJson, parseMajorVersion, replaceBooleanFlagsInCode, - stringToUUID, stripQueryAndHashFromPath, } from "./utils"; import * as dotenv from "dotenv"; @@ -588,8 +588,9 @@ export function createRollupDebugIdInjectionHooks() { stripQueryAndHashFromPath(chunk.fileName).endsWith(ending) ) ) { - const debugId = stringToUUID(code); // generate a deterministic debug ID - const codeToInject = getDebugIdSnippet(debugId); + // Gets an existing debug ID or generates a deterministic debug ID + const debugId = getDebugIdForCode(code); + const codeToInject = getDebugIdPolyfillSnippet(debugId); const ms = new MagicString(code, { filename: chunk.fileName }); @@ -740,7 +741,7 @@ export function createComponentNameAnnotateHooks(ignoredComponents?: string[]) { }; } -export function getDebugIdSnippet(debugId: string): string { +export function getDebugIdPolyfillSnippet(debugId: string): string { return `;{try{let 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]="${debugId}",e._sentryDebugIdIdentifier="sentry-dbid-${debugId}")}catch(e){}};`; } diff --git a/packages/bundler-plugin-core/src/utils.ts b/packages/bundler-plugin-core/src/utils.ts index 24e2049e..b4704215 100644 --- a/packages/bundler-plugin-core/src/utils.ts +++ b/packages/bundler-plugin-core/src/utils.ts @@ -169,6 +169,26 @@ function lookupPackageJson(cwd: string, stopAt: string): PackageJson | undefined return lookupPackageJson(newCwd, stopAt); } +const DEBUG_ID_REGEX = /\/\/# debugId=([a-fA-F0-9-]+)(?![\s\S]*\/\/# debugId=)/m; + +/** + * Returns a debug ID if one can be found in a magic comment + */ +export function getDebugIdFromMagicComment(code: string): string | undefined { + const match = code.match(DEBUG_ID_REGEX); + return match?.[1]; +} + +/** + * Gets a debug ID for the passed source code. + * + * If the source already contains a debug ID magic comment, that existing debug + * ID is used, otherwise a debug ID is created via hashing + */ +export function getDebugIdForCode(code: string): string { + return getDebugIdFromMagicComment(code) || stringToUUID(code); +} + /** * Deterministically hashes a string and turns the hash into a uuid. */ diff --git a/packages/bundler-plugin-core/test/index.test.ts b/packages/bundler-plugin-core/test/index.test.ts index 6e981ced..c41866a3 100644 --- a/packages/bundler-plugin-core/test/index.test.ts +++ b/packages/bundler-plugin-core/test/index.test.ts @@ -1,8 +1,8 @@ -import { getDebugIdSnippet } from "../src"; +import { getDebugIdPolyfillSnippet } from "../src"; -describe("getDebugIdSnippet", () => { +describe("getDebugIdPolyfillSnippet", () => { it("returns the debugId injection snippet for a passed debugId", () => { - const snippet = getDebugIdSnippet("1234"); + const snippet = getDebugIdPolyfillSnippet("1234"); expect(snippet).toMatchInlineSnapshot( `";{try{let 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]=\\"1234\\",e._sentryDebugIdIdentifier=\\"sentry-dbid-1234\\")}catch(e){}};"` ); diff --git a/packages/esbuild-plugin/src/index.ts b/packages/esbuild-plugin/src/index.ts index 08cc17c4..05884b75 100644 --- a/packages/esbuild-plugin/src/index.ts +++ b/packages/esbuild-plugin/src/index.ts @@ -1,7 +1,7 @@ import { sentryUnpluginFactory, Options, - getDebugIdSnippet, + getDebugIdPolyfillSnippet, SentrySDKBuildFlags, } from "@sentry/bundler-plugin-core"; import type { Logger } from "@sentry/bundler-plugin-core"; @@ -125,7 +125,7 @@ function esbuildDebugIdInjectionPlugin(logger: Logger): UnpluginOptions { return { loader: "js", pluginName, - contents: getDebugIdSnippet(uuidv4()), + contents: getDebugIdPolyfillSnippet(uuidv4()), }; }); }, diff --git a/packages/webpack-plugin/src/webpack4and5.ts b/packages/webpack-plugin/src/webpack4and5.ts index e9304038..d8122fad 100644 --- a/packages/webpack-plugin/src/webpack4and5.ts +++ b/packages/webpack-plugin/src/webpack4and5.ts @@ -1,5 +1,5 @@ import { - getDebugIdSnippet, + getDebugIdPolyfillSnippet, Options, sentryUnpluginFactory, stringToUUID, @@ -123,7 +123,7 @@ function webpackDebugIdInjectionPlugin( banner: (arg?: BannerPluginCallbackArg) => { const hash = arg?.chunk?.contentHash?.javascript ?? arg?.chunk?.hash; const debugId = hash ? stringToUUID(hash) : uuidv4(); - return getDebugIdSnippet(debugId); + return getDebugIdPolyfillSnippet(debugId); }, }) );