Skip to content

Commit 77f71ea

Browse files
authored
fix(bundler-plugins): Inject debug IDs into every esbuild rebuild (#26099)
The esbuild plugin cleared its set of wrapped entry points in `setup`, which esbuild runs once per context. On `context().rebuild()` and in watch mode, every entry point after the first build counted as already wrapped, so the rebuilt bundles carried no debug ID and the upload warned that no source maps were found. The reset now runs in an `onStart` hook, once per build. Found while reviewing #25648, where the missing debug IDs also kept the plugin from cleaning up the maps it had turned on. Fixes #26098
1 parent f2ee787 commit 77f71ea

2 files changed

Lines changed: 62 additions & 3 deletions

File tree

‎packages/bundler-plugins/src/esbuild/index.ts‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ interface EsbuildPluginBuild {
6767
options: { filter: RegExp },
6868
callback: (args: EsbuildOnResolveArgs) => EsbuildOnResolveResult | undefined,
6969
) => void;
70+
onStart: (callback: () => void) => void;
7071
onEnd: (callback: (result: EsbuildOnEndArgs) => void | Promise<void>) => void;
7172
}
7273

@@ -156,7 +157,7 @@ export function sentryEsbuildPlugin(userOptions: Options = {}): any {
156157

157158
return {
158159
name: pluginName,
159-
setup({ initialOptions, onLoad, onResolve, onEnd }: EsbuildPluginBuild) {
160+
setup({ initialOptions, onLoad, onResolve, onStart, onEnd }: EsbuildPluginBuild) {
160161
// Release and/or metadata injection
161162
if (!staticInjectionCode.isEmpty()) {
162163
const virtualInjectionFilePath = path.resolve('_sentry-injection-stub');
@@ -194,8 +195,9 @@ export function sentryEsbuildPlugin(userOptions: Options = {}): any {
194195

195196
// Debug ID injection - requires per-entry-point unique IDs
196197
if (sourcemapsEnabled) {
197-
// Clear state from previous builds (important for watch mode and test suites)
198-
debugIdWrappedPaths.clear();
198+
// `setup` runs once per context, `onStart` once per build, so a
199+
// rebuild wraps the entry points again.
200+
onStart(() => debugIdWrappedPaths.clear());
199201

200202
if (!initialOptions.bundle) {
201203
logger.warn(
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import * as fs from 'fs';
2+
import * as os from 'os';
3+
import * as path from 'path';
4+
import * as esbuild from 'esbuild';
5+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
6+
import { sentryEsbuildPlugin } from '../../src/esbuild';
7+
8+
const DEBUG_ID_MARKER = /sentry-dbid-([0-9a-f-]{36})/;
9+
10+
describe('rebuilds', () => {
11+
let tmpDir: string;
12+
let entry: string;
13+
let outDir: string;
14+
15+
beforeEach(() => {
16+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'sentry-esbuild-rebuild-'));
17+
entry = path.join(tmpDir, 'entry.js');
18+
outDir = path.join(tmpDir, 'dist');
19+
fs.writeFileSync(entry, 'export const answer = 42;\nconsole.log(answer);\n');
20+
});
21+
22+
afterEach(() => {
23+
fs.rmSync(tmpDir, { recursive: true, force: true });
24+
});
25+
26+
function readDebugIds(): { bundle: string | undefined; map: unknown } {
27+
const bundle = fs.readFileSync(path.join(outDir, 'entry.js'), 'utf8');
28+
const map = JSON.parse(fs.readFileSync(path.join(outDir, 'entry.js.map'), 'utf8'));
29+
return { bundle: bundle.match(DEBUG_ID_MARKER)?.[1], map: map.debug_id };
30+
}
31+
32+
it('injects a debug ID into every rebuild of a context', async () => {
33+
const context = await esbuild.context({
34+
entryPoints: [entry],
35+
bundle: true,
36+
sourcemap: true,
37+
outdir: outDir,
38+
absWorkingDir: tmpDir,
39+
plugins: [sentryEsbuildPlugin({ telemetry: false, sourcemaps: { disable: 'disable-upload' } })],
40+
});
41+
42+
try {
43+
await context.rebuild();
44+
const first = readDebugIds();
45+
expect(first.bundle).toBeDefined();
46+
expect(first.map).toBe(first.bundle);
47+
48+
fs.writeFileSync(entry, 'export const answer = 43;\nconsole.log(answer);\n');
49+
await context.rebuild();
50+
const second = readDebugIds();
51+
expect(second.bundle).toBeDefined();
52+
expect(second.map).toBe(second.bundle);
53+
} finally {
54+
await context.dispose();
55+
}
56+
});
57+
});

0 commit comments

Comments
 (0)