diff --git a/packages/bundler-plugin-core/src/debug-id-upload.ts b/packages/bundler-plugin-core/src/debug-id-upload.ts index 4de1aaa6..b2bf8663 100644 --- a/packages/bundler-plugin-core/src/debug-id-upload.ts +++ b/packages/bundler-plugin-core/src/debug-id-upload.ts @@ -219,7 +219,10 @@ async function prepareSourceMapForDebugIdUpload( } if (map["sources"] && Array.isArray(map["sources"])) { - map["sources"] = map["sources"].map((source: string) => rewriteSourcesHook(source, map)); + const mapDir = path.dirname(sourceMapPath); + map["sources"] = map["sources"].map((source: string) => + rewriteSourcesHook(source, map, { mapDir }) + ); } try { diff --git a/packages/bundler-plugin-core/src/types.ts b/packages/bundler-plugin-core/src/types.ts index 1c00c94a..2d197e3b 100644 --- a/packages/bundler-plugin-core/src/types.ts +++ b/packages/bundler-plugin-core/src/types.ts @@ -138,6 +138,9 @@ export interface Options { /** * Hook to rewrite the `sources` field inside the source map before being uploaded to Sentry. Does not modify the actual source map. * + * The hook receives the source path, the parsed source map object, and a context object containing `mapDir` - + * the directory of the source map file, useful for resolving relative source paths. + * * Defaults to making all sources relative to `process.cwd()` while building. */ rewriteSources?: RewriteSourcesHook; @@ -427,7 +430,7 @@ export interface Options { } // eslint-disable-next-line @typescript-eslint/no-explicit-any -export type RewriteSourcesHook = (source: string, map: any) => string; +export type RewriteSourcesHook = (source: string, map: any, context?: { mapDir: string }) => string; export type ResolveSourceMapHook = ( artifactPath: string, diff --git a/packages/bundler-plugin-core/test/debug-id-upload.test.ts b/packages/bundler-plugin-core/test/debug-id-upload.test.ts new file mode 100644 index 00000000..bbcc8621 --- /dev/null +++ b/packages/bundler-plugin-core/test/debug-id-upload.test.ts @@ -0,0 +1,71 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import * as fs from "fs"; +import * as path from "path"; +import * as os from "os"; +import { prepareBundleForDebugIdUpload } from "../src/debug-id-upload"; +import type { RewriteSourcesHook } from "../src/types"; +import { Logger } from "../src"; + +describe("prepareBundleForDebugIdUpload", () => { + let tmpDir: string; + + beforeEach(() => { + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "sentry-test-")); + }); + + afterEach(() => { + fs.rmSync(tmpDir, { recursive: true, force: true }); + }); + + it("passes mapDir context to rewriteSources hook", async () => { + const bundleDir = path.join(tmpDir, "src"); + const uploadDir = path.join(tmpDir, "upload"); + fs.mkdirSync(bundleDir, { recursive: true }); + fs.mkdirSync(uploadDir, { recursive: true }); + + const debugId = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"; + const bundlePath = path.join(bundleDir, "bundle.js"); + const mapPath = path.join(bundleDir, "bundle.js.map"); + + // Bundle with debug ID snippet and sourceMappingURL + fs.writeFileSync( + bundlePath, + `"use strict";\n// some code\n;!function(){try{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]="${debugId}",e._sentryDebugIdIdentifier="sentry-dbid-${debugId}")}catch(e){}}();\n//# sourceMappingURL=bundle.js.map` + ); + + // Source map file + fs.writeFileSync( + mapPath, + JSON.stringify({ + version: 3, + sources: ["../original/file.ts"], + mappings: "AAAA", + }) + ); + + const capturedContexts: Array<{ mapDir?: string } | undefined> = []; + const rewriteHook: RewriteSourcesHook = (source, _map, context) => { + capturedContexts.push(context); + return source; + }; + + const logger = { + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + debug: vi.fn(), + }; + + await prepareBundleForDebugIdUpload( + bundlePath, + uploadDir, + 0, + logger as Logger, + rewriteHook, + undefined + ); + + expect(capturedContexts).toHaveLength(1); + expect(capturedContexts[0]!.mapDir).toBe(bundleDir); + }); +}); diff --git a/packages/dev-utils/src/generate-documentation-table.ts b/packages/dev-utils/src/generate-documentation-table.ts index 4c0bf776..c7c5f777 100644 --- a/packages/dev-utils/src/generate-documentation-table.ts +++ b/packages/dev-utils/src/generate-documentation-table.ts @@ -95,9 +95,9 @@ errorHandler: (err) => { }, { name: "rewriteSources", - type: "(source: string, map: any) => string", + type: "(source: string, map: any, context?: { mapDir: string }) => string", fullDescription: - "Hook to rewrite the `sources` field inside the source map before being uploaded to Sentry. Does not modify the actual source map. Effectively, this modifies how files inside the stacktrace will show up in Sentry.\n\nDefaults to making all sources relative to `process.cwd()` while building.", + "Hook to rewrite the `sources` field inside the source map before being uploaded to Sentry. Does not modify the actual source map. Effectively, this modifies how files inside the stacktrace will show up in Sentry.\n\nThe `context.mapDir` parameter provides the directory path of the source map file, which is useful for resolving relative source paths (e.g. `path.resolve(context.mapDir, source)`).\n\nDefaults to making all sources relative to `process.cwd()` while building.", }, { name: "resolveSourceMap",