11import type * as SentryCore from '@sentry/core' ;
2+ import { mkdirSync , mkdtempSync , rmSync , writeFileSync } from 'node:fs' ;
23import type * as NodeModule from 'node:module' ;
3- import { afterEach , beforeEach , describe , expect , it , vi } from 'vitest' ;
4+ import { tmpdir } from 'node:os' ;
5+ import { join } from 'node:path' ;
6+ import { pathToFileURL } from 'node:url' ;
7+ import { afterAll , afterEach , beforeAll , beforeEach , describe , expect , it , vi } from 'vitest' ;
8+
9+ type LoadResult = { format ?: string | null } ;
10+ type LoadHook = ( url : string , context : unknown , nextLoad : ( url : string , context : unknown ) => LoadResult ) => unknown ;
411
512// The registration installs real Node module hooks, which we neither want nor need here. Stub the
613// tracing-hooks surface so the tests can drive the diagnostics callback directly, and neuter
714// `node:module`'s hook installers: on Node 24.13+/26 the stable-sync-hooks path would otherwise call
815// the real `Module.registerHooks({ resolve, load })` with the mocked (undefined-returning) callbacks,
916// leaving a broken resolve hook installed process-wide that crashes vitest's next dynamic `import()`.
17+ const registerHooksMock = vi . fn < ( options : { load : LoadHook ; resolve : unknown } ) => void > ( ) ;
1018vi . mock ( 'node:module' , async importOriginal => {
1119 const actual = await importOriginal < typeof NodeModule > ( ) ;
12- return { ...actual , registerHooks : vi . fn ( ) , register : vi . fn ( ) } ;
20+ return {
21+ ...actual ,
22+ registerHooks : ( options : { load : LoadHook ; resolve : unknown } ) => registerHooksMock ( options ) ,
23+ register : vi . fn ( ) ,
24+ } ;
1325} ) ;
1426
1527const setDiagnosticsHookMock = vi . fn < ( cb : DiagnosticsCallback ) => void > ( ) ;
@@ -21,9 +33,10 @@ vi.mock('@apm-js-collab/tracing-hooks', () => ({
2133 patch ( ) : void { }
2234 } ,
2335} ) ) ;
36+ const loadMock = vi . fn < LoadHook > ( ) ;
2437vi . mock ( '@apm-js-collab/tracing-hooks/hook-sync.mjs' , ( ) => ( {
2538 initialize : vi . fn ( ) ,
26- load : vi . fn ( ) ,
39+ load : ( ... args : Parameters < LoadHook > ) => loadMock ( ... args ) ,
2740 resolve : vi . fn ( ) ,
2841 createDiagnosticsPort : vi . fn ( ) ,
2942} ) ) ;
@@ -192,3 +205,69 @@ describe('registerDiagnosticsChannelInjection - bundled/tree-shaken detection',
192205 expect ( setDiagnosticsHookMock ) . toHaveBeenCalledTimes ( 1 ) ;
193206 } ) ;
194207} ) ;
208+
209+ describe ( 'registerDiagnosticsChannelInjection - Deno module formats' , ( ) => {
210+ let fixtureDir : string ;
211+ let registerDiagnosticsChannelInjection : typeof RegisterModule . registerDiagnosticsChannelInjection ;
212+ let loadHook : LoadHook ;
213+
214+ beforeAll ( ( ) => {
215+ fixtureDir = mkdtempSync ( join ( tmpdir ( ) , 'sentry-deno-formats-' ) ) ;
216+ mkdirSync ( join ( fixtureDir , 'esm-package' , 'lib' ) , { recursive : true } ) ;
217+ writeFileSync ( join ( fixtureDir , 'esm-package' , 'package.json' ) , JSON . stringify ( { type : 'module' } ) ) ;
218+ writeFileSync ( join ( fixtureDir , 'esm-package' , 'lib' , 'index.js' ) , 'export default 1;' ) ;
219+ mkdirSync ( join ( fixtureDir , 'cjs-package' ) , { recursive : true } ) ;
220+ writeFileSync ( join ( fixtureDir , 'cjs-package' , 'package.json' ) , JSON . stringify ( { } ) ) ;
221+ writeFileSync ( join ( fixtureDir , 'cjs-package' , 'index.js' ) , 'module.exports = 1;' ) ;
222+ } ) ;
223+
224+ afterAll ( ( ) => {
225+ rmSync ( fixtureDir , { recursive : true , force : true } ) ;
226+ } ) ;
227+
228+ beforeEach ( async ( ) => {
229+ delete GLOBAL_OBJ . __SENTRY_ORCHESTRION__ ;
230+ ( globalThis as { Deno ?: unknown } ) . Deno = { version : { deno : '2.8.3' } } ;
231+ vi . resetModules ( ) ;
232+ registerHooksMock . mockClear ( ) ;
233+ // The transform reads the format from what `nextLoad` returns, so the stub forwards to it.
234+ loadMock . mockImplementation ( ( url , context , nextLoad ) => nextLoad ( url , context ) ) ;
235+
236+ ( { registerDiagnosticsChannelInjection } = await import ( '../src/register' ) ) ;
237+ registerDiagnosticsChannelInjection ( ) ;
238+
239+ const [ options ] = registerHooksMock . mock . lastCall ?? [ ] ;
240+ if ( ! options ) {
241+ throw new Error ( 'registerDiagnosticsChannelInjection() did not call Module.registerHooks()' ) ;
242+ }
243+ loadHook = options . load ;
244+ } ) ;
245+
246+ afterEach ( ( ) => {
247+ delete GLOBAL_OBJ . __SENTRY_ORCHESTRION__ ;
248+ delete ( globalThis as { Deno ?: unknown } ) . Deno ;
249+ loadMock . mockReset ( ) ;
250+ } ) ;
251+
252+ it . each ( [
253+ [ 'a `.js` file in a `"type": "module"` package' , 'esm-package/lib/index.js' , 'module' ] ,
254+ [ 'an `.mjs` file' , 'cjs-package/other.mjs' , 'module' ] ,
255+ [ 'a `.json` file' , 'cjs-package/package.json' , 'json' ] ,
256+ ] ) ( 'restores the format Deno leaves out for %s' , ( _label , file , format ) => {
257+ const url = pathToFileURL ( join ( fixtureDir , file ) ) . href ;
258+
259+ expect ( loadHook ( url , { } , ( ) => ( { format : null } ) ) ) . toEqual ( { format } ) ;
260+ } ) ;
261+
262+ it ( 'keeps the format missing for a `.js` file in a package without `"type": "module"`' , ( ) => {
263+ const url = pathToFileURL ( join ( fixtureDir , 'cjs-package' , 'index.js' ) ) . href ;
264+
265+ expect ( loadHook ( url , { } , ( ) => ( { format : null } ) ) ) . toEqual ( { format : null } ) ;
266+ } ) ;
267+
268+ it ( 'keeps a format that Deno reports' , ( ) => {
269+ const url = pathToFileURL ( join ( fixtureDir , 'esm-package' , 'lib' , 'index.js' ) ) . href ;
270+
271+ expect ( loadHook ( url , { } , ( ) => ( { format : 'commonjs' } ) ) ) . toEqual ( { format : 'commonjs' } ) ;
272+ } ) ;
273+ } ) ;
0 commit comments