@@ -49,6 +49,11 @@ for (const builtin of RAW_BUILTINS) {
4949// eslint-disable-next-line eslint-rules/eslint-process-env
5050const DD_IAST_ENABLED = process . env . DD_IAST_ENABLED ?. toLowerCase ( ) === 'true' || process . env . DD_IAST_ENABLED === '1'
5151
52+ const DEBUGGER_WORKER_FILENAME = 'dd-trace-debugger-worker.cjs'
53+
54+ // Path pattern to match the debugger index.js inside dd-trace package
55+ const DEBUGGER_INDEX_PATH = path . join ( 'dd-trace' , 'src' , 'debugger' , 'index.js' )
56+
5257module . exports . name = 'datadog-esbuild'
5358
5459function isESMBuild ( build ) {
@@ -377,7 +382,146 @@ register(${JSON.stringify(toRegister)}, _, set, get, ${JSON.stringify(data.raw)}
377382 resolveDir : path . dirname ( args . path ) ,
378383 }
379384 }
385+
386+ // Rewrite the debugger worker path so it points to the emitted worker bundle
387+ if ( args . path . includes ( DEBUGGER_INDEX_PATH ) ) {
388+ log . debug ( 'TRANSFORM DEBUGGER PATH: %s' , args . path )
389+ let contents = fs . readFileSync ( args . path , 'utf8' )
390+
391+ // Replace: join(__dirname, 'devtools_client', 'index.js')
392+ // With: join(__dirname, 'dd-trace-debugger-worker.cjs')
393+ // The bundled __dirname will point to the output directory where we emit the worker bundle
394+ contents = contents . replaceAll (
395+ / j o i n \( _ _ d i r n a m e , \s * [ ' " ] d e v t o o l s _ c l i e n t [ ' " ] , \s * [ ' " ] i n d e x \. j s [ ' " ] \) / g,
396+ `join(__dirname, '${ DEBUGGER_WORKER_FILENAME } ')`
397+ )
398+
399+ return {
400+ contents,
401+ loader : 'js' ,
402+ resolveDir : path . dirname ( args . path ) ,
403+ }
404+ }
380405 } )
406+
407+ // Build the Dynamic Instrumentation worker bundle as a secondary artifact
408+ build . onEnd ( async ( result ) => {
409+ if ( result . errors . length > 0 ) {
410+ log . debug ( 'Skipping DI worker build due to main build errors' )
411+ return
412+ }
413+
414+ const outputDir = getOutputDirectory ( build . initialOptions )
415+ if ( ! outputDir ) {
416+ log . warn (
417+ // eslint-disable-next-line @stylistic/max-len
418+ 'Cannot emit Live Debugger/Dynamic Instrumentation worker bundle. No outfile or outdir specified. LD/DI will not work in the bundled application.'
419+ )
420+ return
421+ }
422+
423+ await buildDebuggerWorker ( build . initialOptions , outputDir , build . esbuild )
424+ } )
425+ }
426+
427+ /**
428+ * Determine the output directory from esbuild options
429+ *
430+ * @param {object } initialOptions - esbuild initial options
431+ * @returns {string | null } - Output directory path or null
432+ */
433+ function getOutputDirectory ( initialOptions ) {
434+ if ( initialOptions . outdir ) {
435+ return initialOptions . outdir
436+ }
437+ if ( initialOptions . outfile ) {
438+ return path . dirname ( initialOptions . outfile )
439+ }
440+ return null
441+ }
442+
443+ /**
444+ * Build the Dynamic Instrumentation worker bundle
445+ *
446+ * @param {object } parentOptions - Parent build options
447+ * @param {string } outputDir - Output directory for the worker bundle
448+ * @param {object } esbuild - esbuild module instance from the parent build
449+ */
450+ async function buildDebuggerWorker ( parentOptions , outputDir , esbuild ) {
451+ if ( ! esbuild ) {
452+ log . warn ( 'esbuild instance not available. LD/DI worker bundle cannot be built.' )
453+ return
454+ }
455+
456+ // Resolve the devtools_client entry point from the installed dd-trace package
457+ let workerEntryPoint
458+ try {
459+ // First try to resolve dd-trace to find its installation path
460+ // eslint-disable-next-line n/no-missing-require -- dd-trace is a peer dependency
461+ const ddTracePath = require . resolve ( 'dd-trace/package.json' )
462+ workerEntryPoint = path . join ( path . dirname ( ddTracePath ) , 'src' , 'debugger' , 'devtools_client' , 'index.js' )
463+ } catch {
464+ // Fallback: resolve relative to this plugin (for development/linked packages)
465+ workerEntryPoint = path . join ( __dirname , '..' , 'dd-trace' , 'src' , 'debugger' , 'devtools_client' , 'index.js' )
466+ }
467+
468+ if ( ! fs . existsSync ( workerEntryPoint ) ) {
469+ log . warn (
470+ 'Could not find DI worker entry point at %s. LD/DI will not work in the bundled application.' ,
471+ workerEntryPoint
472+ )
473+ return
474+ }
475+
476+ const workerOutfile = path . join ( outputDir , DEBUGGER_WORKER_FILENAME )
477+
478+ log . debug ( 'Building DI worker bundle: %s -> %s' , workerEntryPoint , workerOutfile )
479+
480+ // Plugin to patch the trace/span lookup to use global._ddtrace
481+ const patchDDTracePlugin = {
482+ name : 'patch-ddtrace-lookup' ,
483+ setup ( workerBuild ) {
484+ workerBuild . onLoad ( { filter : / d e v t o o l s _ c l i e n t [ / \\ ] i n d e x \. j s $ / } , ( args ) => {
485+ let contents = fs . readFileSync ( args . path , 'utf8' )
486+
487+ // Replace the dd-trace require expression with a bundler-safe version
488+ // Original: global.require('dd-trace').scope().active()?.context()
489+ // New: (global._ddtrace ?? global.require?.('dd-trace'))?.scope?.()?.active?.()?.context?.()
490+ contents = contents . replaceAll (
491+ / g l o b a l \. r e q u i r e \( [ ' " ] d d - t r a c e [ ' " ] \) \. s c o p e \( \) \. a c t i v e \( \) \? \. c o n t e x t \( \) / g,
492+ "(global._ddtrace ?? global.require?.('dd-trace'))?.scope?.()?.active?.()?.context?.()"
493+ )
494+
495+ return {
496+ contents,
497+ loader : 'js' ,
498+ }
499+ } )
500+ } ,
501+ }
502+
503+ try {
504+ await esbuild . build ( {
505+ entryPoints : [ workerEntryPoint ] ,
506+ bundle : true ,
507+ platform : 'node' ,
508+ format : 'cjs' ,
509+ outfile : workerOutfile ,
510+ target : parentOptions . target || [ 'node18' ] ,
511+ // Keep Node.js builtins external
512+ external : [ ...RAW_BUILTINS , ...RAW_BUILTINS . map ( m => `node:${ m } ` ) ] ,
513+ // Ensure function/class names are preserved for debugging
514+ keepNames : true ,
515+ plugins : [ patchDDTracePlugin ] ,
516+ // Don't minify the worker - keep it debuggable
517+ minify : false ,
518+ logLevel : 'warning' ,
519+ } )
520+
521+ log . debug ( 'DI worker bundle emitted: %s' , workerOutfile )
522+ } catch ( err ) {
523+ log . warn ( 'Failed to build DI worker bundle: %s' , err . message )
524+ }
381525}
382526
383527// @see https://github.com/nodejs/node/issues/47000
0 commit comments