From 1401ae27db68241507726fd1305f83fe5686b4c2 Mon Sep 17 00:00:00 2001 From: Alexander Pantiukhov Date: Thu, 6 Nov 2025 17:36:51 +0100 Subject: [PATCH 1/3] Fix how bundle IDs are getting defined for individual bundles --- .../core/src/js/tools/sentryMetroSerializer.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/core/src/js/tools/sentryMetroSerializer.ts b/packages/core/src/js/tools/sentryMetroSerializer.ts index 8e31f512b9..7e17dc1527 100644 --- a/packages/core/src/js/tools/sentryMetroSerializer.ts +++ b/packages/core/src/js/tools/sentryMetroSerializer.ts @@ -74,11 +74,19 @@ export const createSentryMetroSerializer = (customSerializer?: MetroSerializer): const { code: bundleCode, map: bundleMapString } = await extractSerializerResult(serializerResult); // Add debug id comment to the bundle - const debugId = determineDebugIdFromBundleSource(bundleCode); + let debugId = determineDebugIdFromBundleSource(bundleCode); if (!debugId) { - throw new Error( - 'Debug ID was not found in the bundle. Call `options.sentryBundleCallback` if you are using a custom serializer.', - ); + // For lazy-loaded chunks or bundles without the debug ID module, + // calculate the debug ID from the bundle content. + // This ensures Metro 0.83.2+ code-split bundles get debug IDs. + // That needs to be done because when Metro 0.83.2 stopped importing `BabelSourceMapSegment` + // from `@babel/generator` and defined it locally, it subtly changed the source map output format. + // https://github.com/facebook/metro/blob/main/packages/metro-source-map/src/source-map.js#L47 + const hash = crypto.createHash('md5'); + hash.update(bundleCode); + debugId = stringToUUID(hash.digest('hex')); + // eslint-disable-next-line no-console + console.log('info ' + `Bundle Debug ID (calculated): ${debugId}`); } // Only print debug id for command line builds => not hot reload from dev server // eslint-disable-next-line no-console From ef6c42ef159c8a2e49bd882f19b40baf747dac51 Mon Sep 17 00:00:00 2001 From: Alexander Pantiukhov Date: Fri, 7 Nov 2025 09:47:37 +0100 Subject: [PATCH 2/3] Changelog update --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8e4e0ca67..78a2467936 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ ### Fixes - Android SDK not being disabled when `options.enabled` is set to `false` ([#5334](https://github.com/getsentry/sentry-react-native/pull/5334)) +- Fixes how bundle IDs are getting defined for individual bundles ([#5342](https://github.com/getsentry/sentry-react-native/pull/5342)) ### Dependencies From 31ee206bf06e290ce88a7c69c9237082411ace1a Mon Sep 17 00:00:00 2001 From: Alexander Pantiukhov Date: Mon, 10 Nov 2025 15:52:34 +0100 Subject: [PATCH 3/3] Added test --- .../test/tools/sentryMetroSerializer.test.ts | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/packages/core/test/tools/sentryMetroSerializer.test.ts b/packages/core/test/tools/sentryMetroSerializer.test.ts index e654d59490..071ff2cd46 100644 --- a/packages/core/test/tools/sentryMetroSerializer.test.ts +++ b/packages/core/test/tools/sentryMetroSerializer.test.ts @@ -65,6 +65,49 @@ describe('Sentry Metro Serializer', () => { expect(debugId).toMatch(/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/); } }); + + test('calculates debug id from bundle code when debug id module is not found', async () => { + // Create a custom serializer that returns bundle code without the debug ID module + const customSerializer: MetroSerializer = async () => { + const bundleCodeWithoutDebugId = 'console.log("test bundle");'; + return { + code: bundleCodeWithoutDebugId, + map: '{"version":3,"sources":[],"names":[],"mappings":""}', + }; + }; + + const serializer = createSentryMetroSerializer(customSerializer); + const bundle = await serializer(...mockMinSerializerArgs()); + + if (typeof bundle === 'string') { + fail('Expected bundle to be an object with a "code" property'); + } + + // The debug ID should be calculated from the bundle code content + // and added as a comment in the bundle code + expect(bundle.code).toContain('//# debugId='); + + // Extract the debug ID from the comment + const debugIdMatch = bundle.code.match(/\/\/# debugId=([0-9a-fA-F-]+)/); + expect(debugIdMatch).toBeTruthy(); + const debugId = debugIdMatch?.[1]; + + // Verify it's a valid UUID format + expect(debugId).toMatch(/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/); + + // Verify the debug ID is also in the source map + const sourceMap = JSON.parse(bundle.map); + expect(sourceMap.debug_id).toBe(debugId); + expect(sourceMap.debugId).toBe(debugId); + + // The calculated debug ID should be deterministic based on the bundle content + // Running the serializer again with the same content should produce the same debug ID + const bundle2 = await serializer(...mockMinSerializerArgs()); + if (typeof bundle2 !== 'string') { + const debugIdMatch2 = bundle2.code.match(/\/\/# debugId=([0-9a-fA-F-]+)/); + expect(debugIdMatch2?.[1]).toBe(debugId); + } + }); }); function mockMinSerializerArgs(options?: {