From 27c07c14a89cb42c8117b8b27a12c68569a44c46 Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Thu, 10 Sep 2026 10:26:30 +0200 Subject: [PATCH] fix(server-utils): Forward resolveId options in the orchestrion Rollup plugin --- .../src/orchestrion/bundler/rollup.ts | 22 ++++++++-- .../test/orchestrion/bundler.test.ts | 40 +++++++++++++++++++ 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/packages/server-utils/src/orchestrion/bundler/rollup.ts b/packages/server-utils/src/orchestrion/bundler/rollup.ts index 394874a26fdd..87bac2e1e00c 100644 --- a/packages/server-utils/src/orchestrion/bundler/rollup.ts +++ b/packages/server-utils/src/orchestrion/bundler/rollup.ts @@ -1,5 +1,12 @@ import codeTransformer from '@apm-js-collab/code-transformer-bundler-plugins/rollup'; -import type { ExternalOption, InputOptions, NormalizedInputOptions, Plugin, PluginContext } from 'rollup'; +import type { + ExternalOption, + InputOptions, + NormalizedInputOptions, + Plugin, + PluginContext, + ResolveIdHook, +} from 'rollup'; export type { Plugin as RollupPlugin } from 'rollup'; import { instrumentedModuleNames } from '../config'; @@ -60,11 +67,20 @@ export function sentryOrchestrionPlugin(options: PluginOptions = {}): Plugin { // specifier doesn't resolve from an instrumented package's location, so when // normal resolution fails, fall back to this package's own resolution so it // gets bundled from its real on-disk path. - async resolveId(this: PluginContext, source: string, importer: string | undefined) { + // + // Forward `options` unchanged: it carries the `custom` metadata `@rollup/plugin-commonjs` + // uses to recognize a `require()` it is already resolving. Drop it and that plugin warns + // (THIS_RESOLVE_WITHOUT_OPTIONS), then abandons the resolution. + async resolveId( + this: PluginContext, + source: string, + importer: string | undefined, + options: Parameters[2], + ) { if (source !== SNIPPET_IMPORT_SPECIFIER) { return null; } - const resolved = await this.resolve(source, importer, { skipSelf: true }); + const resolved = await this.resolve(source, importer, { ...options, skipSelf: true }); if (resolved) { return resolved; } diff --git a/packages/server-utils/test/orchestrion/bundler.test.ts b/packages/server-utils/test/orchestrion/bundler.test.ts index 7880c47cbcaa..93836ef96ebf 100644 --- a/packages/server-utils/test/orchestrion/bundler.test.ts +++ b/packages/server-utils/test/orchestrion/bundler.test.ts @@ -68,6 +68,46 @@ describe('sentryOrchestrionPlugin (rollup)', () => { // skips the warning when external is not a normalized predicate expect(warn).not.toHaveBeenCalled(); }); + + it('forwards the resolveId options to this.resolve and falls back to self-resolution', async () => { + const plugin = rollupPlugin(); + const resolveId = plugin.resolveId as ( + this: unknown, + source: string, + importer: string | undefined, + opts: unknown, + ) => Promise; + + const resolve = vi.fn().mockResolvedValue(null); + // `custom` is what `@rollup/plugin-commonjs` uses to recognise its own `require()` resolution; + // dropping it makes the plugin warn (THIS_RESOLVE_WITHOUT_OPTIONS) and abandon the resolution. + const options = { attributes: {}, custom: { 'node-resolve': { isRequire: true } }, isEntry: false }; + + resolve.mockResolvedValueOnce({ id: '/resolved.js' }); + await expect(resolveId.call({ resolve }, '@sentry/server-utils', '/x.js', options)).resolves.toEqual({ + id: '/resolved.js', + }); + expect(resolve).toHaveBeenCalledWith('@sentry/server-utils', '/x.js', { ...options, skipSelf: true }); + + // When it fails (pnpm isolation), fall back to this package's own resolution. + const fallback = await resolveId.call({ resolve }, '@sentry/server-utils', '/x.js', options); + expect(typeof fallback).toBe('string'); + expect(fallback).toContain('server-utils'); + }); + + it('ignores specifiers other than the injected snippet import', async () => { + const plugin = rollupPlugin(); + const resolveId = plugin.resolveId as ( + this: unknown, + source: string, + importer: string | undefined, + opts: unknown, + ) => Promise; + + const resolve = vi.fn(); + await expect(resolveId.call({ resolve }, 'mysql', '/x.js', { attributes: {}, isEntry: false })).resolves.toBeNull(); + expect(resolve).not.toHaveBeenCalled(); + }); }); describe('sentryOrchestrionPlugin (esbuild)', () => {