diff --git a/packages/server-utils/src/orchestrion/bundler/subscribeInjection.ts b/packages/server-utils/src/orchestrion/bundler/subscribeInjection.ts index 9bb89257ab89..b0db052bfa71 100644 --- a/packages/server-utils/src/orchestrion/bundler/subscribeInjection.ts +++ b/packages/server-utils/src/orchestrion/bundler/subscribeInjection.ts @@ -10,6 +10,13 @@ import type { PluginOptions } from './options'; // once per file. A `WeakSet` keyed by the node avoids mutating the emitted AST. const injectedPrograms = new WeakSet(); +/** + * Assignment target that keeps the injected call from being tree-shaken. See + * {@link subscribeSnippet}. The value written is always `undefined`; only the + * assignment matters. + */ +const SUBSCRIBE_INJECTION_SINK = 'globalThis.__SENTRY_ORCHESTRION_INJECT__'; + interface ProgramNode { type: string; body: Array<{ type: string; directive?: string }>; @@ -28,13 +35,20 @@ interface ProgramNode { * "only-active-when-bundled" property the runtime module hook gives unbundled * Node, but without a hook (workerd can't monkey-patch requires). The helper is * generic (references no factory), so importing it alongside doesn't pull siblings. + * + * The call result is assigned to a global rather than discarded. The helper + * returns `void` and `@sentry/server-utils` is `sideEffects: false`, so a bare + * call statement is something a bundler can prove droppable: rollup >= 4.63.0 + * does exactly that and removes the whole registration, leaving the module + * instrumented but unsubscribed. Writing to a property of `globalThis` is a + * side effect no bundler can shake out, so the call survives. */ function subscribeSnippet(exportName: string, esm: boolean): string { const importStmt = esm ? `import { ${exportName}, registerOrchestrionChannelIntegration } from '@sentry/server-utils/orchestrion';` : `const { ${exportName}, registerOrchestrionChannelIntegration } = require('@sentry/server-utils/orchestrion');`; - return `${importStmt}\nregisterOrchestrionChannelIntegration(${JSON.stringify(exportName)}, ${exportName});`; + return `${importStmt}\n${SUBSCRIBE_INJECTION_SINK} = registerOrchestrionChannelIntegration(${JSON.stringify(exportName)}, ${exportName});`; } /** diff --git a/packages/server-utils/test/orchestrion/subscribeInjection.test.ts b/packages/server-utils/test/orchestrion/subscribeInjection.test.ts index eaa94cf5173d..f6c595209068 100644 --- a/packages/server-utils/test/orchestrion/subscribeInjection.test.ts +++ b/packages/server-utils/test/orchestrion/subscribeInjection.test.ts @@ -75,6 +75,10 @@ describe('subscribe-injection transform option', () => { expect(result!.code).toContain( 'registerOrchestrionChannelIntegration("mysqlChannelIntegration", mysqlChannelIntegration)', ); + // The result is assigned to a global. `@sentry/server-utils` is `sideEffects: false` and the + // helper returns `void`, so a bare call statement is one a bundler can prove droppable. + // rollup >= 4.63.0 removes it, leaving the module instrumented but unsubscribed. + expect(result!.code).toContain('globalThis.__SENTRY_ORCHESTRION_INJECT__ = registerOrchestrionChannelIntegration('); // No separate @sentry/core import at the injection site — the helper owns that. expect(result!.code).not.toContain('@sentry/core'); // It imports ONLY the mysql factory — no central dispatch pulling in others.