From 9d5002b77a2ed6cd7044d13221031244eaded729 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20de=20Faria?= Date: Wed, 14 Oct 2026 04:51:52 -0300 Subject: [PATCH 1/3] fix(node): restore tsx runtime instrumentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes getsentry/sentry-javascript#24787. Signed-off-by: João Pedro de Faria Co-Authored-By: GPT-6.1 Sol --- .../tracing/orchestrion-tsx/scenario.cjs | 23 +++++++++++++ .../tracing/orchestrion-tsx/scenario.mjs | 14 ++++++++ .../suites/tracing/orchestrion-tsx/test.ts | 21 ++++++++++++ .../server-runtime-injection/src/register.ts | 33 +++++++++++++++++-- .../test/register.test.ts | 13 ++++++++ 5 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/scenario.cjs create mode 100644 dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/scenario.mjs create mode 100644 dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/test.ts diff --git a/dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/scenario.cjs b/dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/scenario.cjs new file mode 100644 index 000000000000..cbc31405b4b2 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/scenario.cjs @@ -0,0 +1,23 @@ +const Sentry = require('@sentry/node'); +const { tracingChannel } = require('node:diagnostics_channel'); + +Sentry.init({ tracesSampleRate: 0 }); + +let loads = 0; +tracingChannel('orchestrion:dataloader:load').start.subscribe(() => { + loads++; +}); + +async function run() { + const observations = []; + for (let attempt = 0; attempt < 2; attempt++) { + const DataLoader = require('dataloader'); + const value = await new DataLoader(async keys => keys).load('real-key'); + observations.push({ value, loads }); + delete require.cache[require.resolve('dataloader')]; + } + await Sentry.close(1000); + process.stdout.write(JSON.stringify(observations)); +} + +run(); diff --git a/dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/scenario.mjs b/dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/scenario.mjs new file mode 100644 index 000000000000..8f620616f58f --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/scenario.mjs @@ -0,0 +1,14 @@ +import * as Sentry from '@sentry/node'; +import { tracingChannel } from 'node:diagnostics_channel'; + +Sentry.init({ tracesSampleRate: 0 }); + +let loads = 0; +tracingChannel('orchestrion:dataloader:load').start.subscribe(() => { + loads++; +}); + +const { default: DataLoader } = await import(`${import.meta.resolve('dataloader')}?instrumentation-test`); +const value = await new DataLoader(async keys => keys).load('real-key'); +await Sentry.close(1000); +process.stdout.write(JSON.stringify([{ value, loads }])); diff --git a/dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/test.ts b/dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/test.ts new file mode 100644 index 000000000000..3e664ca2b147 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/test.ts @@ -0,0 +1,21 @@ +import { spawnSync } from 'node:child_process'; +import { join } from 'node:path'; +import { expect, test } from 'vitest'; + +test.each([ + { name: 'plain Node CommonJS', flags: [], scenario: 'scenario.cjs', operations: 2 }, + { name: 'tsx CommonJS', flags: ['--require', 'tsx/cjs'], scenario: 'scenario.cjs', operations: 2 }, + { name: 'plain Node ESM', flags: [], scenario: 'scenario.mjs', operations: 1 }, + { name: 'tsx ESM', flags: ['--import', 'tsx'], scenario: 'scenario.mjs', operations: 1 }, +])('instruments each operation exactly once with $name', ({ flags, scenario, operations }) => { + const result = spawnSync(process.execPath, [...flags, join(__dirname, scenario)], { + encoding: 'utf8', + timeout: 30_000, + }); + + expect(result.error).toBeUndefined(); + expect(result.status, result.stderr).toBe(0); + expect(JSON.parse(result.stdout)).toEqual( + Array.from({ length: operations }, (_, index) => ({ value: 'real-key', loads: index + 1 })), + ); +}); diff --git a/packages/server-runtime-injection/src/register.ts b/packages/server-runtime-injection/src/register.ts index fd9289aeb26d..9fc7ab715e51 100644 --- a/packages/server-runtime-injection/src/register.ts +++ b/packages/server-runtime-injection/src/register.ts @@ -10,6 +10,11 @@ import { initialize, load, resolve, createDiagnosticsPort } from '@apm-js-collab import { setDiagnosticsHook } from '@apm-js-collab/tracing-hooks/lib/diagnostics.js'; type NodeModule = { + Module: { + prototype: { + _compile: (this: unknown, content: string, filename: string, ...args: unknown[]) => unknown; + }; + }; registerHooks?: (options: { load: Function; resolve: Function }) => { deregister: () => void }; register?: typeof register; }; @@ -192,7 +197,7 @@ export function registerDiagnosticsChannelInjection(): void { // `Module.registerHooks` / `Module.register` are newer than the @types/node // we build against, hence the cast. - const mod = Module as NodeModule; + const mod = Module as unknown as NodeModule; setDiagnosticsHook(({ url, moduleName, error }): void => { if (error) { @@ -224,7 +229,31 @@ export function registerDiagnosticsChannelInjection(): void { try { if (typeof mod.registerHooks === 'function' && stableSyncHooks) { initialize({ instrumentations: SENTRY_RUNTIME_INSTRUMENTATIONS }); - mod.registerHooks({ resolve, load: globalAny.Deno ? withDenoFormats(load) : load }); + const isNode = !globalAny.Bun && !globalAny.Deno; + const filesLoadedByHook = new Set(); + const loadHook = globalAny.Deno ? withDenoFormats(load) : load; + mod.registerHooks({ + resolve, + load: (url: string, context: unknown, nextLoad: Function) => { + const result = loadHook(url, context, nextLoad) as { format?: string } | undefined; + if (isNode && result?.format === 'commonjs') { + filesLoadedByHook.add(url); + } + return result; + }, + }); + + if (isNode) { + // Custom require hooks bypass load. Avoid transforming files twice when load already handled them. + const prototype = mod.Module.prototype; + const originalCompile = prototype._compile; + new ModulePatch({ instrumentations: SENTRY_RUNTIME_INSTRUMENTATIONS }).patch(); + const patchedCompile = prototype._compile; + prototype._compile = function (content, filename, ...args) { + const compile = filesLoadedByHook.delete(pathToFileURL(filename).href) ? originalCompile : patchedCompile; + return compile.call(this, content, filename, ...args); + }; + } debug.log('Registered diagnostics-channel injection via Module.registerHooks()'); } else if (typeof mod.register === 'function' && !globalAny.Bun && !globalAny.Deno) { // `Module.register` + the `_compile` patch is Node 18.19–24.12 / 25.0 diff --git a/packages/server-runtime-injection/test/register.test.ts b/packages/server-runtime-injection/test/register.test.ts index f34408f98009..e968de935041 100644 --- a/packages/server-runtime-injection/test/register.test.ts +++ b/packages/server-runtime-injection/test/register.test.ts @@ -15,10 +15,12 @@ type LoadHook = (url: string, context: unknown, nextLoad: (url: string, context: // the real `Module.registerHooks({ resolve, load })` with the mocked (undefined-returning) callbacks, // leaving a broken resolve hook installed process-wide that crashes vitest's next dynamic `import()`. const registerHooksMock = vi.fn<(options: { load: LoadHook; resolve: unknown }) => void>(); +const modulePrototypeMock = vi.hoisted(() => ({ _compile: vi.fn() })); vi.mock('node:module', async importOriginal => { const actual = await importOriginal(); return { ...actual, + Module: { prototype: modulePrototypeMock }, registerHooks: (options: { load: LoadHook; resolve: unknown }) => registerHooksMock(options), register: vi.fn(), }; @@ -81,6 +83,7 @@ describe('registerDiagnosticsChannelInjection - bundled/tree-shaken detection', } beforeEach(async () => { + modulePrototypeMock._compile = vi.fn(); delete GLOBAL_OBJ.__SENTRY_ORCHESTRION__; setDiagnosticsHookMock.mockClear(); warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined); @@ -93,9 +96,19 @@ describe('registerDiagnosticsChannelInjection - bundled/tree-shaken detection', afterEach(() => { delete GLOBAL_OBJ.__SENTRY_ORCHESTRION__; + vi.unstubAllGlobals(); warnSpy.mockRestore(); }); + it.each(['Bun', 'Deno'])('does not replace the CommonJS compiler on %s', runtime => { + vi.stubGlobal(runtime, {}); + const originalCompile = modulePrototypeMock._compile; + + registerDiagnosticsChannelInjection(); + + expect(modulePrototypeMock._compile).toBe(originalCompile); + }); + it('includes the underlying error when registering the hooks fails', () => { // Deno always takes the `Module.registerHooks` path, independent of the Node version running the tests. (globalThis as { Deno?: unknown }).Deno = { version: { deno: '2.8.3' } }; From a255fd3c2f4675474f74ff8e00477f1e8fd16f72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20de=20Faria?= Date: Wed, 14 Oct 2026 06:18:21 -0300 Subject: [PATCH 2/3] fix(node): avoid repeated instrumentation for queried modules Run the tsx-express E2E application on the inherited Node 24 runtime. Co-Authored-By: GPT-6.1 Sol --- .../tsx-express/package.json | 1 - .../orchestrion-tsx/scenario-reload.mjs | 29 +++++++++++++++ .../tracing/orchestrion-tsx/scenario.cjs | 8 ++++- .../tracing/orchestrion-tsx/scenario.mjs | 12 +++++-- .../suites/tracing/orchestrion-tsx/test.ts | 36 ++++++++++++++++--- .../server-runtime-injection/src/register.ts | 28 +++++++++++---- 6 files changed, 100 insertions(+), 14 deletions(-) create mode 100644 dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/scenario-reload.mjs diff --git a/dev-packages/e2e-tests/test-applications/tsx-express/package.json b/dev-packages/e2e-tests/test-applications/tsx-express/package.json index 77277c1e9394..cb25a8631317 100644 --- a/dev-packages/e2e-tests/test-applications/tsx-express/package.json +++ b/dev-packages/e2e-tests/test-applications/tsx-express/package.json @@ -30,7 +30,6 @@ "@types/qs": "6.9.17" }, "volta": { - "node": "20.19.5", "extends": "../../package.json" } } diff --git a/dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/scenario-reload.mjs b/dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/scenario-reload.mjs new file mode 100644 index 000000000000..1f3f3fcdb3cc --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/scenario-reload.mjs @@ -0,0 +1,29 @@ +import * as Sentry from '@sentry/node'; +import { tracingChannel } from 'node:diagnostics_channel'; +import { createRequire } from 'node:module'; + +Sentry.init({ tracesSampleRate: 0 }); + +let loads = 0; +let injections = 0; +Sentry.getClient().on('orchestrion.module-injected', moduleName => { + if (moduleName === 'dataloader') { + injections++; + } +}); +tracingChannel('orchestrion:dataloader:load').start.subscribe(() => { + loads++; +}); + +const url = import.meta.resolve('dataloader'); +await import(`${url}?first`); +await import(`${url}?second`); + +const require = createRequire(import.meta.url); +delete require.cache[require.resolve('dataloader')]; +injections = 0; + +const DataLoader = require('dataloader'); +const value = await new DataLoader(async keys => keys).load('real-key'); +await Sentry.close(1000); +process.stdout.write(JSON.stringify([{ value, loads, injections }])); diff --git a/dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/scenario.cjs b/dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/scenario.cjs index cbc31405b4b2..7f567101fd87 100644 --- a/dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/scenario.cjs +++ b/dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/scenario.cjs @@ -4,6 +4,12 @@ const { tracingChannel } = require('node:diagnostics_channel'); Sentry.init({ tracesSampleRate: 0 }); let loads = 0; +let injections = 0; +Sentry.getClient().on('orchestrion.module-injected', moduleName => { + if (moduleName === 'dataloader') { + injections++; + } +}); tracingChannel('orchestrion:dataloader:load').start.subscribe(() => { loads++; }); @@ -13,7 +19,7 @@ async function run() { for (let attempt = 0; attempt < 2; attempt++) { const DataLoader = require('dataloader'); const value = await new DataLoader(async keys => keys).load('real-key'); - observations.push({ value, loads }); + observations.push({ value, loads, injections }); delete require.cache[require.resolve('dataloader')]; } await Sentry.close(1000); diff --git a/dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/scenario.mjs b/dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/scenario.mjs index 8f620616f58f..58772fbed931 100644 --- a/dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/scenario.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/scenario.mjs @@ -4,11 +4,19 @@ import { tracingChannel } from 'node:diagnostics_channel'; Sentry.init({ tracesSampleRate: 0 }); let loads = 0; +let injections = 0; +Sentry.getClient().on('orchestrion.module-injected', moduleName => { + if (moduleName === 'dataloader') { + injections++; + } +}); tracingChannel('orchestrion:dataloader:load').start.subscribe(() => { loads++; }); -const { default: DataLoader } = await import(`${import.meta.resolve('dataloader')}?instrumentation-test`); +const { default: DataLoader } = await import( + `${import.meta.resolve('dataloader')}${process.argv[2] ?? '?instrumentation-test'}` +); const value = await new DataLoader(async keys => keys).load('real-key'); await Sentry.close(1000); -process.stdout.write(JSON.stringify([{ value, loads }])); +process.stdout.write(JSON.stringify([{ value, loads, injections }])); diff --git a/dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/test.ts b/dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/test.ts index 3e664ca2b147..09bbe2b284b1 100644 --- a/dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/test.ts @@ -6,9 +6,33 @@ test.each([ { name: 'plain Node CommonJS', flags: [], scenario: 'scenario.cjs', operations: 2 }, { name: 'tsx CommonJS', flags: ['--require', 'tsx/cjs'], scenario: 'scenario.cjs', operations: 2 }, { name: 'plain Node ESM', flags: [], scenario: 'scenario.mjs', operations: 1 }, - { name: 'tsx ESM', flags: ['--import', 'tsx'], scenario: 'scenario.mjs', operations: 1 }, -])('instruments each operation exactly once with $name', ({ flags, scenario, operations }) => { - const result = spawnSync(process.execPath, [...flags, join(__dirname, scenario)], { + // tsx can replace the hook's source, requiring another transform before compilation. + { + name: 'tsx ESM', + flags: ['--import', 'tsx'], + scenario: 'scenario.mjs', + operations: 1, + injections: expect.any(Number), + }, + { + name: 'plain Node ESM fragment', + flags: [], + scenario: 'scenario.mjs', + operations: 1, + suffix: '#instrumentation-test', + }, + { + name: 'tsx ESM fragment', + flags: ['--import', 'tsx'], + scenario: 'scenario.mjs', + operations: 1, + suffix: '#instrumentation-test', + injections: expect.any(Number), + }, + { name: 'plain Node after queried imports', flags: [], scenario: 'scenario-reload.mjs', operations: 1 }, + { name: 'tsx after queried imports', flags: ['--import', 'tsx'], scenario: 'scenario-reload.mjs', operations: 1 }, +])('instruments each operation exactly once with $name', ({ flags, scenario, operations, suffix, injections }) => { + const result = spawnSync(process.execPath, [...flags, join(__dirname, scenario), suffix ?? '?instrumentation-test'], { encoding: 'utf8', timeout: 30_000, }); @@ -16,6 +40,10 @@ test.each([ expect(result.error).toBeUndefined(); expect(result.status, result.stderr).toBe(0); expect(JSON.parse(result.stdout)).toEqual( - Array.from({ length: operations }, (_, index) => ({ value: 'real-key', loads: index + 1 })), + Array.from({ length: operations }, (_, index) => ({ + value: 'real-key', + loads: index + 1, + injections: injections ?? index + 1, + })), ); }); diff --git a/packages/server-runtime-injection/src/register.ts b/packages/server-runtime-injection/src/register.ts index 9fc7ab715e51..b2e42eca470e 100644 --- a/packages/server-runtime-injection/src/register.ts +++ b/packages/server-runtime-injection/src/register.ts @@ -1,4 +1,5 @@ import { consoleSandbox, debug, getClient, GLOBAL_OBJ, parseSemver } from '@sentry/core'; +import { createHash } from 'node:crypto'; import { existsSync, readFileSync } from 'node:fs'; import * as Module from 'node:module'; import { dirname, join } from 'node:path'; @@ -230,14 +231,24 @@ export function registerDiagnosticsChannelInjection(): void { if (typeof mod.registerHooks === 'function' && stableSyncHooks) { initialize({ instrumentations: SENTRY_RUNTIME_INSTRUMENTATIONS }); const isNode = !globalAny.Bun && !globalAny.Deno; - const filesLoadedByHook = new Set(); + const filesLoadedByHook = new Map(); const loadHook = globalAny.Deno ? withDenoFormats(load) : load; mod.registerHooks({ resolve, - load: (url: string, context: unknown, nextLoad: Function) => { - const result = loadHook(url, context, nextLoad) as { format?: string } | undefined; - if (isNode && result?.format === 'commonjs') { - filesLoadedByHook.add(url); + load: (url: string, context: { format?: string }, nextLoad: Function) => { + const result = loadHook(url, context, nextLoad) as + | { format?: string; source?: string | Uint8Array; shortCircuit?: boolean } + | undefined; + const format = result?.format ?? context.format; + if ( + isNode && + (format == null || format === 'commonjs') && + result?.shortCircuit === true && + result?.source != null && + url.startsWith('file:') + ) { + const sourceHash = createHash('sha256').update(result.source).digest('hex'); + filesLoadedByHook.set(pathToFileURL(fileURLToPath(url)).href, sourceHash); } return result; }, @@ -250,7 +261,12 @@ export function registerDiagnosticsChannelInjection(): void { new ModulePatch({ instrumentations: SENTRY_RUNTIME_INSTRUMENTATIONS }).patch(); const patchedCompile = prototype._compile; prototype._compile = function (content, filename, ...args) { - const compile = filesLoadedByHook.delete(pathToFileURL(filename).href) ? originalCompile : patchedCompile; + const url = pathToFileURL(filename).href; + const loadedSourceHash = filesLoadedByHook.get(url); + filesLoadedByHook.delete(url); + const alreadyTransformed = + loadedSourceHash !== undefined && loadedSourceHash === createHash('sha256').update(content).digest('hex'); + const compile = alreadyTransformed ? originalCompile : patchedCompile; return compile.call(this, content, filename, ...args); }; } From 9a97d9caad72d2a41ec8383ff1644466a1298679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20de=20Faria?= Date: Fri, 16 Oct 2026 06:38:29 -0300 Subject: [PATCH 3/3] fix(node): apply runtime instrumentation review feedback Allow the CommonJS fallback on runtimes with stable synchronous hooks and exercise reloads without closing the client. Co-Authored-By: GPT-6.1 Sol --- .../orchestrion-tsx/scenario-reload.mjs | 1 - .../server-runtime-injection/src/register.ts | 32 ++++++++----------- .../test/register.test.ts | 6 ++-- 3 files changed, 17 insertions(+), 22 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/scenario-reload.mjs b/dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/scenario-reload.mjs index 1f3f3fcdb3cc..07e8afb65e04 100644 --- a/dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/scenario-reload.mjs +++ b/dev-packages/node-integration-tests/suites/tracing/orchestrion-tsx/scenario-reload.mjs @@ -25,5 +25,4 @@ injections = 0; const DataLoader = require('dataloader'); const value = await new DataLoader(async keys => keys).load('real-key'); -await Sentry.close(1000); process.stdout.write(JSON.stringify([{ value, loads, injections }])); diff --git a/packages/server-runtime-injection/src/register.ts b/packages/server-runtime-injection/src/register.ts index b2e42eca470e..4b51eea9a053 100644 --- a/packages/server-runtime-injection/src/register.ts +++ b/packages/server-runtime-injection/src/register.ts @@ -230,7 +230,6 @@ export function registerDiagnosticsChannelInjection(): void { try { if (typeof mod.registerHooks === 'function' && stableSyncHooks) { initialize({ instrumentations: SENTRY_RUNTIME_INSTRUMENTATIONS }); - const isNode = !globalAny.Bun && !globalAny.Deno; const filesLoadedByHook = new Map(); const loadHook = globalAny.Deno ? withDenoFormats(load) : load; mod.registerHooks({ @@ -241,7 +240,6 @@ export function registerDiagnosticsChannelInjection(): void { | undefined; const format = result?.format ?? context.format; if ( - isNode && (format == null || format === 'commonjs') && result?.shortCircuit === true && result?.source != null && @@ -254,22 +252,20 @@ export function registerDiagnosticsChannelInjection(): void { }, }); - if (isNode) { - // Custom require hooks bypass load. Avoid transforming files twice when load already handled them. - const prototype = mod.Module.prototype; - const originalCompile = prototype._compile; - new ModulePatch({ instrumentations: SENTRY_RUNTIME_INSTRUMENTATIONS }).patch(); - const patchedCompile = prototype._compile; - prototype._compile = function (content, filename, ...args) { - const url = pathToFileURL(filename).href; - const loadedSourceHash = filesLoadedByHook.get(url); - filesLoadedByHook.delete(url); - const alreadyTransformed = - loadedSourceHash !== undefined && loadedSourceHash === createHash('sha256').update(content).digest('hex'); - const compile = alreadyTransformed ? originalCompile : patchedCompile; - return compile.call(this, content, filename, ...args); - }; - } + // Custom require hooks bypass load. Avoid transforming files twice when load already handled them. + const prototype = mod.Module.prototype; + const originalCompile = prototype._compile; + new ModulePatch({ instrumentations: SENTRY_RUNTIME_INSTRUMENTATIONS }).patch(); + const patchedCompile = prototype._compile; + prototype._compile = function (content, filename, ...args) { + const url = pathToFileURL(filename).href; + const loadedSourceHash = filesLoadedByHook.get(url); + filesLoadedByHook.delete(url); + const alreadyTransformed = + loadedSourceHash !== undefined && loadedSourceHash === createHash('sha256').update(content).digest('hex'); + const compile = alreadyTransformed ? originalCompile : patchedCompile; + return compile.call(this, content, filename, ...args); + }; debug.log('Registered diagnostics-channel injection via Module.registerHooks()'); } else if (typeof mod.register === 'function' && !globalAny.Bun && !globalAny.Deno) { // `Module.register` + the `_compile` patch is Node 18.19–24.12 / 25.0 diff --git a/packages/server-runtime-injection/test/register.test.ts b/packages/server-runtime-injection/test/register.test.ts index e968de935041..3dfa3bdb4b15 100644 --- a/packages/server-runtime-injection/test/register.test.ts +++ b/packages/server-runtime-injection/test/register.test.ts @@ -100,13 +100,13 @@ describe('registerDiagnosticsChannelInjection - bundled/tree-shaken detection', warnSpy.mockRestore(); }); - it.each(['Bun', 'Deno'])('does not replace the CommonJS compiler on %s', runtime => { - vi.stubGlobal(runtime, {}); + it('installs the CommonJS compiler fallback on Deno', () => { + vi.stubGlobal('Deno', { version: { deno: '2.8.3' } }); const originalCompile = modulePrototypeMock._compile; registerDiagnosticsChannelInjection(); - expect(modulePrototypeMock._compile).toBe(originalCompile); + expect(modulePrototypeMock._compile).not.toBe(originalCompile); }); it('includes the underlying error when registering the hooks fails', () => {