@depup/dd-trace 5.96.0-depup.0 → 5.97.0-depup.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 (116) hide show
  1. package/README.md +2 -2
  2. package/changes.json +1 -1
  3. package/index.d.ts +34 -0
  4. package/package.json +11 -9
  5. package/packages/datadog-esbuild/index.js +20 -9
  6. package/packages/datadog-instrumentations/src/child_process.js +7 -17
  7. package/packages/datadog-instrumentations/src/crypto.js +1 -2
  8. package/packages/datadog-instrumentations/src/cucumber.js +4 -1
  9. package/packages/datadog-instrumentations/src/cypress-config.js +324 -0
  10. package/packages/datadog-instrumentations/src/cypress.js +86 -4
  11. package/packages/datadog-instrumentations/src/dns.js +1 -2
  12. package/packages/datadog-instrumentations/src/express.js +4 -4
  13. package/packages/datadog-instrumentations/src/fs.js +27 -29
  14. package/packages/datadog-instrumentations/src/graphql.js +1 -1
  15. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +41 -13
  16. package/packages/datadog-instrumentations/src/helpers/hook.js +31 -6
  17. package/packages/datadog-instrumentations/src/helpers/hooks.js +12 -19
  18. package/packages/datadog-instrumentations/src/helpers/instrument.js +27 -13
  19. package/packages/datadog-instrumentations/src/helpers/register.js +103 -142
  20. package/packages/datadog-instrumentations/src/http/client.js +2 -3
  21. package/packages/datadog-instrumentations/src/http/server.js +2 -5
  22. package/packages/datadog-instrumentations/src/http2/client.js +1 -3
  23. package/packages/datadog-instrumentations/src/http2/server.js +1 -3
  24. package/packages/datadog-instrumentations/src/jest.js +13 -4
  25. package/packages/datadog-instrumentations/src/limitd-client.js +1 -1
  26. package/packages/datadog-instrumentations/src/mocha/utils.js +4 -1
  27. package/packages/datadog-instrumentations/src/net.js +2 -8
  28. package/packages/datadog-instrumentations/src/pino.js +1 -1
  29. package/packages/datadog-instrumentations/src/playwright.js +4 -1
  30. package/packages/datadog-instrumentations/src/prisma.js +1 -2
  31. package/packages/datadog-instrumentations/src/selenium.js +4 -1
  32. package/packages/datadog-instrumentations/src/sequelize.js +1 -1
  33. package/packages/datadog-instrumentations/src/url.js +1 -3
  34. package/packages/datadog-instrumentations/src/vitest.js +5 -1
  35. package/packages/datadog-instrumentations/src/vm.js +1 -3
  36. package/packages/datadog-plugin-aws-sdk/src/base.js +4 -3
  37. package/packages/datadog-plugin-cucumber/src/index.js +7 -3
  38. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +57 -5
  39. package/packages/datadog-plugin-graphql/src/resolve.js +1 -1
  40. package/packages/datadog-plugin-jest/src/index.js +4 -2
  41. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +31 -4
  42. package/packages/datadog-plugin-mocha/src/index.js +5 -2
  43. package/packages/datadog-plugin-next/src/index.js +2 -14
  44. package/packages/datadog-plugin-openai/src/services.js +1 -0
  45. package/packages/datadog-webpack/index.js +3 -3
  46. package/packages/dd-trace/index.js +12 -10
  47. package/packages/dd-trace/src/agent/url.js +2 -2
  48. package/packages/dd-trace/src/aiguard/sdk.js +4 -0
  49. package/packages/dd-trace/src/appsec/blocking.js +3 -0
  50. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +1 -1
  51. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +1 -1
  52. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
  53. package/packages/dd-trace/src/appsec/remote_config.js +1 -0
  54. package/packages/dd-trace/src/appsec/sdk/index.js +4 -0
  55. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +6 -1
  56. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +4 -0
  57. package/packages/dd-trace/src/config/defaults.js +316 -146
  58. package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -1
  59. package/packages/dd-trace/src/config/helper.js +59 -10
  60. package/packages/dd-trace/src/config/index.js +569 -1505
  61. package/packages/dd-trace/src/config/parsers.js +256 -0
  62. package/packages/dd-trace/src/config/remote_config.js +59 -2
  63. package/packages/dd-trace/src/config/supported-configurations.json +350 -433
  64. package/packages/dd-trace/src/crashtracking/crashtracker.js +7 -1
  65. package/packages/dd-trace/src/crashtracking/index.js +1 -7
  66. package/packages/dd-trace/src/debugger/index.js +1 -1
  67. package/packages/dd-trace/src/dogstatsd.js +12 -9
  68. package/packages/dd-trace/src/encode/0.4.js +1 -1
  69. package/packages/dd-trace/src/exporters/agent/writer.js +7 -1
  70. package/packages/dd-trace/src/exporters/common/request.js +9 -0
  71. package/packages/dd-trace/src/exporters/common/writer.js +12 -2
  72. package/packages/dd-trace/src/heap_snapshots.js +3 -0
  73. package/packages/dd-trace/src/index.js +5 -2
  74. package/packages/dd-trace/src/lambda/runtime/ritm.js +6 -6
  75. package/packages/dd-trace/src/llmobs/index.js +4 -1
  76. package/packages/dd-trace/src/llmobs/plugins/ai/index.js +5 -1
  77. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +60 -12
  78. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +4 -2
  79. package/packages/dd-trace/src/llmobs/sdk.js +12 -8
  80. package/packages/dd-trace/src/llmobs/span_processor.js +1 -1
  81. package/packages/dd-trace/src/llmobs/tagger.js +9 -6
  82. package/packages/dd-trace/src/llmobs/writers/base.js +2 -0
  83. package/packages/dd-trace/src/llmobs/writers/util.js +3 -0
  84. package/packages/dd-trace/src/log/index.js +26 -55
  85. package/packages/dd-trace/src/log/writer.js +7 -19
  86. package/packages/dd-trace/src/noop/proxy.js +8 -0
  87. package/packages/dd-trace/src/opentelemetry/logs/index.js +1 -1
  88. package/packages/dd-trace/src/opentelemetry/metrics/index.js +1 -1
  89. package/packages/dd-trace/src/opentracing/propagation/text_map.js +9 -4
  90. package/packages/dd-trace/src/payload-tagging/config/index.js +6 -5
  91. package/packages/dd-trace/src/plugin_manager.js +8 -6
  92. package/packages/dd-trace/src/plugins/ci_plugin.js +4 -0
  93. package/packages/dd-trace/src/plugins/plugin.js +7 -4
  94. package/packages/dd-trace/src/process-tags/index.js +3 -0
  95. package/packages/dd-trace/src/profiler.js +27 -2
  96. package/packages/dd-trace/src/profiling/config.js +73 -241
  97. package/packages/dd-trace/src/profiling/exporter_cli.js +1 -4
  98. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +6 -2
  99. package/packages/dd-trace/src/profiling/profiler.js +56 -44
  100. package/packages/dd-trace/src/profiling/profilers/events.js +2 -3
  101. package/packages/dd-trace/src/profiling/profilers/wall.js +89 -6
  102. package/packages/dd-trace/src/profiling/ssi-heuristics.js +4 -1
  103. package/packages/dd-trace/src/propagation-hash/index.js +2 -1
  104. package/packages/dd-trace/src/proxy.js +32 -3
  105. package/packages/dd-trace/src/remote_config/index.js +3 -0
  106. package/packages/dd-trace/src/require-package-json.js +8 -4
  107. package/packages/dd-trace/src/ritm.js +58 -26
  108. package/packages/dd-trace/src/runtime_metrics/index.js +3 -0
  109. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +3 -0
  110. package/packages/dd-trace/src/sampler.js +1 -1
  111. package/packages/dd-trace/src/standalone/index.js +3 -0
  112. package/packages/dd-trace/src/telemetry/index.js +2 -3
  113. package/packages/dd-trace/src/telemetry/send-data.js +5 -19
  114. package/packages/dd-trace/src/telemetry/session-propagation.js +19 -44
  115. package/packages/dd-trace/src/telemetry/telemetry.js +28 -171
  116. package/packages/dd-trace/src/util.js +0 -9
package/README.md CHANGED
@@ -13,8 +13,8 @@ npm install @depup/dd-trace
13
13
 
14
14
  | Field | Value |
15
15
  |-------|-------|
16
- | Original | [dd-trace](https://www.npmjs.com/package/dd-trace) @ 5.96.0 |
17
- | Processed | 2026-04-08 |
16
+ | Original | [dd-trace](https://www.npmjs.com/package/dd-trace) @ 5.97.0 |
17
+ | Processed | 2026-04-10 |
18
18
  | Smoke test | passed |
19
19
  | Deps updated | 0 |
20
20
 
package/changes.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "bumped": {},
3
- "timestamp": "2026-04-08T20:19:26.689Z",
3
+ "timestamp": "2026-04-10T16:23:10.646Z",
4
4
  "totalUpdated": 0
5
5
  }
package/index.d.ts CHANGED
@@ -130,6 +130,11 @@ interface Tracer extends opentracing.Tracer {
130
130
 
131
131
  appsec: tracer.Appsec;
132
132
 
133
+ /**
134
+ * Profiling API for attaching custom labels to profiler samples.
135
+ */
136
+ profiling: tracer.Profiling;
137
+
133
138
  TracerProvider: tracer.opentelemetry.TracerProvider;
134
139
 
135
140
  dogstatsd: tracer.DogStatsD;
@@ -1570,6 +1575,35 @@ declare namespace tracer {
1570
1575
  trackUserLoginFailure(login: string, metadata?: any): void;
1571
1576
  }
1572
1577
 
1578
+ export interface Profiling {
1579
+ /**
1580
+ * Declares the set of custom label keys that will be used with
1581
+ * {@link runWithLabels}. This is used for profile upload metadata
1582
+ * (so the Datadog UI knows which keys to index for filtering) and
1583
+ * for pprof serialization optimization.
1584
+ *
1585
+ * @param keys Custom label key names.
1586
+ */
1587
+ setCustomLabelKeys(keys: Iterable<string>): void;
1588
+
1589
+ /**
1590
+ * Runs a function with custom profiling labels attached to all wall profiler
1591
+ * samples taken during its execution. Labels are key-value pairs that appear
1592
+ * in the pprof output and can be used to filter flame graphs in the Datadog UI.
1593
+ *
1594
+ * Requires AsyncContextFrame (ACF) to be enabled. Supports nesting: inner
1595
+ * calls merge labels with outer calls, with inner values taking precedence.
1596
+ *
1597
+ * When profiling is not enabled or ACF is not active, the function is still
1598
+ * called but labels are silently dropped.
1599
+ *
1600
+ * @param labels Custom labels to attach to profiler samples.
1601
+ * @param fn Function to execute with the labels.
1602
+ * @returns The return value of fn.
1603
+ */
1604
+ runWithLabels<T>(labels: Record<string, string | number>, fn: () => T): T;
1605
+ }
1606
+
1573
1607
  export interface Appsec {
1574
1608
  /**
1575
1609
  * Links a successful login event to the current trace. Will link the passed user to the current trace with Appsec.setUser() internally.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@depup/dd-trace",
3
- "version": "5.96.0-depup.0",
3
+ "version": "5.97.0-depup.0",
4
4
  "description": "Datadog APM tracing client for JavaScript (with updated dependencies)",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -9,6 +9,8 @@
9
9
  "bench": "node benchmark/index.js",
10
10
  "bench:e2e:test-optimization": "node benchmark/e2e-test-optimization/benchmark-run.js",
11
11
  "dependencies:dedupe": "yarn-deduplicate yarn.lock",
12
+ "generate:config:types": "node scripts/generate-config-types.js",
13
+ "verify:config:types": "node scripts/generate-config-types.js --check",
12
14
  "type:check": "tsc --noEmit -p tsconfig.dev.json",
13
15
  "type:doc:build": "cd docs && yarn && yarn build",
14
16
  "type:doc:test": "cd docs && yarn && yarn test",
@@ -147,7 +149,7 @@
147
149
  "import-in-the-middle": "^3.0.1"
148
150
  },
149
151
  "optionalDependencies": {
150
- "@datadog/libdatadog": "0.9.2",
152
+ "@datadog/libdatadog": "0.9.3",
151
153
  "@datadog/native-appsec": "11.0.1",
152
154
  "@datadog/native-iast-taint-tracking": "4.1.0",
153
155
  "@datadog/native-metrics": "3.1.1",
@@ -171,19 +173,19 @@
171
173
  "@types/mocha": "^10.0.10",
172
174
  "@types/node": "^18.19.106",
173
175
  "@types/sinon": "^21.0.0",
174
- "axios": "^1.13.4",
176
+ "axios": "^1.15.0",
175
177
  "benchmark": "^2.1.4",
176
178
  "body-parser": "^2.2.2",
177
179
  "bun": "1.3.11",
178
180
  "codeowners-audit": "^2.9.0",
179
181
  "eslint": "^9.39.2",
180
- "eslint-plugin-cypress": "^6.2.1",
182
+ "eslint-plugin-cypress": "^6.2.2",
181
183
  "eslint-plugin-import": "^2.32.0",
182
- "eslint-plugin-jsdoc": "^62.8.1",
184
+ "eslint-plugin-jsdoc": "^62.9.0",
183
185
  "eslint-plugin-mocha": "^11.2.0",
184
186
  "eslint-plugin-n": "^17.23.2",
185
187
  "eslint-plugin-promise": "^7.2.1",
186
- "eslint-plugin-unicorn": "^63.0.0",
188
+ "eslint-plugin-unicorn": "^64.0.0",
187
189
  "express": "^5.1.0",
188
190
  "glob": "^10.4.5",
189
191
  "globals": "^17.2.0",
@@ -205,7 +207,7 @@
205
207
  "semver": "^7.7.2",
206
208
  "sinon": "^21.0.3",
207
209
  "tiktoken": "^1.0.21",
208
- "typescript": "^5.9.2",
210
+ "typescript": "^6.0.2",
209
211
  "workerpool": "^10.0.0",
210
212
  "yaml": "^2.8.3",
211
213
  "yarn-deduplicate": "^6.0.2"
@@ -214,8 +216,8 @@
214
216
  "changes": {},
215
217
  "depsUpdated": 0,
216
218
  "originalPackage": "dd-trace",
217
- "originalVersion": "5.96.0",
218
- "processedAt": "2026-04-08T20:19:41.573Z",
219
+ "originalVersion": "5.97.0",
220
+ "processedAt": "2026-04-10T16:23:28.402Z",
219
221
  "smokeTest": "passed"
220
222
  }
221
223
  }
@@ -2,7 +2,6 @@
2
2
 
3
3
  const { execSync } = require('node:child_process')
4
4
  const fs = require('node:fs')
5
- const RAW_BUILTINS = require('node:module').builtinModules
6
5
  const path = require('node:path')
7
6
  const { pathToFileURL, fileURLToPath } = require('node:url')
8
7
 
@@ -25,15 +24,27 @@ for (const hook of Object.values(hooks)) {
25
24
  }
26
25
  }
27
26
 
27
+ function moduleOfInterestKey (name, file) {
28
+ return file ? `${name}/${file}` : name
29
+ }
30
+
31
+ const builtinModules = new Set(require('module').builtinModules)
32
+
33
+ function addModuleOfInterest (name, file) {
34
+ if (!name) return
35
+
36
+ modulesOfInterest.add(moduleOfInterestKey(name, file))
37
+
38
+ if (builtinModules.has(name)) {
39
+ modulesOfInterest.add(moduleOfInterestKey(`node:${name}`, file))
40
+ }
41
+ }
42
+
28
43
  const modulesOfInterest = new Set()
29
44
 
30
- for (const instrumentation of Object.values(instrumentations)) {
45
+ for (const [name, instrumentation] of Object.entries(instrumentations)) {
31
46
  for (const entry of instrumentation) {
32
- if (entry.file) {
33
- modulesOfInterest.add(`${entry.name}/${entry.file}`) // e.g. "redis/my/file.js"
34
- } else {
35
- modulesOfInterest.add(entry.name) // e.g. "redis"
36
- }
47
+ addModuleOfInterest(name, entry.file)
37
48
  }
38
49
  }
39
50
 
@@ -41,7 +52,7 @@ const CHANNEL = 'dd-trace:bundler:load'
41
52
 
42
53
  const builtins = new Set()
43
54
 
44
- for (const builtin of RAW_BUILTINS) {
55
+ for (const builtin of builtinModules) {
45
56
  builtins.add(builtin)
46
57
  builtins.add(`node:${builtin}`)
47
58
  }
@@ -247,7 +258,7 @@ ${build.initialOptions.banner.js}`
247
258
  }
248
259
 
249
260
  try {
250
- const packageJson = JSON.parse(fs.readFileSync(/** @type {string} */ (pathToPackageJson)).toString())
261
+ const packageJson = JSON.parse(fs.readFileSync(/** @type {string} */(pathToPackageJson)).toString())
251
262
 
252
263
  const isESM = isESMFile(fullPathToModule, pathToPackageJson, packageJson)
253
264
  if (isESM && !interceptedESMModules.has(fullPathToModule)) {
@@ -14,11 +14,6 @@ const childProcessChannel = dc.tracingChannel('datadog:child_process:execution')
14
14
  // ignored exec method because it calls to execFile directly
15
15
  const execAsyncMethods = ['execFile', 'spawn', 'fork']
16
16
 
17
- const names = ['child_process', 'node:child_process']
18
-
19
- // child_process and node:child_process returns the same object instance, we only want to add hooks once
20
- let patched = false
21
-
22
17
  function throwSyncError (error) {
23
18
  throw error
24
19
  }
@@ -37,19 +32,14 @@ function returnSpawnSyncError (error, context) {
37
32
  return context.result
38
33
  }
39
34
 
40
- for (const name of names) {
41
- addHook({ name }, childProcess => {
42
- if (!patched) {
43
- patched = true
44
- shimmer.massWrap(childProcess, execAsyncMethods, wrapChildProcessAsyncMethod(childProcess.ChildProcess))
45
- shimmer.wrap(childProcess, 'execSync', wrapChildProcessSyncMethod(throwSyncError, true))
46
- shimmer.wrap(childProcess, 'execFileSync', wrapChildProcessSyncMethod(throwSyncError))
47
- shimmer.wrap(childProcess, 'spawnSync', wrapChildProcessSyncMethod(returnSpawnSyncError))
48
- }
35
+ addHook({ name: 'child_process' }, childProcess => {
36
+ shimmer.massWrap(childProcess, execAsyncMethods, wrapChildProcessAsyncMethod(childProcess.ChildProcess))
37
+ shimmer.wrap(childProcess, 'execSync', wrapChildProcessSyncMethod(throwSyncError, true))
38
+ shimmer.wrap(childProcess, 'execFileSync', wrapChildProcessSyncMethod(throwSyncError))
39
+ shimmer.wrap(childProcess, 'spawnSync', wrapChildProcessSyncMethod(returnSpawnSyncError))
49
40
 
50
- return childProcess
51
- })
52
- }
41
+ return childProcess
42
+ })
53
43
 
54
44
  function normalizeArgs (args, shell) {
55
45
  const childProcessInfo = {
@@ -11,9 +11,8 @@ const cryptoCipherCh = channel('datadog:crypto:cipher:start')
11
11
 
12
12
  const hashMethods = ['createHash', 'createHmac', 'createSign', 'createVerify', 'sign', 'verify']
13
13
  const cipherMethods = ['createCipheriv', 'createDecipheriv']
14
- const names = ['crypto', 'node:crypto']
15
14
 
16
- addHook({ name: names }, crypto => {
15
+ addHook({ name: 'crypto' }, crypto => {
17
16
  shimmer.massWrap(crypto, hashMethods, wrapCryptoMethod(cryptoHashCh))
18
17
  shimmer.massWrap(crypto, cipherMethods, wrapCryptoMethod(cryptoCipherCh))
19
18
  return crypto
@@ -61,6 +61,8 @@ const numRetriesByPickleId = new Map()
61
61
  const numAttemptToCtx = new Map()
62
62
  const newTestsByTestFullname = new Map()
63
63
  const modifiedTestsByPickleId = new Map()
64
+ // Pickle IDs for tests that are genuinely new (not in known tests list).
65
+ const newTestPickleIds = new Set()
64
66
 
65
67
  let eventDataCollector = null
66
68
  let pickleByFile = {}
@@ -359,7 +361,7 @@ function wrapRun (pl, isLatestVersion, version) {
359
361
  }
360
362
 
361
363
  if (isKnownTestsEnabled && status !== 'skip') {
362
- isNew = numRetries !== undefined
364
+ isNew = newTestPickleIds.has(this.pickle.id)
363
365
  }
364
366
 
365
367
  if (isNew || isModified) {
@@ -714,6 +716,7 @@ function getWrappedRunTestCase (runTestCaseFunction, isNewerCucumberVersion = fa
714
716
  if (isKnownTestsEnabled && !isAttemptToFix) {
715
717
  isNew = isNewTest(testSuitePath, pickle.name)
716
718
  if (isNew) {
719
+ newTestPickleIds.add(pickle.id)
717
720
  numRetriesByPickleId.set(pickle.id, 0)
718
721
  }
719
722
  }
@@ -0,0 +1,324 @@
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
+
8
+ const DD_CONFIG_WRAPPED = Symbol('dd-trace.cypress.config.wrapped')
9
+
10
+ const noopTask = {
11
+ 'dd:testSuiteStart': () => null,
12
+ 'dd:beforeEach': () => ({}),
13
+ 'dd:afterEach': () => null,
14
+ 'dd:addTags': () => null,
15
+ 'dd:log': () => null,
16
+ }
17
+
18
+ /**
19
+ * @param {unknown} value
20
+ * @returns {boolean}
21
+ */
22
+ function isPlainObject (value) {
23
+ if (!value || typeof value !== 'object') return false
24
+ const prototype = Object.getPrototypeOf(value)
25
+ return prototype === Object.prototype || prototype === null
26
+ }
27
+
28
+ /**
29
+ * Cypress allows setupNodeEvents to return partial config fragments that it
30
+ * diffs and merges into the resolved config. Preserve that behavior here so
31
+ * the wrapper does not drop user-provided config updates.
32
+ *
33
+ * @param {object} config Cypress resolved config object
34
+ * @param {unknown} updatedConfig value returned from setupNodeEvents
35
+ * @returns {object} resolved config with returned overrides applied
36
+ */
37
+ function mergeReturnedConfig (config, updatedConfig) {
38
+ if (!isPlainObject(updatedConfig) || updatedConfig === config) {
39
+ return config
40
+ }
41
+
42
+ const mergedConfig = { ...config }
43
+
44
+ for (const [key, value] of Object.entries(updatedConfig)) {
45
+ mergedConfig[key] = isPlainObject(value) && isPlainObject(mergedConfig[key])
46
+ ? mergeReturnedConfig(mergedConfig[key], value)
47
+ : value
48
+ }
49
+
50
+ return mergedConfig
51
+ }
52
+
53
+ /**
54
+ * Creates a temporary wrapper support file under os.tmpdir() that loads
55
+ * dd-trace's browser-side hooks before the user's original support file.
56
+ * Returns the wrapper path (for cleanup) or undefined if injection was skipped.
57
+ *
58
+ * @param {object} config Cypress resolved config object
59
+ * @returns {string|undefined} wrapper file path, or undefined if skipped
60
+ */
61
+ function injectSupportFile (config) {
62
+ const originalSupportFile = config.supportFile
63
+ if (!originalSupportFile || originalSupportFile === false) return
64
+
65
+ try {
66
+ const content = fs.readFileSync(originalSupportFile, 'utf8')
67
+ // Naive check: skip lines starting with // or * to avoid matching commented-out imports.
68
+ const hasActiveDdTraceImport = content.split('\n').some(line => {
69
+ const trimmed = line.trim()
70
+ return trimmed.includes('dd-trace/ci/cypress/support') &&
71
+ !trimmed.startsWith('//') && !trimmed.startsWith('*')
72
+ })
73
+ if (hasActiveDdTraceImport) return
74
+ } catch {
75
+ return
76
+ }
77
+
78
+ const ddSupportFile = require.resolve('../../../ci/cypress/support')
79
+ const wrapperFile = path.join(os.tmpdir(), `dd-cypress-support-${process.pid}.mjs`)
80
+
81
+ // Always use ESM: it can import both CJS and ESM support files.
82
+ const wrapperContent =
83
+ `import ${JSON.stringify(ddSupportFile)}\nimport ${JSON.stringify(originalSupportFile)}\n`
84
+
85
+ try {
86
+ fs.writeFileSync(wrapperFile, wrapperContent)
87
+ config.supportFile = wrapperFile
88
+ return wrapperFile
89
+ } catch {
90
+ // Can't write wrapper - skip injection
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Registers dd-trace's Cypress hooks (before:run, after:spec, after:run, tasks)
96
+ * and injects the support file. Handles chaining with user-registered handlers
97
+ * for after:spec/after:run so both the user's code and dd-trace's run in sequence.
98
+ *
99
+ * @param {Function} on Cypress event registration function
100
+ * @param {object} config Cypress resolved config object
101
+ * @param {Function[]} userAfterSpecHandlers user's after:spec handlers collected from wrappedOn
102
+ * @param {Function[]} userAfterRunHandlers user's after:run handlers collected from wrappedOn
103
+ * @returns {object} the config object (possibly modified)
104
+ */
105
+ function registerDdTraceHooks (on, config, userAfterSpecHandlers, userAfterRunHandlers) {
106
+ const wrapperFile = injectSupportFile(config)
107
+
108
+ const cleanupWrapper = () => {
109
+ if (wrapperFile) {
110
+ try { fs.unlinkSync(wrapperFile) } catch { /* best effort */ }
111
+ }
112
+ }
113
+
114
+ const tracer = global._ddtrace
115
+
116
+ const registerAfterRunWithCleanup = () => {
117
+ on('after:run', (results) => {
118
+ const chain = userAfterRunHandlers.reduce(
119
+ (p, h) => p.then(() => h(results)),
120
+ Promise.resolve()
121
+ )
122
+ return chain.finally(cleanupWrapper)
123
+ })
124
+ }
125
+
126
+ const registerNoopHandlers = () => {
127
+ for (const h of userAfterSpecHandlers) on('after:spec', h)
128
+ registerAfterRunWithCleanup()
129
+ on('task', noopTask)
130
+ }
131
+
132
+ if (!tracer || !tracer._initialized) {
133
+ registerNoopHandlers()
134
+ return config
135
+ }
136
+
137
+ const NoopTracer = require('../../../packages/dd-trace/src/noop/tracer')
138
+
139
+ if (tracer._tracer instanceof NoopTracer) {
140
+ registerNoopHandlers()
141
+ return config
142
+ }
143
+
144
+ const cypressPlugin = require('../../../packages/datadog-plugin-cypress/src/cypress-plugin')
145
+
146
+ if (cypressPlugin._isInit) {
147
+ for (const h of userAfterSpecHandlers) on('after:spec', h)
148
+ registerAfterRunWithCleanup()
149
+ return config
150
+ }
151
+
152
+ on('before:run', cypressPlugin.beforeRun.bind(cypressPlugin))
153
+
154
+ on('after:spec', (spec, results) => {
155
+ const chain = userAfterSpecHandlers.reduce(
156
+ (p, h) => p.then(() => h(spec, results)),
157
+ Promise.resolve()
158
+ )
159
+ return chain.then(() => cypressPlugin.afterSpec(spec, results))
160
+ })
161
+
162
+ on('after:run', (results) => {
163
+ const chain = userAfterRunHandlers.reduce(
164
+ (p, h) => p.then(() => h(results)),
165
+ Promise.resolve()
166
+ )
167
+ return chain
168
+ .then(() => cypressPlugin.afterRun(results))
169
+ .finally(cleanupWrapper)
170
+ })
171
+
172
+ on('task', cypressPlugin.getTasks())
173
+
174
+ return Promise.resolve(cypressPlugin.init(tracer, config)).then(() => config)
175
+ }
176
+
177
+ /**
178
+ * @param {Function|undefined} originalSetupNodeEvents
179
+ * @returns {Function}
180
+ */
181
+ function wrapSetupNodeEvents (originalSetupNodeEvents) {
182
+ return function ddSetupNodeEvents (on, config) {
183
+ const userAfterSpecHandlers = []
184
+ const userAfterRunHandlers = []
185
+
186
+ const wrappedOn = (event, handler) => {
187
+ if (event === 'after:spec') {
188
+ userAfterSpecHandlers.push(handler)
189
+ } else if (event === 'after:run') {
190
+ userAfterRunHandlers.push(handler)
191
+ } else {
192
+ on(event, handler)
193
+ }
194
+ }
195
+
196
+ const maybePromise = originalSetupNodeEvents
197
+ ? originalSetupNodeEvents.call(this, wrappedOn, config)
198
+ : undefined
199
+
200
+ if (maybePromise && typeof maybePromise.then === 'function') {
201
+ return maybePromise.then((result) => {
202
+ return registerDdTraceHooks(
203
+ on,
204
+ mergeReturnedConfig(config, result),
205
+ userAfterSpecHandlers,
206
+ userAfterRunHandlers
207
+ )
208
+ })
209
+ }
210
+
211
+ return registerDdTraceHooks(
212
+ on,
213
+ mergeReturnedConfig(config, maybePromise),
214
+ userAfterSpecHandlers,
215
+ userAfterRunHandlers
216
+ )
217
+ }
218
+ }
219
+
220
+ /**
221
+ * @param {object} config
222
+ * @returns {object}
223
+ */
224
+ function wrapConfig (config) {
225
+ if (!config || config[DD_CONFIG_WRAPPED]) return config
226
+ config[DD_CONFIG_WRAPPED] = true
227
+
228
+ if (config.e2e) {
229
+ config.e2e.setupNodeEvents = wrapSetupNodeEvents(config.e2e.setupNodeEvents)
230
+ }
231
+ if (config.component) {
232
+ config.component.setupNodeEvents = wrapSetupNodeEvents(config.component.setupNodeEvents)
233
+ }
234
+
235
+ return config
236
+ }
237
+
238
+ /**
239
+ * @param {string} originalConfigFile absolute path to the original config file
240
+ * @returns {string} path to the generated wrapper file
241
+ */
242
+ function createConfigWrapper (originalConfigFile) {
243
+ const wrapperFile = path.join(
244
+ path.dirname(originalConfigFile),
245
+ `.dd-cypress-config-${process.pid}.mjs`
246
+ )
247
+
248
+ const cypressConfigPath = require.resolve('./cypress-config')
249
+
250
+ // Always use ESM: it can import both CJS and ESM configs, so it works
251
+ // regardless of the original file's extension or "type": "module" in package.json.
252
+ // Import cypress-config.js directly (CJS default = module.exports object).
253
+ fs.writeFileSync(wrapperFile, [
254
+ `import originalConfig from ${JSON.stringify(pathToFileURL(originalConfigFile).href)}`,
255
+ `import cypressConfig from ${JSON.stringify(pathToFileURL(cypressConfigPath).href)}`,
256
+ '',
257
+ 'export default cypressConfig.wrapConfig(originalConfig)',
258
+ '',
259
+ ].join('\n'))
260
+
261
+ return wrapperFile
262
+ }
263
+
264
+ /**
265
+ * Wraps the Cypress config file for a CLI start() call. When an explicit
266
+ * configFile is provided, creates a temp wrapper that imports the original
267
+ * and passes it through wrapConfig. This handles ESM configs (.mjs) and
268
+ * plain-object configs (without defineConfig) that can't be intercepted
269
+ * via the defineConfig shimmer.
270
+ *
271
+ * @param {object|undefined} options
272
+ * @returns {{ options: object|undefined, cleanup: Function }}
273
+ */
274
+ function wrapCliConfigFileOptions (options) {
275
+ const noop = { options, cleanup: () => {} }
276
+
277
+ if (!options) return noop
278
+
279
+ const projectRoot = typeof options.project === 'string' ? options.project : process.cwd()
280
+ let configFilePath
281
+
282
+ if (options.configFile === false) {
283
+ // configFile: false means "no config file" — respect Cypress's semantics
284
+ return noop
285
+ } else if (typeof options.configFile === 'string') {
286
+ configFilePath = path.isAbsolute(options.configFile)
287
+ ? options.configFile
288
+ : path.resolve(projectRoot, options.configFile)
289
+ } else {
290
+ // No explicit --config-file: resolve the default cypress.config.{js,ts,cjs,mjs}
291
+ for (const ext of ['.js', '.ts', '.cjs', '.mjs']) {
292
+ const candidate = path.join(projectRoot, `cypress.config${ext}`)
293
+ if (fs.existsSync(candidate)) {
294
+ configFilePath = candidate
295
+ break
296
+ }
297
+ }
298
+ }
299
+
300
+ // Skip .ts files — Cypress transpiles them internally via its own loader.
301
+ // The ESM wrapper can't import .ts directly. The defineConfig shimmer
302
+ // handles .ts configs since they're transpiled to CJS by Cypress.
303
+ if (!configFilePath || !fs.existsSync(configFilePath) || path.extname(configFilePath) === '.ts') return noop
304
+
305
+ try {
306
+ const wrapperFile = createConfigWrapper(configFilePath)
307
+
308
+ return {
309
+ options: { ...options, configFile: wrapperFile },
310
+ cleanup: () => {
311
+ try { fs.unlinkSync(wrapperFile) } catch { /* best effort */ }
312
+ },
313
+ }
314
+ } catch {
315
+ // Config directory may be read-only — fall back to no wrapping.
316
+ // The defineConfig shimmer will still handle configs that use defineConfig.
317
+ return noop
318
+ }
319
+ }
320
+
321
+ module.exports = {
322
+ wrapCliConfigFileOptions,
323
+ wrapConfig,
324
+ }