dd-trace 5.96.0 → 5.98.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/index.d.ts +60 -2
  2. package/package.json +9 -7
  3. package/packages/datadog-esbuild/index.js +20 -9
  4. package/packages/datadog-instrumentations/src/child_process.js +7 -17
  5. package/packages/datadog-instrumentations/src/crypto.js +1 -2
  6. package/packages/datadog-instrumentations/src/cucumber.js +69 -4
  7. package/packages/datadog-instrumentations/src/cypress-config.js +318 -0
  8. package/packages/datadog-instrumentations/src/cypress.js +86 -4
  9. package/packages/datadog-instrumentations/src/dns.js +1 -2
  10. package/packages/datadog-instrumentations/src/express.js +4 -4
  11. package/packages/datadog-instrumentations/src/fs.js +27 -29
  12. package/packages/datadog-instrumentations/src/graphql.js +1 -1
  13. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +41 -13
  14. package/packages/datadog-instrumentations/src/helpers/hook.js +31 -6
  15. package/packages/datadog-instrumentations/src/helpers/hooks.js +12 -19
  16. package/packages/datadog-instrumentations/src/helpers/instrument.js +27 -13
  17. package/packages/datadog-instrumentations/src/helpers/register.js +103 -142
  18. package/packages/datadog-instrumentations/src/http/client.js +2 -3
  19. package/packages/datadog-instrumentations/src/http/server.js +2 -5
  20. package/packages/datadog-instrumentations/src/http2/client.js +1 -3
  21. package/packages/datadog-instrumentations/src/http2/server.js +1 -3
  22. package/packages/datadog-instrumentations/src/jest.js +117 -16
  23. package/packages/datadog-instrumentations/src/limitd-client.js +1 -1
  24. package/packages/datadog-instrumentations/src/mocha/utils.js +12 -1
  25. package/packages/datadog-instrumentations/src/net.js +2 -8
  26. package/packages/datadog-instrumentations/src/pino.js +1 -1
  27. package/packages/datadog-instrumentations/src/playwright.js +4 -1
  28. package/packages/datadog-instrumentations/src/prisma.js +1 -2
  29. package/packages/datadog-instrumentations/src/redis.js +12 -6
  30. package/packages/datadog-instrumentations/src/selenium.js +4 -1
  31. package/packages/datadog-instrumentations/src/sequelize.js +1 -1
  32. package/packages/datadog-instrumentations/src/url.js +1 -3
  33. package/packages/datadog-instrumentations/src/vitest.js +5 -1
  34. package/packages/datadog-instrumentations/src/vm.js +1 -3
  35. package/packages/datadog-plugin-aws-sdk/src/base.js +5 -4
  36. package/packages/datadog-plugin-aws-sdk/src/services/dynamodb.js +1 -0
  37. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +1 -0
  38. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -0
  39. package/packages/datadog-plugin-aws-sdk/src/services/redshift.js +1 -0
  40. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +1 -0
  41. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -0
  42. package/packages/datadog-plugin-cucumber/src/index.js +13 -3
  43. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +166 -6
  44. package/packages/datadog-plugin-cypress/src/index.js +59 -2
  45. package/packages/datadog-plugin-fs/src/index.js +1 -1
  46. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -1
  47. package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +2 -7
  48. package/packages/datadog-plugin-graphql/src/resolve.js +1 -1
  49. package/packages/datadog-plugin-http/src/client.js +1 -1
  50. package/packages/datadog-plugin-http/src/server.js +10 -2
  51. package/packages/datadog-plugin-http2/src/client.js +1 -1
  52. package/packages/datadog-plugin-http2/src/server.js +10 -2
  53. package/packages/datadog-plugin-jest/src/index.js +4 -2
  54. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +31 -4
  55. package/packages/datadog-plugin-mocha/src/index.js +5 -2
  56. package/packages/datadog-plugin-mongodb-core/src/index.js +3 -3
  57. package/packages/datadog-plugin-mysql/src/index.js +1 -1
  58. package/packages/datadog-plugin-next/src/index.js +10 -16
  59. package/packages/datadog-plugin-openai/src/services.js +1 -0
  60. package/packages/datadog-plugin-pg/src/index.js +1 -1
  61. package/packages/datadog-plugin-tedious/src/index.js +1 -1
  62. package/packages/datadog-plugin-ws/src/close.js +1 -1
  63. package/packages/datadog-plugin-ws/src/receiver.js +1 -1
  64. package/packages/datadog-webpack/index.js +3 -3
  65. package/packages/dd-trace/index.js +12 -10
  66. package/packages/dd-trace/src/agent/url.js +2 -2
  67. package/packages/dd-trace/src/aiguard/sdk.js +26 -22
  68. package/packages/dd-trace/src/appsec/blocked_templates.js +4 -3
  69. package/packages/dd-trace/src/appsec/blocking.js +64 -33
  70. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +1 -1
  71. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +1 -1
  72. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
  73. package/packages/dd-trace/src/appsec/remote_config.js +1 -0
  74. package/packages/dd-trace/src/appsec/sdk/index.js +4 -0
  75. package/packages/dd-trace/src/appsec/sdk/set_user.js +1 -1
  76. package/packages/dd-trace/src/appsec/sdk/track_event.js +5 -5
  77. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +2 -2
  78. package/packages/dd-trace/src/appsec/sdk/utils.js +4 -2
  79. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +6 -1
  80. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +4 -0
  81. package/packages/dd-trace/src/config/defaults.js +315 -146
  82. package/packages/dd-trace/src/config/generated-config-types.d.ts +9 -1
  83. package/packages/dd-trace/src/config/helper.js +59 -10
  84. package/packages/dd-trace/src/config/index.js +587 -1496
  85. package/packages/dd-trace/src/config/parsers.js +256 -0
  86. package/packages/dd-trace/src/config/remote_config.js +59 -2
  87. package/packages/dd-trace/src/config/supported-configurations.json +406 -432
  88. package/packages/dd-trace/src/constants.js +1 -0
  89. package/packages/dd-trace/src/crashtracking/crashtracker.js +7 -1
  90. package/packages/dd-trace/src/crashtracking/index.js +1 -7
  91. package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +5 -2
  92. package/packages/dd-trace/src/debugger/index.js +1 -1
  93. package/packages/dd-trace/src/dogstatsd.js +12 -9
  94. package/packages/dd-trace/src/encode/0.4.js +8 -7
  95. package/packages/dd-trace/src/encode/span-stats.js +4 -1
  96. package/packages/dd-trace/src/exporters/agent/writer.js +7 -1
  97. package/packages/dd-trace/src/exporters/common/request.js +9 -0
  98. package/packages/dd-trace/src/exporters/common/writer.js +12 -2
  99. package/packages/dd-trace/src/heap_snapshots.js +3 -0
  100. package/packages/dd-trace/src/index.js +5 -2
  101. package/packages/dd-trace/src/lambda/runtime/ritm.js +6 -6
  102. package/packages/dd-trace/src/llmobs/index.js +4 -1
  103. package/packages/dd-trace/src/llmobs/plugins/ai/index.js +5 -1
  104. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +60 -12
  105. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +4 -2
  106. package/packages/dd-trace/src/llmobs/sdk.js +12 -8
  107. package/packages/dd-trace/src/llmobs/span_processor.js +1 -1
  108. package/packages/dd-trace/src/llmobs/tagger.js +9 -6
  109. package/packages/dd-trace/src/llmobs/writers/base.js +2 -0
  110. package/packages/dd-trace/src/llmobs/writers/util.js +3 -0
  111. package/packages/dd-trace/src/log/index.js +20 -59
  112. package/packages/dd-trace/src/log/writer.js +7 -19
  113. package/packages/dd-trace/src/noop/proxy.js +8 -0
  114. package/packages/dd-trace/src/openfeature/remote_config.js +6 -1
  115. package/packages/dd-trace/src/opentelemetry/context_manager.js +6 -4
  116. package/packages/dd-trace/src/opentelemetry/logs/index.js +1 -1
  117. package/packages/dd-trace/src/opentelemetry/metrics/index.js +1 -1
  118. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +17 -2
  119. package/packages/dd-trace/src/opentelemetry/otlp/protobuf_loader.js +14 -2
  120. package/packages/dd-trace/src/opentelemetry/otlp/trace.proto +358 -0
  121. package/packages/dd-trace/src/opentelemetry/otlp/trace_service.proto +78 -0
  122. package/packages/dd-trace/src/opentelemetry/trace/index.js +75 -0
  123. package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +66 -0
  124. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +332 -0
  125. package/packages/dd-trace/src/opentracing/propagation/text_map.js +9 -4
  126. package/packages/dd-trace/src/opentracing/tracer.js +9 -4
  127. package/packages/dd-trace/src/payload-tagging/config/index.js +6 -5
  128. package/packages/dd-trace/src/plugin_manager.js +8 -6
  129. package/packages/dd-trace/src/plugins/ci_plugin.js +4 -0
  130. package/packages/dd-trace/src/plugins/log_plugin.js +3 -0
  131. package/packages/dd-trace/src/plugins/plugin.js +11 -13
  132. package/packages/dd-trace/src/plugins/storage.js +2 -2
  133. package/packages/dd-trace/src/plugins/tracing.js +22 -5
  134. package/packages/dd-trace/src/plugins/util/test.js +2 -0
  135. package/packages/dd-trace/src/plugins/util/web.js +6 -88
  136. package/packages/dd-trace/src/process-tags/index.js +3 -0
  137. package/packages/dd-trace/src/profiler.js +27 -2
  138. package/packages/dd-trace/src/profiling/config.js +73 -241
  139. package/packages/dd-trace/src/profiling/exporter_cli.js +1 -4
  140. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +6 -2
  141. package/packages/dd-trace/src/profiling/profiler.js +78 -109
  142. package/packages/dd-trace/src/profiling/profilers/events.js +2 -3
  143. package/packages/dd-trace/src/profiling/profilers/wall.js +89 -6
  144. package/packages/dd-trace/src/profiling/ssi-heuristics.js +4 -1
  145. package/packages/dd-trace/src/propagation-hash/index.js +2 -1
  146. package/packages/dd-trace/src/proxy.js +40 -6
  147. package/packages/dd-trace/src/remote_config/index.js +3 -0
  148. package/packages/dd-trace/src/require-package-json.js +8 -4
  149. package/packages/dd-trace/src/ritm.js +58 -26
  150. package/packages/dd-trace/src/runtime_metrics/index.js +3 -0
  151. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +18 -11
  152. package/packages/dd-trace/src/sampler.js +1 -1
  153. package/packages/dd-trace/src/service-naming/index.js +1 -1
  154. package/packages/dd-trace/src/service-naming/schemas/definition.js +4 -1
  155. package/packages/dd-trace/src/service-naming/schemas/util.js +15 -1
  156. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +24 -1
  157. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +60 -0
  158. package/packages/dd-trace/src/service-naming/schemas/v0/web.js +17 -1
  159. package/packages/dd-trace/src/service-naming/schemas/v0/websocket.js +5 -0
  160. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +17 -0
  161. package/packages/dd-trace/src/service-naming/schemas/v1/web.js +11 -1
  162. package/packages/dd-trace/src/service-naming/schemas/v1/websocket.js +6 -0
  163. package/packages/dd-trace/src/span_stats.js +5 -1
  164. package/packages/dd-trace/src/standalone/index.js +3 -0
  165. package/packages/dd-trace/src/telemetry/index.js +2 -3
  166. package/packages/dd-trace/src/telemetry/send-data.js +5 -19
  167. package/packages/dd-trace/src/telemetry/session-propagation.js +19 -44
  168. package/packages/dd-trace/src/telemetry/telemetry.js +28 -171
  169. package/packages/dd-trace/src/tracer.js +2 -2
  170. package/packages/dd-trace/src/util.js +0 -9
  171. package/vendor/dist/@apm-js-collab/code-transformer/index.js +28 -6
  172. package/vendor/dist/protobufjs/index.js +1 -1
  173. package/packages/dd-trace/src/log/utils.js +0 -16
@@ -0,0 +1,318 @@
1
+ 'use strict'
2
+
3
+ const fs = require('fs')
4
+ const os = require('os')
5
+ const path = require('path')
6
+ const { pathToFileURL } = require('url')
7
+ const { channel } = require('./helpers/instrument')
8
+
9
+ const DD_CONFIG_WRAPPED = Symbol('dd-trace.cypress.config.wrapped')
10
+
11
+ const setupNodeEventsCh = channel('ci:cypress:setup-node-events')
12
+
13
+ // Ensure the cypress plugin is loaded so it can subscribe to our channel.
14
+ // Normally, plugins are loaded when their npm module is required (via addHook),
15
+ // but plain-object configs don't require('cypress'), so the plugin would never
16
+ // be instantiated in the Cypress Config Manager child process.
17
+ const loadCh = channel('dd-trace:instrumentation:load')
18
+ if (loadCh.hasSubscribers) {
19
+ loadCh.publish({ name: 'cypress' })
20
+ }
21
+
22
+ const noopTask = {
23
+ 'dd:testSuiteStart': () => null,
24
+ 'dd:beforeEach': () => ({}),
25
+ 'dd:afterEach': () => null,
26
+ 'dd:addTags': () => null,
27
+ 'dd:log': () => null,
28
+ }
29
+
30
+ /**
31
+ * @param {unknown} value
32
+ * @returns {boolean}
33
+ */
34
+ function isPlainObject (value) {
35
+ if (!value || typeof value !== 'object') return false
36
+ const prototype = Object.getPrototypeOf(value)
37
+ return prototype === Object.prototype || prototype === null
38
+ }
39
+
40
+ /**
41
+ * Cypress allows setupNodeEvents to return partial config fragments that it
42
+ * diffs and merges into the resolved config. Preserve that behavior here so
43
+ * the wrapper does not drop user-provided config updates.
44
+ *
45
+ * @param {object} config Cypress resolved config object
46
+ * @param {unknown} updatedConfig value returned from setupNodeEvents
47
+ * @returns {object} resolved config with returned overrides applied
48
+ */
49
+ function mergeReturnedConfig (config, updatedConfig) {
50
+ if (!isPlainObject(updatedConfig) || updatedConfig === config) {
51
+ return config
52
+ }
53
+
54
+ const mergedConfig = { ...config }
55
+
56
+ for (const [key, value] of Object.entries(updatedConfig)) {
57
+ mergedConfig[key] = isPlainObject(value) && isPlainObject(mergedConfig[key])
58
+ ? mergeReturnedConfig(mergedConfig[key], value)
59
+ : value
60
+ }
61
+
62
+ return mergedConfig
63
+ }
64
+
65
+ /**
66
+ * Creates a temporary wrapper support file under os.tmpdir() that loads
67
+ * dd-trace's browser-side hooks before the user's original support file.
68
+ * Returns the wrapper path (for cleanup) or undefined if injection was skipped.
69
+ *
70
+ * @param {object} config Cypress resolved config object
71
+ * @returns {string|undefined} wrapper file path, or undefined if skipped
72
+ */
73
+ function injectSupportFile (config) {
74
+ const originalSupportFile = config.supportFile
75
+ if (!originalSupportFile || originalSupportFile === false) return
76
+
77
+ try {
78
+ const content = fs.readFileSync(originalSupportFile, 'utf8')
79
+ // Naive check: skip lines starting with // or * to avoid matching commented-out imports.
80
+ const hasActiveDdTraceImport = content.split('\n').some(line => {
81
+ const trimmed = line.trim()
82
+ return trimmed.includes('dd-trace/ci/cypress/support') &&
83
+ !trimmed.startsWith('//') && !trimmed.startsWith('*')
84
+ })
85
+ if (hasActiveDdTraceImport) return
86
+ } catch {
87
+ return
88
+ }
89
+
90
+ const ddSupportFile = require.resolve('../../../ci/cypress/support')
91
+ const wrapperFile = path.join(os.tmpdir(), `dd-cypress-support-${process.pid}.mjs`)
92
+
93
+ // Always use ESM: it can import both CJS and ESM support files.
94
+ const wrapperContent =
95
+ `import ${JSON.stringify(ddSupportFile)}\nimport ${JSON.stringify(originalSupportFile)}\n`
96
+
97
+ try {
98
+ fs.writeFileSync(wrapperFile, wrapperContent)
99
+ config.supportFile = wrapperFile
100
+ return wrapperFile
101
+ } catch {
102
+ // Can't write wrapper - skip injection
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Registers dd-trace's Cypress hooks (before:run, after:spec, after:run, tasks)
108
+ * and injects the support file. Communicates with the plugin layer via
109
+ * the `ci:cypress:setup-node-events` diagnostic channel, avoiding direct
110
+ * tracer references in the instrumentation layer.
111
+ *
112
+ * @param {Function} on Cypress event registration function
113
+ * @param {object} config Cypress resolved config object
114
+ * @param {Function[]} userAfterSpecHandlers user's after:spec handlers collected from wrappedOn
115
+ * @param {Function[]} userAfterRunHandlers user's after:run handlers collected from wrappedOn
116
+ * @returns {object} the config object (possibly modified)
117
+ */
118
+ function registerDdTraceHooks (on, config, userAfterSpecHandlers, userAfterRunHandlers) {
119
+ const wrapperFile = injectSupportFile(config)
120
+
121
+ const cleanupWrapper = () => {
122
+ if (wrapperFile) {
123
+ try { fs.unlinkSync(wrapperFile) } catch { /* best effort */ }
124
+ }
125
+ }
126
+
127
+ const registerAfterRunWithCleanup = () => {
128
+ on('after:run', (results) => {
129
+ const chain = userAfterRunHandlers.reduce(
130
+ (p, h) => p.then(() => h(results)),
131
+ Promise.resolve()
132
+ )
133
+ return chain.finally(cleanupWrapper)
134
+ })
135
+ }
136
+
137
+ const registerNoopHandlers = () => {
138
+ for (const h of userAfterSpecHandlers) on('after:spec', h)
139
+ registerAfterRunWithCleanup()
140
+ on('task', noopTask)
141
+ }
142
+
143
+ if (!setupNodeEventsCh.hasSubscribers) {
144
+ registerNoopHandlers()
145
+ return config
146
+ }
147
+
148
+ // Publish to the plugin layer via diagnostic channel.
149
+ // The subscriber sets `payload.registered = true` and optionally
150
+ // `payload.configPromise` when it handles the event.
151
+ const payload = {
152
+ on,
153
+ config,
154
+ userAfterSpecHandlers,
155
+ userAfterRunHandlers,
156
+ cleanupWrapper,
157
+ registered: false,
158
+ configPromise: undefined,
159
+ }
160
+
161
+ setupNodeEventsCh.publish(payload)
162
+
163
+ if (!payload.registered) {
164
+ registerNoopHandlers()
165
+ return config
166
+ }
167
+
168
+ return payload.configPromise || config
169
+ }
170
+
171
+ /**
172
+ * @param {Function|undefined} originalSetupNodeEvents
173
+ * @returns {Function}
174
+ */
175
+ function wrapSetupNodeEvents (originalSetupNodeEvents) {
176
+ return function ddSetupNodeEvents (on, config) {
177
+ const userAfterSpecHandlers = []
178
+ const userAfterRunHandlers = []
179
+
180
+ const wrappedOn = (event, handler) => {
181
+ if (event === 'after:spec') {
182
+ userAfterSpecHandlers.push(handler)
183
+ } else if (event === 'after:run') {
184
+ userAfterRunHandlers.push(handler)
185
+ } else {
186
+ on(event, handler)
187
+ }
188
+ }
189
+
190
+ const maybePromise = originalSetupNodeEvents
191
+ ? originalSetupNodeEvents.call(this, wrappedOn, config)
192
+ : undefined
193
+
194
+ if (maybePromise && typeof maybePromise.then === 'function') {
195
+ return maybePromise.then((result) => {
196
+ return registerDdTraceHooks(
197
+ on,
198
+ mergeReturnedConfig(config, result),
199
+ userAfterSpecHandlers,
200
+ userAfterRunHandlers
201
+ )
202
+ })
203
+ }
204
+
205
+ return registerDdTraceHooks(
206
+ on,
207
+ mergeReturnedConfig(config, maybePromise),
208
+ userAfterSpecHandlers,
209
+ userAfterRunHandlers
210
+ )
211
+ }
212
+ }
213
+
214
+ /**
215
+ * @param {object} config
216
+ * @returns {object}
217
+ */
218
+ function wrapConfig (config) {
219
+ if (!config || config[DD_CONFIG_WRAPPED]) return config
220
+ config[DD_CONFIG_WRAPPED] = true
221
+
222
+ if (config.e2e) {
223
+ config.e2e.setupNodeEvents = wrapSetupNodeEvents(config.e2e.setupNodeEvents)
224
+ }
225
+ if (config.component) {
226
+ config.component.setupNodeEvents = wrapSetupNodeEvents(config.component.setupNodeEvents)
227
+ }
228
+
229
+ return config
230
+ }
231
+
232
+ /**
233
+ * @param {string} originalConfigFile absolute path to the original config file
234
+ * @returns {string} path to the generated wrapper file
235
+ */
236
+ function createConfigWrapper (originalConfigFile) {
237
+ const wrapperFile = path.join(
238
+ path.dirname(originalConfigFile),
239
+ `.dd-cypress-config-${process.pid}.mjs`
240
+ )
241
+
242
+ const cypressConfigPath = require.resolve('./cypress-config')
243
+
244
+ // Always use ESM: it can import both CJS and ESM configs, so it works
245
+ // regardless of the original file's extension or "type": "module" in package.json.
246
+ // Import cypress-config.js directly (CJS default = module.exports object).
247
+ fs.writeFileSync(wrapperFile, [
248
+ `import originalConfig from ${JSON.stringify(pathToFileURL(originalConfigFile).href)}`,
249
+ `import cypressConfig from ${JSON.stringify(pathToFileURL(cypressConfigPath).href)}`,
250
+ '',
251
+ 'export default cypressConfig.wrapConfig(originalConfig)',
252
+ '',
253
+ ].join('\n'))
254
+
255
+ return wrapperFile
256
+ }
257
+
258
+ /**
259
+ * Wraps the Cypress config file for a CLI start() call. When an explicit
260
+ * configFile is provided, creates a temp wrapper that imports the original
261
+ * and passes it through wrapConfig. This handles ESM configs (.mjs) and
262
+ * plain-object configs (without defineConfig) that can't be intercepted
263
+ * via the defineConfig shimmer.
264
+ *
265
+ * @param {object|undefined} options
266
+ * @returns {{ options: object|undefined, cleanup: Function }}
267
+ */
268
+ function wrapCliConfigFileOptions (options) {
269
+ const noop = { options, cleanup: () => {} }
270
+
271
+ if (!options) return noop
272
+
273
+ const projectRoot = typeof options.project === 'string' ? options.project : process.cwd()
274
+ let configFilePath
275
+
276
+ if (options.configFile === false) {
277
+ // configFile: false means "no config file" — respect Cypress's semantics
278
+ return noop
279
+ } else if (typeof options.configFile === 'string') {
280
+ configFilePath = path.isAbsolute(options.configFile)
281
+ ? options.configFile
282
+ : path.resolve(projectRoot, options.configFile)
283
+ } else {
284
+ // No explicit --config-file: resolve the default cypress.config.{js,ts,cjs,mjs}
285
+ for (const ext of ['.js', '.ts', '.cjs', '.mjs']) {
286
+ const candidate = path.join(projectRoot, `cypress.config${ext}`)
287
+ if (fs.existsSync(candidate)) {
288
+ configFilePath = candidate
289
+ break
290
+ }
291
+ }
292
+ }
293
+
294
+ // Skip .ts files — Cypress transpiles them internally via its own loader.
295
+ // The ESM wrapper can't import .ts directly. The defineConfig shimmer
296
+ // handles .ts configs since they're transpiled to CJS by Cypress.
297
+ if (!configFilePath || !fs.existsSync(configFilePath) || path.extname(configFilePath) === '.ts') return noop
298
+
299
+ try {
300
+ const wrapperFile = createConfigWrapper(configFilePath)
301
+
302
+ return {
303
+ options: { ...options, configFile: wrapperFile },
304
+ cleanup: () => {
305
+ try { fs.unlinkSync(wrapperFile) } catch { /* best effort */ }
306
+ },
307
+ }
308
+ } catch {
309
+ // Config directory may be read-only — fall back to no wrapping.
310
+ // The defineConfig shimmer will still handle configs that use defineConfig.
311
+ return noop
312
+ }
313
+ }
314
+
315
+ module.exports = {
316
+ wrapCliConfigFileOptions,
317
+ wrapConfig,
318
+ }
@@ -1,11 +1,93 @@
1
1
  'use strict'
2
2
 
3
+ const shimmer = require('../../datadog-shimmer')
3
4
  const { DD_MAJOR } = require('../../../version')
4
5
  const { addHook } = require('./helpers/instrument')
6
+ const {
7
+ wrapCliConfigFileOptions,
8
+ wrapConfig,
9
+ } = require('./cypress-config')
5
10
 
6
- // No handler because this is only useful for testing.
7
- // Cypress plugin does not patch any library.
11
+ // Wrap defineConfig() so configs are instrumented when loaded in Cypress's
12
+ // config child process. This covers both CLI and programmatic usage with CJS configs.
8
13
  addHook({
9
14
  name: 'cypress',
10
- versions: DD_MAJOR >= 6 ? ['>=10.2.0'] : ['>=6.7.0'],
11
- }, lib => lib)
15
+ versions: ['>=10.2.0'],
16
+ }, (cypress) => {
17
+ if (typeof cypress.defineConfig === 'function') {
18
+ shimmer.wrap(cypress, 'defineConfig', (defineConfig) => function (config) {
19
+ wrapConfig(config)
20
+ return defineConfig(config)
21
+ })
22
+ }
23
+ return cypress
24
+ })
25
+
26
+ // Wrap the CLI entry points (cypress run / cypress open) to handle config files
27
+ // that can't be intercepted via the defineConfig shimmer: ESM configs (.mjs)
28
+ // and plain-object configs (without defineConfig).
29
+ function getCliStartWrapper (start) {
30
+ return function ddTraceCliStart (options) {
31
+ const { options: wrappedOptions, cleanup } = wrapCliConfigFileOptions(options)
32
+ const result = start.call(this, wrappedOptions)
33
+
34
+ if (result && typeof result.then === 'function') {
35
+ return result.finally(cleanup)
36
+ }
37
+
38
+ cleanup()
39
+ return result
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Wraps `start` on an object (or its `.default`) if present.
45
+ *
46
+ * @param {object} mod module exports
47
+ * @returns {object} mod
48
+ */
49
+ function wrapStartOnModule (mod) {
50
+ const target = mod.default || mod
51
+ if (typeof target.start === 'function') {
52
+ shimmer.wrap(target, 'start', getCliStartWrapper)
53
+ }
54
+ return mod
55
+ }
56
+
57
+ // Hook the CLI entry points where Cypress resolves and executes `run`/`open`.
58
+ // Cypress 10-14: lib/exec/{run,open}.js as separate files.
59
+ // Cypress 15-15.10: dist/exec/{run,open}.js as separate files.
60
+ // Cypress >=15.11: bundled into dist/cli-<hash>.js exporting runModule/openModule.
61
+ for (const file of ['lib/exec/run.js', 'lib/exec/open.js', 'dist/exec/run.js', 'dist/exec/open.js']) {
62
+ addHook({
63
+ name: 'cypress',
64
+ versions: ['>=10.2.0'],
65
+ file,
66
+ }, wrapStartOnModule)
67
+ }
68
+
69
+ // Cypress >=15.11 bundles run/open into a single CLI chunk (dist/cli-<hash>.js).
70
+ // The chunk exports runModule and openModule, each with a start() method.
71
+ addHook({
72
+ name: 'cypress',
73
+ versions: ['>=10.2.0'],
74
+ filePattern: 'dist/cli.*',
75
+ }, (cliChunk) => {
76
+ if (cliChunk.runModule?.start) {
77
+ shimmer.wrap(cliChunk.runModule, 'start', getCliStartWrapper)
78
+ }
79
+ if (cliChunk.openModule?.start) {
80
+ shimmer.wrap(cliChunk.openModule, 'start', getCliStartWrapper)
81
+ }
82
+ return cliChunk
83
+ })
84
+
85
+ // Cypress <10 uses the old pluginsFile approach. No auto-instrumentation;
86
+ // users must use the manual dd-trace/ci/cypress/plugin setup.
87
+ // This hook is kept so the plugin system registers Cypress for version tracking.
88
+ if (DD_MAJOR < 6) {
89
+ addHook({
90
+ name: 'cypress',
91
+ versions: ['>=6.7.0 <10.2.0'],
92
+ }, lib => lib)
93
+ }
@@ -18,9 +18,8 @@ const rrtypes = {
18
18
  }
19
19
 
20
20
  const rrtypeMap = new WeakMap()
21
- const names = ['dns', 'node:dns']
22
21
 
23
- addHook({ name: names }, dns => {
22
+ addHook({ name: 'dns' }, dns => {
24
23
  shimmer.wrap(dns, 'lookup', fn => wrap('apm:dns:lookup', fn, 2))
25
24
  shimmer.wrap(dns, 'lookupService', fn => wrap('apm:dns:lookup_service', fn, 2))
26
25
  shimmer.wrap(dns, 'resolve', fn => wrap('apm:dns:resolve', fn, 2))
@@ -146,7 +146,7 @@ function wrapAppUse (use) {
146
146
  }
147
147
  }
148
148
 
149
- addHook({ name: 'express', versions: ['>=4'], file: ['lib/express.js'] }, express => {
149
+ addHook({ name: 'express', versions: ['>=4'], file: 'lib/express.js' }, express => {
150
150
  shimmer.wrap(express.application, 'handle', wrapHandle)
151
151
  shimmer.wrap(express.application, 'all', wrapAppAll)
152
152
  shimmer.wrap(express.application, 'route', wrapAppRoute)
@@ -224,19 +224,19 @@ function wrapProcessParamsMethod (requestPositionInArguments) {
224
224
  }
225
225
  }
226
226
 
227
- addHook({ name: 'express', versions: ['>=4.0.0 <4.3.0'], file: ['lib/express.js'] }, express => {
227
+ addHook({ name: 'express', versions: ['>=4.0.0 <4.3.0'], file: 'lib/express.js' }, express => {
228
228
  shimmer.wrap(express.Router, 'process_params', wrapProcessParamsMethod(1))
229
229
  return express
230
230
  })
231
231
 
232
- addHook({ name: 'express', versions: ['>=4.3.0 <5.0.0'], file: ['lib/express.js'] }, express => {
232
+ addHook({ name: 'express', versions: ['>=4.3.0 <5.0.0'], file: 'lib/express.js' }, express => {
233
233
  shimmer.wrap(express.Router, 'process_params', wrapProcessParamsMethod(2))
234
234
  return express
235
235
  })
236
236
 
237
237
  const queryReadCh = channel('datadog:express:query:finish')
238
238
 
239
- addHook({ name: 'express', file: ['lib/request.js'], versions: ['>=5.0.0'] }, request => {
239
+ addHook({ name: 'express', file: 'lib/request.js', versions: ['>=5.0.0'] }, request => {
240
240
  shimmer.wrap(request, 'query', function (originalGet) {
241
241
  return function wrappedGet () {
242
242
  const query = originalGet.call(this)
@@ -84,37 +84,35 @@ const paramsByFileHandleMethods = {
84
84
  writeFile: ['data', 'options'],
85
85
  writev: ['buffers', 'position'],
86
86
  }
87
- const names = ['fs', 'node:fs']
88
- for (const name of names) {
89
- addHook({ name }, fs => {
90
- const asyncMethods = Object.keys(paramsByMethod)
91
- const syncMethods = asyncMethods.map(name => `${name}Sync`)
92
-
93
- massWrap(fs, asyncMethods, createWrapFunction())
94
- massWrap(fs, syncMethods, createWrapFunction())
95
- massWrap(fs.promises, asyncMethods, createWrapFunction('promises.'))
96
-
97
- wrap(fs.realpath, 'native', createWrapFunction('', 'realpath.native'))
98
- wrap(fs.realpathSync, 'native', createWrapFunction('', 'realpath.native'))
99
- wrap(fs.promises.realpath, 'native', createWrapFunction('', 'realpath.native'))
100
-
101
- wrap(fs, 'createReadStream', wrapCreateStream)
102
- wrap(fs, 'createWriteStream', wrapCreateStream)
103
- if (fs.Dir) {
104
- wrap(fs.Dir.prototype, 'close', createWrapFunction('dir.'))
105
- wrap(fs.Dir.prototype, 'closeSync', createWrapFunction('dir.'))
106
- wrap(fs.Dir.prototype, 'read', createWrapFunction('dir.'))
107
- wrap(fs.Dir.prototype, 'readSync', createWrapFunction('dir.'))
108
- wrap(fs.Dir.prototype, Symbol.asyncIterator, createWrapDirAsyncIterator())
109
- }
87
+ addHook({ name: 'fs' }, fs => {
88
+ const asyncMethods = Object.keys(paramsByMethod)
89
+ const syncMethods = asyncMethods.map(name => `${name}Sync`)
90
+
91
+ massWrap(fs, asyncMethods, createWrapFunction())
92
+ massWrap(fs, syncMethods, createWrapFunction())
93
+ massWrap(fs.promises, asyncMethods, createWrapFunction('promises.'))
94
+
95
+ wrap(fs.realpath, 'native', createWrapFunction('', 'realpath.native'))
96
+ wrap(fs.realpathSync, 'native', createWrapFunction('', 'realpath.native'))
97
+ wrap(fs.promises.realpath, 'native', createWrapFunction('', 'realpath.native'))
98
+
99
+ wrap(fs, 'createReadStream', wrapCreateStream)
100
+ wrap(fs, 'createWriteStream', wrapCreateStream)
101
+ if (fs.Dir) {
102
+ wrap(fs.Dir.prototype, 'close', createWrapFunction('dir.'))
103
+ wrap(fs.Dir.prototype, 'closeSync', createWrapFunction('dir.'))
104
+ wrap(fs.Dir.prototype, 'read', createWrapFunction('dir.'))
105
+ wrap(fs.Dir.prototype, 'readSync', createWrapFunction('dir.'))
106
+ wrap(fs.Dir.prototype, Symbol.asyncIterator, createWrapDirAsyncIterator())
107
+ }
110
108
 
111
- wrap(fs, 'unwatchFile', createWatchWrapFunction())
112
- wrap(fs, 'watch', createWatchWrapFunction())
113
- wrap(fs, 'watchFile', createWatchWrapFunction())
109
+ wrap(fs, 'unwatchFile', createWatchWrapFunction())
110
+ wrap(fs, 'watch', createWatchWrapFunction())
111
+ wrap(fs, 'watchFile', createWatchWrapFunction())
112
+
113
+ return fs
114
+ })
114
115
 
115
- return fs
116
- })
117
- }
118
116
  function isFirstMethodReturningFileHandle (original) {
119
117
  return !kHandle && original.name === 'open'
120
118
  }
@@ -171,7 +171,7 @@ function wrapExecute (execute) {
171
171
  args,
172
172
  docSource: documentSources.get(document),
173
173
  source,
174
- fields: {},
174
+ fields: Object.create(null),
175
175
  abortController: new AbortController(),
176
176
  }
177
177
 
@@ -45,45 +45,73 @@ if (!dc.unsubscribe) {
45
45
  dc.unsubscribe = (channel, cb) => {
46
46
  if (dc.channel(channel).hasSubscribers) {
47
47
  dc.channel(channel).unsubscribe(cb)
48
+ return true
48
49
  }
50
+ return false
49
51
  }
50
52
  }
51
53
 
52
- function doHook (payload) {
53
- const hook = hooks[payload.package]
54
+ /**
55
+ * @param {string} name
56
+ */
57
+ function doHook (name) {
58
+ const hook = hooks[name] ?? hooks[`node:${name}`]
54
59
  if (!hook) {
55
- log.error('esbuild-wrapped %s missing in list of hooks', payload.package)
60
+ log.error('esbuild-wrapped %s missing in list of hooks', name)
56
61
  return
57
62
  }
58
63
 
59
64
  const hookFn = hook.fn ?? hook
60
65
  if (typeof hookFn !== 'function') {
61
- log.error('esbuild-wrapped hook %s is not a function', payload.package)
66
+ log.error('esbuild-wrapped hook %s is not a function', name)
62
67
  return
63
68
  }
64
69
 
65
70
  try {
66
71
  hookFn()
67
72
  } catch {
68
- log.error('esbuild-wrapped %s hook failed', payload.package)
73
+ log.error('esbuild-wrapped %s hook failed', name)
69
74
  }
70
75
  }
71
76
 
72
- dc.subscribe(CHANNEL, (payload) => {
73
- doHook(payload)
77
+ /** @type {Set<string>} */
78
+ const instrumentedNodeModules = new Set()
74
79
 
75
- if (!instrumentations[payload.package]) {
76
- log.error('esbuild-wrapped %s missing in list of instrumentations', payload.package)
80
+ /** @typedef {{ package: string, module: unknown, version: string, path: string }} Payload */
81
+ dc.subscribe(CHANNEL, (message) => {
82
+ const payload = /** @type {Payload} */ (message)
83
+ const name = payload.package
84
+
85
+ const isPrefixedWithNode = name.startsWith('node:')
86
+
87
+ const isNodeModule = isPrefixedWithNode || !hooks[name]
88
+
89
+ if (isNodeModule) {
90
+ const nodeName = isPrefixedWithNode ? name.slice(5) : name
91
+ // Used for node: prefixed modules to prevent double instrumentation.
92
+ if (instrumentedNodeModules.has(nodeName)) {
93
+ return
94
+ }
95
+ instrumentedNodeModules.add(nodeName)
96
+ }
97
+
98
+ doHook(name)
99
+
100
+ const instrumentation = instrumentations[name] ?? instrumentations[`node:${name}`]
101
+
102
+ if (!instrumentation) {
103
+ log.error('esbuild-wrapped %s missing in list of instrumentations', name)
77
104
  return
78
105
  }
79
106
 
80
- for (const { name, file, versions, hook } of instrumentations[payload.package]) {
81
- if (payload.path !== filename(name, file)) continue
82
- if (!matchVersion(payload.version, versions)) continue
107
+ for (const { file, versions, hook } of instrumentation) {
108
+ if (payload.path !== filename(name, file) || !matchVersion(payload.version, versions)) {
109
+ continue
110
+ }
83
111
 
84
112
  try {
85
113
  loadChannel.publish({ name, version: payload.version, file })
86
- payload.module = hook(payload.module, payload.version)
114
+ payload.module = hook(payload.module, payload.version) ?? payload.module
87
115
  } catch (e) {
88
116
  log.error('Error executing bundler hook', e)
89
117
  }