diff --git a/CHANGELOG.md b/CHANGELOG.md index a4383a395e..8fad966c3e 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 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 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?: {