dd-trace 6.7.0 → 6.8.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 (147) hide show
  1. package/ci/test-optimization-validation/command-output-policy.js +5 -5
  2. package/ci/test-optimization-validation/framework-adapters/playwright.js +5 -5
  3. package/ci/test-optimization-validation/generated-files.js +9 -9
  4. package/ci/test-optimization-validation/generated-test-contract.js +1 -3
  5. package/ci/test-optimization-validation/manifest-scaffold.js +1 -2
  6. package/ci/test-optimization-validation/offline-output.js +6 -6
  7. package/ci/test-optimization-validation/runner-command.js +1 -1
  8. package/ci/test-optimization-validation/safe-files.js +4 -4
  9. package/ci/test-optimization-validation/scenarios/basic-reporting.js +1 -1
  10. package/ci/test-optimization-validation/scenarios/test-management.js +2 -2
  11. package/ci/vitest-no-worker-init-setup.mjs +348 -51
  12. package/index.d.ts +77 -13
  13. package/package.json +9 -3
  14. package/packages/datadog-core/src/utils/src/kebabcase.js +8 -2
  15. package/packages/datadog-esbuild/index.js +4 -2
  16. package/packages/datadog-esbuild/src/utils.js +1 -1
  17. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +4 -4
  18. package/packages/datadog-instrumentations/src/cucumber.js +9 -6
  19. package/packages/datadog-instrumentations/src/helpers/extract-package-and-module-path.js +10 -0
  20. package/packages/datadog-instrumentations/src/helpers/hook.js +19 -2
  21. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  22. package/packages/datadog-instrumentations/src/helpers/register.js +9 -3
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +1 -0
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +57 -0
  26. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +50 -18
  27. package/packages/datadog-instrumentations/src/helpers/router-helper.js +1 -1
  28. package/packages/datadog-instrumentations/src/helpers/shared-utils.js +39 -0
  29. package/packages/datadog-instrumentations/src/http/client.js +1 -3
  30. package/packages/datadog-instrumentations/src/jest.js +8 -14
  31. package/packages/datadog-instrumentations/src/mocha/common.js +1 -3
  32. package/packages/datadog-instrumentations/src/mocha/main.js +2 -42
  33. package/packages/datadog-instrumentations/src/mocha/utils.js +68 -2
  34. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +19 -0
  35. package/packages/datadog-instrumentations/src/mocha/worker.js +446 -42
  36. package/packages/datadog-instrumentations/src/moleculer/client.js +1 -1
  37. package/packages/datadog-instrumentations/src/playwright.js +3 -2
  38. package/packages/datadog-instrumentations/src/rhea.js +1 -1
  39. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +525 -56
  40. package/packages/datadog-instrumentations/src/vitest-main.js +90 -10
  41. package/packages/datadog-instrumentations/src/webdriverio.js +914 -0
  42. package/packages/datadog-plugin-aerospike/src/index.js +1 -3
  43. package/packages/datadog-plugin-amqplib/src/client.js +1 -1
  44. package/packages/datadog-plugin-amqplib/src/producer.js +1 -1
  45. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +2 -2
  46. package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +2 -2
  47. package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -3
  48. package/packages/datadog-plugin-azure-service-bus/src/producer.js +5 -3
  49. package/packages/datadog-plugin-bullmq/src/producer.js +3 -3
  50. package/packages/datadog-plugin-child_process/src/index.js +3 -3
  51. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +5 -8
  52. package/packages/datadog-plugin-cypress/src/source-map-utils.js +1 -1
  53. package/packages/datadog-plugin-fastify/src/tracing.js +1 -1
  54. package/packages/datadog-plugin-fs/src/index.js +0 -4
  55. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +14 -7
  56. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +19 -10
  57. package/packages/datadog-plugin-graphql/src/execute.js +1 -0
  58. package/packages/datadog-plugin-graphql/src/tools/signature.js +1 -1
  59. package/packages/datadog-plugin-http/src/client.js +2 -1
  60. package/packages/datadog-plugin-jest/src/index.js +2 -3
  61. package/packages/datadog-plugin-jest/src/util.js +2 -1
  62. package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
  63. package/packages/datadog-plugin-mocha/src/index.js +25 -6
  64. package/packages/datadog-plugin-prisma/src/index.js +5 -1
  65. package/packages/datadog-plugin-rhea/src/consumer.js +16 -13
  66. package/packages/datadog-plugin-rhea/src/producer.js +1 -1
  67. package/packages/datadog-plugin-undici/src/index.js +2 -1
  68. package/packages/datadog-plugin-vitest/src/index.js +84 -11
  69. package/packages/datadog-webpack/index.js +2 -2
  70. package/packages/dd-trace/src/appsec/downstream_requests.js +1 -1
  71. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-base-analyzer.js +1 -1
  72. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-analyzer.js +1 -1
  73. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +1 -1
  74. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +1 -3
  75. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
  76. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +2 -2
  77. package/packages/dd-trace/src/appsec/sdk/track_event.js +1 -1
  78. package/packages/dd-trace/src/appsec/waf/waf_manager.js +1 -1
  79. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +7 -6
  80. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +2 -2
  81. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +2 -1
  82. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +4 -0
  83. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/webdriverio.js +26 -0
  84. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -1
  85. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -3
  86. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +1 -1
  87. package/packages/dd-trace/src/config/defaults.js +14 -6
  88. package/packages/dd-trace/src/config/generated-config-types.d.ts +2 -2
  89. package/packages/dd-trace/src/config/index.js +15 -0
  90. package/packages/dd-trace/src/config/parsers.js +19 -1
  91. package/packages/dd-trace/src/config/supported-configurations.json +5 -6
  92. package/packages/dd-trace/src/datastreams/encoding.js +3 -4
  93. package/packages/dd-trace/src/debugger/devtools_client/log.js +2 -1
  94. package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +1 -4
  95. package/packages/dd-trace/src/debugger/devtools_client/send.js +1 -2
  96. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +2 -2
  97. package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +16 -2
  98. package/packages/dd-trace/src/encode/tags-processors.js +2 -2
  99. package/packages/dd-trace/src/exporters/common/url.js +3 -5
  100. package/packages/dd-trace/src/exporters/electron/index.js +4 -1
  101. package/packages/dd-trace/src/external-logger/src/index.js +1 -2
  102. package/packages/dd-trace/src/flare/index.js +2 -2
  103. package/packages/dd-trace/src/id.js +1 -1
  104. package/packages/dd-trace/src/llmobs/constants/tags.js +1 -1
  105. package/packages/dd-trace/src/llmobs/experiments/client.js +4 -2
  106. package/packages/dd-trace/src/llmobs/experiments/dataset.js +66 -14
  107. package/packages/dd-trace/src/llmobs/experiments/experiment.js +289 -95
  108. package/packages/dd-trace/src/llmobs/experiments/index.js +51 -12
  109. package/packages/dd-trace/src/llmobs/experiments/noop.js +95 -10
  110. package/packages/dd-trace/src/llmobs/experiments/result.js +13 -2
  111. package/packages/dd-trace/src/llmobs/experiments/util.js +218 -0
  112. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +1 -1
  113. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +1 -1
  114. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
  115. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
  116. package/packages/dd-trace/src/llmobs/sdk.js +4 -11
  117. package/packages/dd-trace/src/llmobs/span_processor.js +9 -7
  118. package/packages/dd-trace/src/llmobs/tagger.js +1 -2
  119. package/packages/dd-trace/src/llmobs/writers/spans.js +12 -6
  120. package/packages/dd-trace/src/noop/proxy.js +2 -2
  121. package/packages/dd-trace/src/opentelemetry/metrics/otlp_transformer.js +1 -1
  122. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +2 -1
  123. package/packages/dd-trace/src/opentracing/span.js +1 -1
  124. package/packages/dd-trace/src/opentracing/span_context.js +1 -1
  125. package/packages/dd-trace/src/plugins/ci_plugin.js +105 -20
  126. package/packages/dd-trace/src/plugins/index.js +1 -0
  127. package/packages/dd-trace/src/plugins/plugin.js +1 -1
  128. package/packages/dd-trace/src/plugins/tracing.js +1 -0
  129. package/packages/dd-trace/src/plugins/util/ci.js +7 -6
  130. package/packages/dd-trace/src/plugins/util/git.js +1 -1
  131. package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +1 -1
  132. package/packages/dd-trace/src/plugins/util/llm.js +2 -2
  133. package/packages/dd-trace/src/plugins/util/tags.js +2 -0
  134. package/packages/dd-trace/src/plugins/util/test.js +38 -12
  135. package/packages/dd-trace/src/plugins/util/url.js +5 -5
  136. package/packages/dd-trace/src/plugins/util/user-provided-git.js +1 -1
  137. package/packages/dd-trace/src/plugins/util/web.js +1 -1
  138. package/packages/dd-trace/src/priority_sampler.js +6 -3
  139. package/packages/dd-trace/src/profiling/profilers/poisson.js +1 -1
  140. package/packages/dd-trace/src/profiling/profilers/shared.js +2 -2
  141. package/packages/dd-trace/src/ritm.js +5 -20
  142. package/packages/dd-trace/src/startup-log.js +3 -0
  143. package/packages/dd-trace/src/telemetry/logs/log-collector.js +3 -1
  144. package/packages/dd-trace/src/util.js +16 -0
  145. package/vendor/dist/@apm-js-collab/code-transformer/index.js +12 -12
  146. package/vendor/dist/pprof-format/index.js +1 -1
  147. package/vendor/dist/source-map/index.js +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "6.7.0",
3
+ "version": "6.8.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -96,6 +96,9 @@
96
96
  "test:integration:playwright:coverage": "node ./integration-tests/coverage/run-suite.js --timeout 60000 \"integration-tests/playwright/${SPEC:-playwright-*}.spec.js\"",
97
97
  "test:integration:selenium": "mocha --timeout 60000 \"integration-tests/selenium/*.spec.js\"",
98
98
  "test:integration:selenium:coverage": "node ./integration-tests/coverage/run-suite.js --timeout 60000 \"integration-tests/selenium/*.spec.js\"",
99
+ "test:integration:test-optimization:helpers": "mocha \"integration-tests/cypress/*dependencies.spec.js\" \"integration-tests/jest/*dependencies.spec.js\" \"integration-tests/playwright/version*.spec.js\"",
100
+ "test:integration:webdriverio": "mocha --timeout 60000 \"integration-tests/webdriverio/*.spec.js\"",
101
+ "test:integration:webdriverio:coverage": "node ./integration-tests/coverage/run-suite.js --timeout 60000 \"integration-tests/webdriverio/*.spec.js\"",
99
102
  "test:integration:vitest": "mocha --timeout 60000 \"integration-tests/vitest/${SPEC:-vitest*}.spec.js\"",
100
103
  "test:integration:vitest:coverage": "node ./integration-tests/coverage/run-suite.js --timeout 60000 \"integration-tests/vitest/${SPEC:-vitest*}.spec.js\"",
101
104
  "test:integration:testopt": "mocha --timeout 120000 \"integration-tests/ci-visibility/*.spec.js\"",
@@ -178,7 +181,7 @@
178
181
  "@datadog/native-appsec": "11.0.1",
179
182
  "@datadog/native-iast-taint-tracking": "4.2.0",
180
183
  "@datadog/native-metrics": "3.1.2",
181
- "@datadog/openfeature-node-server": "2.0.0",
184
+ "@datadog/openfeature-node-server": "2.0.2",
182
185
  "@datadog/pprof": "5.17.0",
183
186
  "@datadog/wasm-js-rewriter": "5.0.1",
184
187
  "@opentelemetry/api": ">=1.0.0 <1.10.0",
@@ -199,6 +202,7 @@
199
202
  "@types/node": "^18.19.106",
200
203
  "@types/sinon": "^22.0.0",
201
204
  "@vercel/nft": "^1.10.2",
205
+ "@yarnpkg/lockfile": "^1.1.0",
202
206
  "axios": "^1.18.1",
203
207
  "benchmark": "^2.1.4",
204
208
  "body-parser": "^2.3.0",
@@ -213,7 +217,7 @@
213
217
  "eslint-plugin-n": "^18.2.1",
214
218
  "eslint-plugin-promise": "^7.3.0",
215
219
  "eslint-plugin-sonarjs": "^4.2.0",
216
- "eslint-plugin-unicorn": "^68.0.0",
220
+ "eslint-plugin-unicorn": "^72.0.0",
217
221
  "express": "^5.1.0",
218
222
  "glob": "^10.4.5",
219
223
  "globals": "^17.7.0",
@@ -221,7 +225,9 @@
221
225
  "husky": "^9.1.7",
222
226
  "istanbul-lib-report": "^3.0.0",
223
227
  "istanbul-reports": "^3.0.2",
228
+ "jsonc-parser": "^3.3.1",
224
229
  "jszip": "^3.10.1",
230
+ "minimatch": "^10.2.5",
225
231
  "mocha": "^11.7.6",
226
232
  "mocha-junit-reporter": "^2.2.1",
227
233
  "mocha-multi-reporters": "^1.5.1",
@@ -5,10 +5,16 @@ module.exports = function kebabcase (str) {
5
5
  throw new TypeError('Expected a string')
6
6
  }
7
7
 
8
- return str
8
+ const kebab = str
9
9
  .trim()
10
10
  .replaceAll(/([a-z])([A-Z])/g, '$1-$2') // Convert camelCase to kebab-case
11
11
  .replaceAll(/[\s_]+/g, '-') // Replace spaces and underscores with a single dash
12
- .replaceAll(/^-+|-+$/g, '') // Trim leading and trailing dashes
13
12
  .toLowerCase()
13
+
14
+ // Trim by index; a `/-+$/` regex re-examines the dash run once per start position.
15
+ let start = 0
16
+ let end = kebab.length
17
+ while (kebab.charCodeAt(start) === 45) start++ // '-'
18
+ while (end > start && kebab.charCodeAt(end - 1) === 45) end--
19
+ return kebab.slice(start, end)
14
20
  }
@@ -265,10 +265,12 @@ ${build.initialOptions.banner.js}`
265
265
  }
266
266
  }
267
267
  // The file namespace is used when requiring files from disk in userland
268
+ if (extracted.pkg === null) return
269
+
268
270
  let pathToPackageJson
269
271
  try {
270
272
  // we can't use require.resolve('pkg/package.json') as ESM modules don't make the file available
271
- pathToPackageJson = require.resolve(`${extracted.pkg}`, { paths: [args.resolveDir] })
273
+ pathToPackageJson = require.resolve(extracted.pkg, { paths: [args.resolveDir] })
272
274
  pathToPackageJson = extractPackageAndModulePath(pathToPackageJson).pkgJson
273
275
  } catch (err) {
274
276
  if (err.code === 'MODULE_NOT_FOUND') {
@@ -340,7 +342,7 @@ ${build.initialOptions.banner.js}`
340
342
 
341
343
  if (data.isESM) {
342
344
  if (args.path.endsWith(ESM_INTERCEPTED_SUFFIX)) {
343
- args.path = args.path.slice(0, -1 * ESM_INTERCEPTED_SUFFIX.length)
345
+ args.path = args.path.slice(0, -ESM_INTERCEPTED_SUFFIX.length)
344
346
 
345
347
  if (data.internal) {
346
348
  args.path = args.path.slice(INTERNAL_ESM_INTERCEPTED_PREFIX.length)
@@ -174,7 +174,7 @@ async function processModule ({ path, internal = false, context, excludeDefault
174
174
  for (const n of exportNames) {
175
175
  if (n === 'default' && excludeDefault) continue
176
176
 
177
- if (isStarExportLine(n) === true) {
177
+ if (isStarExportLine(n)) {
178
178
  // export * from 'wherever'
179
179
  const [, modFile] = n.split('* from ')
180
180
 
@@ -54,7 +54,7 @@ function buildTracerHooks (sessionCtx) {
54
54
  sessionCtx.cwd = input.cwd
55
55
  sessionCtx.transcriptPath = input.transcript_path
56
56
  sessionCtx.agentType = input.agent_type
57
- sessionCtx.permissionMode = sessionCtx.permissionMode || input.permission_mode
57
+ sessionCtx.permissionMode ||= input.permission_mode
58
58
  return {}
59
59
  }
60
60
 
@@ -64,8 +64,8 @@ function buildTracerHooks (sessionCtx) {
64
64
  }
65
65
 
66
66
  function onUserPromptSubmit (input) {
67
- sessionCtx.sessionId = sessionCtx.sessionId || input.session_id
68
- sessionCtx.prompt = sessionCtx.prompt || input.prompt
67
+ sessionCtx.sessionId ||= input.session_id
68
+ sessionCtx.prompt ||= input.prompt
69
69
  return {}
70
70
  }
71
71
 
@@ -254,7 +254,7 @@ function createStreamLookup (chunks) {
254
254
  scanLocalLifecycle(chunks, startIndex, toolUseId, localLifecycle)
255
255
  if (localLifecycle.toolResultIndex !== undefined) return localLifecycle
256
256
 
257
- streamIndex = streamIndex || buildStreamIndex(chunks)
257
+ streamIndex ||= buildStreamIndex(chunks)
258
258
  const indexedLifecycle = streamIndex.get(toolUseId)
259
259
  return {
260
260
  taskStartedChunk: localLifecycle.taskStartedChunk || indexedLifecycle?.taskStartedChunk,
@@ -6,6 +6,7 @@ const { createCoverageMap } = require('../../../vendor/dist/istanbul-lib-coverag
6
6
  const shimmer = require('../../datadog-shimmer')
7
7
  const log = require('../../dd-trace/src/log')
8
8
  const { getEnvironmentVariable } = require('../../dd-trace/src/config/helper')
9
+ const { getSegment } = require('../../dd-trace/src/util')
9
10
  const {
10
11
  getCoveredFilesFromCoverage,
11
12
  getExecutableFilesFromCoverage,
@@ -342,7 +343,7 @@ function handleParallelTestCaseFinished (pickle, worstTestStepResult) {
342
343
  }
343
344
 
344
345
  const testFileAbsolutePath = pickle.uri
345
- const finished = pickleResultByFile[testFileAbsolutePath] || (pickleResultByFile[testFileAbsolutePath] = [])
346
+ const finished = (pickleResultByFile[testFileAbsolutePath] ||= [])
346
347
 
347
348
  if (isEarlyFlakeDetectionEnabled && isNew) {
348
349
  const testFullname = `${pickle.uri}:${pickle.name}`
@@ -555,8 +556,7 @@ function getErrorFromCucumberResult (cucumberResult) {
555
556
  return
556
557
  }
557
558
 
558
- const [message] = cucumberResult.message.split('\n')
559
- const error = new Error(message)
559
+ const error = new Error(getSegment(cucumberResult.message, '\n', 0))
560
560
  if (cucumberResult.exception) {
561
561
  error.type = cucumberResult.exception.type
562
562
  }
@@ -696,7 +696,7 @@ function publishRetriedAttempt (runner, state) {
696
696
 
697
697
  // ATR: record this attempt as failed so when run().finally runs (after retry) we have all statuses
698
698
  if (isFlakyTestRetriesEnabled) {
699
- const nameForKey = runner.pickle.name.replace(/\s*\(attempt \d+(?:, retried)?\)\s*$/, '')
699
+ const nameForKey = runner.pickle.name.replace(/ ?\(attempt \d+(?:, retried)?\) ?$/, '')
700
700
  const atrKey = `${runner.pickle.uri}:${nameForKey}`
701
701
  if (atrStatusesByScenarioKey.has(atrKey)) {
702
702
  atrStatusesByScenarioKey.get(atrKey).push('fail')
@@ -793,7 +793,7 @@ function wrapRun (pl, isLatestVersion, version) {
793
793
  testFnCh.runStores(ctx, () => {
794
794
  promise = run.apply(this, args)
795
795
  })
796
- promise.finally(async () => {
796
+ const finalize = async () => {
797
797
  if (!canAwaitRetries) {
798
798
  this.eventBroadcaster.removeListener('envelope', onEnvelope)
799
799
  }
@@ -889,7 +889,7 @@ function wrapRun (pl, isLatestVersion, version) {
889
889
  // ATR: accumulate statuses by stable scenario key (uri:name) so retries are grouped.
890
890
  // Cucumber appends " (attempt N)" or " (attempt N, retried)" to the scenario name; normalize for keying.
891
891
  if (isFlakyTestRetriesEnabled && !isAttemptToFix && !isEfdRetry && numTestRetries > 0) {
892
- const nameForKey = this.pickle.name.replace(/\s*\(attempt \d+(?:, retried)?\)\s*$/, '')
892
+ const nameForKey = this.pickle.name.replace(/ ?\(attempt \d+(?:, retried)?\) ?$/, '')
893
893
  const atrKey = `${this.pickle.uri}:${nameForKey}`
894
894
  if (atrStatusesByScenarioKey.has(atrKey)) {
895
895
  atrStatusesByScenarioKey.get(atrKey).push(status)
@@ -966,6 +966,9 @@ function wrapRun (pl, isLatestVersion, version) {
966
966
  ...attemptCtx.currentStore,
967
967
  finalStatus,
968
968
  })
969
+ }
970
+ promise.then(finalize, finalize).catch(error => {
971
+ log.error('Cucumber test finalization error', error)
969
972
  })
970
973
  return promise
971
974
  } catch (err) {
@@ -2,11 +2,21 @@
2
2
 
3
3
  const NM = 'node_modules/'
4
4
 
5
+ /**
6
+ * @typedef {object} PackageAndModulePath
7
+ * @property {string|null} pkg
8
+ * @property {string|null} path
9
+ * @property {string} [pkgJson]
10
+ */
11
+
5
12
  /**
6
13
  * For a given full path to a module,
7
14
  * return the package name it belongs to and the local path to the module
8
15
  * input: '/foo/node_modules/@co/stuff/foo/bar/baz.js'
9
16
  * output: { pkg: '@co/stuff', path: 'foo/bar/baz.js', pkgJson: '/foo/node_modules/@co/stuff/package.json' }
17
+ *
18
+ * @param {string} fullPath
19
+ * @returns {PackageAndModulePath}
10
20
  */
11
21
  module.exports = function extractPackageAndModulePath (fullPath) {
12
22
  const nm = fullPath.lastIndexOf(NM)
@@ -6,6 +6,7 @@ const iitm = require('../../../dd-trace/src/iitm')
6
6
  const ritm = require('../../../dd-trace/src/ritm')
7
7
  const log = require('../../../dd-trace/src/log')
8
8
  const requirePackageJson = require('../../../dd-trace/src/require-package-json')
9
+ const { isNodeBuiltinModuleName } = require('./shared-utils')
9
10
 
10
11
  /**
11
12
  * @param {string} moduleBaseDir
@@ -82,7 +83,7 @@ function Hook (modules, hookOptions, onrequire) {
82
83
  moduleVersion ||= getVersion(moduleBaseDir)
83
84
  } catch (error) {
84
85
  log.error('Error getting version for "%s": %s', moduleName, error.message, error)
85
- return
86
+ return moduleExports
86
87
  }
87
88
 
88
89
  if (
@@ -101,7 +102,23 @@ function Hook (modules, hookOptions, onrequire) {
101
102
  }
102
103
  }
103
104
 
104
- const newExports = wrappedOnrequire(moduleExports, moduleName, moduleBaseDir, moduleVersion, isIitm)
105
+ // A builtin's named ESM exports are a second view of what `default` already points at, and
106
+ // `patched` cannot relate them because it keys on the object the hook ran against.
107
+ let newExports
108
+ if (defaultWrapResult && isNodeBuiltinModuleName(moduleName)) {
109
+ // Accessor exports are read, not skipped: `replaceGetter` leaves Node 20's `fs.opendir`
110
+ // an accessor, and Node's ESM facade resolved every export before handing us this view.
111
+ for (const key of Object.keys(defaultWrapResult)) {
112
+ const wrapped = defaultWrapResult[key]
113
+ const existing = moduleExports[key]
114
+ if (existing !== wrapped && existing !== undefined) {
115
+ moduleExports[key] = wrapped
116
+ }
117
+ }
118
+ newExports = moduleExports
119
+ } else {
120
+ newExports = wrappedOnrequire(moduleExports, moduleName, moduleBaseDir, moduleVersion, isIitm)
121
+ }
105
122
 
106
123
  if (defaultWrapResult && defaultExportAliases) {
107
124
  newExports.default = defaultWrapResult
@@ -58,6 +58,8 @@ module.exports = {
58
58
  '@smithy/core': () => require('../aws-sdk'),
59
59
  '@smithy/smithy-client': () => require('../aws-sdk'),
60
60
  '@vitest/runner': { esmFirst: true, fn: () => require('../vitest') },
61
+ '@wdio/cli': { esmFirst: true, fn: () => require('../webdriverio') },
62
+ '@wdio/local-runner': { esmFirst: true, fn: () => require('../webdriverio') },
61
63
  aerospike: () => require('../aerospike'),
62
64
  ai: () => require('../ai'),
63
65
  amqp10: () => require('../amqp10'),
@@ -57,19 +57,23 @@ const alreadyLoggedIncompatibleIntegrations = new Set()
57
57
  // Always disable prefixed and unprefixed node modules if one is disabled.
58
58
  if (disabledInstrumentations.size) {
59
59
  const builtinsSet = new Set(builtinModules)
60
+ const disabledBuiltinCounterparts = []
60
61
  for (const name of disabledInstrumentations) {
61
62
  const hasPrefix = name.startsWith('node:')
62
63
  if (hasPrefix || builtinsSet.has(name)) {
63
64
  if (hasPrefix) {
64
65
  const unprefixedName = name.slice(5)
65
66
  if (!disabledInstrumentations.has(unprefixedName)) {
66
- disabledInstrumentations.add(unprefixedName)
67
+ disabledBuiltinCounterparts.push(unprefixedName)
67
68
  }
68
69
  } else if (!disabledInstrumentations.has(`node:${name}`)) {
69
- disabledInstrumentations.add(`node:${name}`)
70
+ disabledBuiltinCounterparts.push(`node:${name}`)
70
71
  }
71
72
  }
72
73
  }
74
+ for (const name of disabledBuiltinCounterparts) {
75
+ disabledInstrumentations.add(name)
76
+ }
73
77
  builtinsSet.clear()
74
78
  }
75
79
 
@@ -163,7 +167,9 @@ function logAbortedIntegrations () {
163
167
  for (const [nameVersion, success] of instrumentedIntegrationsSuccess) {
164
168
  // Only ever log a single version of an integration, even if it is loaded later.
165
169
  if (!success && !alreadyLoggedIncompatibleIntegrations.has(nameVersion)) {
166
- const [name, version] = nameVersion.split('@')
170
+ const lastAtPosition = nameVersion.lastIndexOf('@')
171
+ const name = nameVersion.slice(0, lastAtPosition)
172
+ const version = nameVersion.slice(lastAtPosition + 1)
167
173
  telemetry('abort.integration', [
168
174
  `integration:${name}`,
169
175
  `integration_version:${version}`,
@@ -39,6 +39,7 @@ for (const matcher of [matcherCjs, matcherEsm]) {
39
39
 
40
40
  // Keep the marker split: source-map scanners can read a contiguous token in
41
41
  // string literals as this file's own inline map.
42
+ // eslint-disable-next-line unicorn/no-useless-concat -- Keep the source-map marker non-contiguous.
42
43
  const SOURCE_MAP_PREFIX = '//# sourceMapping' + 'URL=data:application/json;base64,'
43
44
 
44
45
  function rewrite (content, filename, format) {
@@ -12,5 +12,6 @@ module.exports = [
12
12
  ...require('./modelcontextprotocol-sdk'),
13
13
  ...require('./openai-agents'),
14
14
  ...require('./playwright'),
15
+ ...require('./webdriverio'),
15
16
  ...require('./aws-durable-execution-sdk-js'),
16
17
  ]
@@ -0,0 +1,57 @@
1
+ 'use strict'
2
+
3
+ module.exports = [
4
+ {
5
+ module: {
6
+ name: '@wdio/cli',
7
+ versionRange: '>=9.0.0',
8
+ filePath: 'build/index.js',
9
+ },
10
+ functionQuery: {
11
+ className: 'Launcher',
12
+ methodName: '_startInstance',
13
+ kind: 'Async',
14
+ },
15
+ channelName: 'Launcher_startInstance',
16
+ },
17
+ {
18
+ module: {
19
+ name: '@wdio/local-runner',
20
+ versionRange: '>=9.0.0',
21
+ filePath: 'build/index.js',
22
+ },
23
+ functionQuery: {
24
+ className: 'LocalRunner',
25
+ methodName: 'run',
26
+ kind: 'Async',
27
+ },
28
+ channelName: 'LocalRunner_run',
29
+ },
30
+ {
31
+ module: {
32
+ name: '@wdio/local-runner',
33
+ versionRange: '>=9.0.0',
34
+ filePath: 'build/index.js',
35
+ },
36
+ functionQuery: {
37
+ className: 'LocalRunner',
38
+ methodName: 'shutdown',
39
+ kind: 'Async',
40
+ },
41
+ channelName: 'LocalRunner_shutdown',
42
+ },
43
+ {
44
+ module: {
45
+ name: '@wdio/local-runner',
46
+ versionRange: '>=9.0.0',
47
+ filePath: 'build/index.js',
48
+ },
49
+ astQuery: 'VariableDeclarator[id.name="LocalRunner"] > ClassExpression > ClassBody > ' +
50
+ 'MethodDefinition[key.name="shutdown"] ReturnStatement > ' +
51
+ 'CallExpression[callee.object.name="promise"][callee.property.name="then"], ' +
52
+ 'ClassDeclaration[id.name="LocalRunner"] > ClassBody > MethodDefinition[key.name="shutdown"] ReturnStatement > ' +
53
+ 'CallExpression[callee.object.name="promise"][callee.property.name="then"]',
54
+ channelName: 'LocalRunner_shutdown',
55
+ transform: 'waitForAsyncEnd',
56
+ },
57
+ ]
@@ -17,8 +17,8 @@ const { parse, query } = require('./compiler')
17
17
  module.exports = { waitForAsyncEnd }
18
18
 
19
19
  /**
20
- * Injects a wait for `ctx.asyncEndPromise` into a generated `tracePromise`
21
- * wrapper's native-Promise fulfillment handler.
20
+ * Injects settlement-specific asyncEnd waits into a generated `tracePromise`
21
+ * wrapper's native-Promise handlers.
22
22
  *
23
23
  * @param {object} _state
24
24
  * @param {import('estree').CallExpression} node
@@ -26,36 +26,68 @@ module.exports = { waitForAsyncEnd }
26
26
  */
27
27
  function waitForAsyncEnd (_state, node) {
28
28
  const onFulfilled = node.arguments[0]
29
- const statements = onFulfilled?.body?.body
29
+ const onRejected = node.arguments[1]
30
30
 
31
- if (!statements || query(onFulfilled.body, '[id.name=__apm$asyncEndPromise]').length > 0) {
31
+ if (!onFulfilled?.body || !onRejected?.body) {
32
32
  return
33
33
  }
34
34
 
35
- const returnIndex = statements.findIndex(statement =>
36
- statement.type === 'ReturnStatement' && statement.argument
35
+ injectAsyncEndCallbackWait(onFulfilled.body, 'ReturnStatement', 'resolveCallback')
36
+ injectAsyncEndCallbackWait(onRejected.body, 'ThrowStatement', 'rejectCallback')
37
+ }
38
+
39
+ /**
40
+ * Injects a settlement-specific callback wait before an exit.
41
+ *
42
+ * @param {import('estree').BlockStatement} body
43
+ * @param {'ReturnStatement'|'ThrowStatement'} exitType
44
+ * @param {'resolveCallback'|'rejectCallback'} callbackProperty
45
+ * @returns {void}
46
+ */
47
+ function injectAsyncEndCallbackWait (body, exitType, callbackProperty) {
48
+ const callbackVariable = `__apm$${callbackProperty}`
49
+ if (query(body, `[id.name=${callbackVariable}]`).length > 0) {
50
+ return
51
+ }
52
+
53
+ const exitIndex = body.body.findIndex(statement =>
54
+ statement.type === exitType && statement.argument
37
55
  )
38
56
 
39
- // The generated fulfillment handler always ends in a return; a miss means the
57
+ // The generated settlement handlers always end in a return or throw; a miss means the
40
58
  // upstream template changed and the caller's try/catch falls back to the
41
59
  // unwrapped source.
42
- assert(returnIndex !== -1, 'waitForAsyncEnd: no return statement to wait on')
60
+ assert(exitIndex !== -1, `waitForAsyncEnd: no ${exitType} to wait on`)
43
61
 
62
+ // This runs inside tracePromise's native-Promise settlement handler. The
63
+ // Promise adapts subscriber callback completion to that existing chain.
44
64
  const waitStatements = parse(`
45
65
  function wrapper () {
46
- const __apm$asyncEndPromise = __apm$ctx.asyncEndPromise;
47
- if (__apm$asyncEndPromise && typeof __apm$asyncEndPromise.then === 'function') {
48
- return __apm$asyncEndPromise.then(() => __apm$result, () => __apm$result);
66
+ const ${callbackVariable} = __apm$ctx.${callbackProperty};
67
+ if (typeof ${callbackVariable} === 'function') {
68
+ return new Promise(${callbackVariable}).then(() => __apm$result, () => __apm$result);
49
69
  }
50
70
  }
51
71
  `).body[0].body.body
52
72
 
53
- // Resolve to whatever the fulfillment handler returns (its return argument),
54
- // so a subscriber that reassigned `__apm$ctx.result` in `asyncEnd` still wins.
55
- const returnArgument = statements[returnIndex].argument
56
- const { arguments: onSettled } = waitStatements[1].consequent.body[0].argument
57
- onSettled[0].body = clone(returnArgument)
58
- onSettled[1].body = clone(returnArgument)
73
+ const exitArgument = body.body[exitIndex].argument
74
+ const callbackIf = waitStatements[1]
75
+ const { arguments: onCallbackSettled } = callbackIf.consequent.body[0].argument
76
+
77
+ if (exitType === 'ThrowStatement') {
78
+ for (const handler of onCallbackSettled) {
79
+ handler.body = {
80
+ type: 'BlockStatement',
81
+ body: [{
82
+ type: 'ThrowStatement',
83
+ argument: clone(exitArgument),
84
+ }],
85
+ }
86
+ }
87
+ } else {
88
+ onCallbackSettled[0].body = clone(exitArgument)
89
+ onCallbackSettled[1].body = clone(exitArgument)
90
+ }
59
91
 
60
- statements.splice(returnIndex, 0, ...waitStatements)
92
+ body.body.splice(exitIndex, 0, ...waitStatements)
61
93
  }
@@ -88,7 +88,7 @@ function collectRoutesFromRouter (router, prefix) {
88
88
  function normalizeRoutePaths (path) {
89
89
  if (path == null) return []
90
90
 
91
- if (Array.isArray(path) === false) {
91
+ if (!Array.isArray(path)) {
92
92
  const normalized = normalizeRoutePath(path)
93
93
  return [normalized]
94
94
  }
@@ -1,9 +1,48 @@
1
1
  'use strict'
2
2
 
3
+ const { builtinModules } = require('node:module')
4
+
5
+ /**
6
+ * @param {string} moduleName
7
+ */
8
+ function stripNodePrefix (moduleName) {
9
+ return moduleName.startsWith('node:') ? moduleName.slice(5) : moduleName
10
+ }
11
+
12
+ const builtinModuleNames = new Set(builtinModules.map(stripNodePrefix))
13
+
14
+ /**
15
+ * @param {string} moduleName
16
+ */
17
+ function isNodeBuiltinModuleName (moduleName) {
18
+ return builtinModuleNames.has(stripNodePrefix(moduleName))
19
+ }
20
+
21
+ /**
22
+ * @param {string} moduleName
23
+ */
24
+ function isBuiltinModuleName (moduleName) {
25
+ return moduleName === 'electron' || isNodeBuiltinModuleName(moduleName)
26
+ }
27
+
28
+ /**
29
+ * @param {string} moduleName
30
+ */
31
+ function normalizeModuleName (moduleName) {
32
+ const stripped = stripNodePrefix(moduleName)
33
+ return builtinModuleNames.has(stripped) ? stripped : moduleName
34
+ }
35
+
36
+ /**
37
+ * @param {string} moduleName
38
+ */
3
39
  function isRelativeRequire (moduleName) {
4
40
  return moduleName.startsWith('./') || moduleName.startsWith('../')
5
41
  }
6
42
 
7
43
  module.exports = {
44
+ isBuiltinModuleName,
45
+ isNodeBuiltinModuleName,
8
46
  isRelativeRequire,
47
+ normalizeModuleName,
9
48
  }
@@ -89,9 +89,7 @@ function setupResponseInstrumentation (ctx, res) {
89
89
  const collectChunk = chunk => {
90
90
  if (!bodyChunks || !chunk) return
91
91
 
92
- if (typeof chunk === 'string') {
93
- bodyChunks.push(chunk)
94
- } else if (Buffer.isBuffer(chunk)) {
92
+ if (typeof chunk === 'string' || Buffer.isBuffer(chunk)) {
95
93
  bodyChunks.push(chunk)
96
94
  } else {
97
95
  // Handle Uint8Array or other array-like types
@@ -96,7 +96,7 @@ const DD_JEST_HANDLE_TEST_EVENT_WRAPPED = Symbol('dd-trace:jest:handle-test-even
96
96
  const DD_JEST_HANDLE_TEST_EVENT_DATADOG = Symbol('dd-trace:jest:handle-test-event-datadog')
97
97
  const DD_JEST_CONCURRENT_TEST_ORIGINAL = Symbol('dd-trace:jest:concurrent-test-original')
98
98
  const isJestWorker = !!getEnvironmentVariable('JEST_WORKER_ID')
99
- const jestSessionState = globalThis[JEST_SESSION_STATE] || (globalThis[JEST_SESSION_STATE] = {})
99
+ const jestSessionState = (globalThis[JEST_SESSION_STATE] ||= {})
100
100
 
101
101
  // https://github.com/jestjs/jest/blob/41f842a46bb2691f828c3a5f27fc1d6290495b82/packages/jest-circus/src/types.ts#L9C8-L9C54
102
102
  const RETRY_TIMES = Symbol.for('RETRY_TIMES')
@@ -1192,9 +1192,9 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
1192
1192
  ctx.testParameters = testParameters
1193
1193
  ctx.frameworkVersion = jestVersion
1194
1194
  ctx.isNew = isNewTest
1195
- ctx.isEfdRetry = ctx.isEfdRetry || numEfdRetry > 0
1195
+ ctx.isEfdRetry ||= numEfdRetry > 0
1196
1196
  ctx.isAttemptToFix = isAttemptToFix
1197
- ctx.isAttemptToFixRetry = ctx.isAttemptToFixRetry || numOfAttemptsToFixRetries > 0
1197
+ ctx.isAttemptToFixRetry ||= numOfAttemptsToFixRetries > 0
1198
1198
  ctx.isJestRetry = isJestRetry
1199
1199
  ctx.isDisabled = isDisabled
1200
1200
  ctx.isQuarantined = isQuarantined
@@ -1361,8 +1361,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
1361
1361
  }
1362
1362
  }
1363
1363
  }
1364
- }
1365
- if (event.name === 'test_done') {
1364
+ } else if (event.name === 'test_done') {
1366
1365
  const originalError = event.test?.errors?.[0]
1367
1366
  let status = 'pass'
1368
1367
  if (event.test.errors && event.test.errors.length) {
@@ -1565,8 +1564,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
1565
1564
  if (ctx.concurrentTestState) {
1566
1565
  ctx.currentStore = undefined
1567
1566
  }
1568
- }
1569
- if (event.name === 'run_finish') {
1567
+ } else if (event.name === 'run_finish') {
1570
1568
  for (const [test, errors] of atrSuppressedErrors) {
1571
1569
  // Do not restore errors for non-ATF quarantined tests — they should stay suppressed
1572
1570
  // so Jest doesn't see the failure (prevents --bail from stopping the run).
@@ -1595,8 +1593,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
1595
1593
  attemptToFixRetriedTestsStatuses.clear()
1596
1594
  testsToBeRetried.clear()
1597
1595
  testSuiteDatadogEnvironments.delete(this.testSuiteAbsolutePath)
1598
- }
1599
- if (event.name === 'test_skip' || event.name === 'test_todo') {
1596
+ } else if (event.name === 'test_skip' || event.name === 'test_todo') {
1600
1597
  const testName = getJestTestName(event.test)
1601
1598
  testSkippedCh.publish({
1602
1599
  test: {
@@ -2298,12 +2295,11 @@ function getCliWrapper (isNewJestVersion) {
2298
2295
  } = skippableSuitesResponse || await getChannelPromise(skippableSuitesCh)
2299
2296
  if (err) {
2300
2297
  skippableSuitesCoverage = {}
2301
- skippedSuitesCoverage = {}
2302
2298
  } else {
2303
2299
  skippableSuites = receivedSkippableSuites
2304
2300
  skippableSuitesCoverage = receivedSkippableSuitesCoverage || {}
2305
- skippedSuitesCoverage = {}
2306
2301
  }
2302
+ skippedSuitesCoverage = {}
2307
2303
  } catch (err) {
2308
2304
  log.error('Jest test-suite skippable error', err)
2309
2305
  }
@@ -3037,9 +3033,7 @@ function wrapJestObject (jestObject, suiteFilePath) {
3037
3033
 
3038
3034
  shimmer.wrap(jestObject, 'mock', mock => function (moduleName) {
3039
3035
  // If the library is mocked with `jest.mock`, we don't want to bypass jest's own require engine
3040
- if (LIBRARIES_BYPASSING_JEST_REQUIRE_ENGINE.has(moduleName)) {
3041
- LIBRARIES_BYPASSING_JEST_REQUIRE_ENGINE.delete(moduleName)
3042
- }
3036
+ LIBRARIES_BYPASSING_JEST_REQUIRE_ENGINE.delete(moduleName)
3043
3037
  recordMockedFile(suiteFilePath, moduleName)
3044
3038
  return mock.apply(this, arguments)
3045
3039
  })
@@ -41,11 +41,9 @@ addHook({
41
41
  }, (Suite) => {
42
42
  shimmer.wrap(Suite.prototype, 'addTest', addTest => function (test) {
43
43
  const callSites = getCallSites()
44
- let startLine
45
44
  const testCallSite = callSites.find(site => site.getFileName() === test.file)
46
45
  if (testCallSite) {
47
- startLine = testCallSite.getLineNumber()
48
- testToStartLine.set(test, startLine)
46
+ testToStartLine.set(test, testCallSite.getLineNumber())
49
47
  }
50
48
  return addTest.apply(this, arguments)
51
49
  })