dd-trace 6.8.0 → 6.10.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 (209) hide show
  1. package/ci/diagnose.js +27 -25
  2. package/ci/init.js +6 -6
  3. package/ci/runbook.md +114 -20
  4. package/ci/test-optimization-validation/approval-artifacts.js +36 -3
  5. package/ci/test-optimization-validation/approval.js +78 -22
  6. package/ci/test-optimization-validation/blocker-category.js +24 -0
  7. package/ci/test-optimization-validation/ci-discovery.js +23 -17
  8. package/ci/test-optimization-validation/ci-package-scripts.js +34 -6
  9. package/ci/test-optimization-validation/ci-remediation.js +1 -1
  10. package/ci/test-optimization-validation/cli.js +280 -52
  11. package/ci/test-optimization-validation/command-blocker.js +159 -20
  12. package/ci/test-optimization-validation/command-output-policy.js +9 -3
  13. package/ci/test-optimization-validation/command-runner.js +9 -7
  14. package/ci/test-optimization-validation/environment.js +4 -2
  15. package/ci/test-optimization-validation/executable.js +15 -13
  16. package/ci/test-optimization-validation/execution-lock.js +70 -0
  17. package/ci/test-optimization-validation/framework-adapters/cucumber-validation.json +3 -0
  18. package/ci/test-optimization-validation/framework-adapters/cucumber.js +657 -5
  19. package/ci/test-optimization-validation/framework-adapters/vitest.js +517 -0
  20. package/ci/test-optimization-validation/generated-files.js +38 -28
  21. package/ci/test-optimization-validation/generated-test-contract.js +8 -4
  22. package/ci/test-optimization-validation/generated-verifier.js +24 -17
  23. package/ci/test-optimization-validation/literal-glob.js +52 -0
  24. package/ci/test-optimization-validation/manifest-loader.js +3 -0
  25. package/ci/test-optimization-validation/manifest-scaffold.js +300 -50
  26. package/ci/test-optimization-validation/manifest-schema.js +80 -17
  27. package/ci/test-optimization-validation/offline-fixtures.js +13 -4
  28. package/ci/test-optimization-validation/package-check.js +94 -0
  29. package/ci/test-optimization-validation/plan-writer.js +183 -10
  30. package/ci/test-optimization-validation/preflight-runner.js +110 -31
  31. package/ci/test-optimization-validation/project-build-artifact.js +31 -0
  32. package/ci/test-optimization-validation/redaction.js +18 -27
  33. package/ci/test-optimization-validation/report-writer.js +182 -48
  34. package/ci/test-optimization-validation/result-semantics.js +20 -3
  35. package/ci/test-optimization-validation/runner-command.js +40 -20
  36. package/ci/test-optimization-validation/runner-contract.js +79 -43
  37. package/ci/test-optimization-validation/scenarios/basic-reporting.js +59 -5
  38. package/ci/test-optimization-validation/scenarios/ci-wiring.js +141 -35
  39. package/ci/test-optimization-validation/scenarios/helpers.js +30 -6
  40. package/ci/test-optimization-validation/static-diagnosis.js +10 -3
  41. package/ci/test-optimization-validation/test-output.js +2 -1
  42. package/ci/test-optimization-validation/validation-blocker.js +21 -0
  43. package/ci/vitest-no-worker-init-setup.mjs +140 -42
  44. package/index.d.ts +140 -1
  45. package/init.js +1 -17
  46. package/initialize.mjs +11 -0
  47. package/loader-hook.mjs +6 -4
  48. package/package.json +8 -5
  49. package/packages/datadog-core/src/utils/src/parse-tags.js +0 -1
  50. package/packages/datadog-esbuild/src/utils.js +16 -1
  51. package/packages/datadog-instrumentations/src/ai.js +61 -34
  52. package/packages/datadog-instrumentations/src/bluebird.js +6 -6
  53. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +81 -5
  54. package/packages/datadog-instrumentations/src/confluentinc-kafka-javascript.js +111 -6
  55. package/packages/datadog-instrumentations/src/cucumber.js +124 -47
  56. package/packages/datadog-instrumentations/src/dns.js +21 -2
  57. package/packages/datadog-instrumentations/src/electron.js +1 -0
  58. package/packages/datadog-instrumentations/src/fastify.js +3 -1
  59. package/packages/datadog-instrumentations/src/grpc/client.js +4 -2
  60. package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +3 -1
  61. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  62. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +629 -0
  63. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +34 -9
  64. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +199 -0
  65. package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +20 -2
  66. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.js +32 -0
  67. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +62 -0
  68. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +83 -1
  69. package/packages/datadog-instrumentations/src/helpers/router-helper.js +37 -6
  70. package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +1 -1
  71. package/packages/datadog-instrumentations/src/jest.js +980 -234
  72. package/packages/datadog-instrumentations/src/mocha/main.js +108 -10
  73. package/packages/datadog-instrumentations/src/mocha/utils.js +66 -50
  74. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +25 -0
  75. package/packages/datadog-instrumentations/src/mocha/worker.js +12 -18
  76. package/packages/datadog-instrumentations/src/mysql.js +46 -16
  77. package/packages/datadog-instrumentations/src/mysql2.js +119 -18
  78. package/packages/datadog-instrumentations/src/next.js +155 -23
  79. package/packages/datadog-instrumentations/src/openai.js +14 -0
  80. package/packages/datadog-instrumentations/src/path-to-regexp.js +67 -1
  81. package/packages/datadog-instrumentations/src/pg.js +209 -11
  82. package/packages/datadog-instrumentations/src/playwright.js +173 -79
  83. package/packages/datadog-instrumentations/src/promise-js.js +4 -4
  84. package/packages/datadog-instrumentations/src/promise.js +3 -3
  85. package/packages/datadog-instrumentations/src/router.js +195 -19
  86. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +85 -13
  87. package/packages/datadog-instrumentations/src/vitest-main.js +528 -72
  88. package/packages/datadog-instrumentations/src/vitest-util.js +106 -12
  89. package/packages/datadog-instrumentations/src/vitest-worker.js +382 -40
  90. package/packages/datadog-instrumentations/src/webdriverio.js +212 -22
  91. package/packages/datadog-instrumentations/src/when.js +3 -3
  92. package/packages/datadog-instrumentations/src/ws.js +5 -1
  93. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/trace-checkpoint.js +12 -6
  94. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +59 -48
  95. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +2 -5
  96. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -5
  97. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +1 -3
  98. package/packages/datadog-plugin-cucumber/src/index.js +5 -1
  99. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +51 -47
  100. package/packages/datadog-plugin-cypress/src/support.js +7 -4
  101. package/packages/datadog-plugin-electron/src/net.js +10 -4
  102. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +31 -17
  103. package/packages/datadog-plugin-http/src/client.js +1 -1
  104. package/packages/datadog-plugin-http2/src/client.js +1 -1
  105. package/packages/datadog-plugin-jest/src/index.js +6 -22
  106. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +2 -3
  107. package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -3
  108. package/packages/datadog-plugin-kafkajs/src/producer.js +5 -3
  109. package/packages/datadog-plugin-kafkajs/src/utils.js +65 -7
  110. package/packages/datadog-plugin-mocha/src/index.js +832 -4
  111. package/packages/datadog-plugin-mysql/src/index.js +37 -0
  112. package/packages/datadog-plugin-next/src/index.js +51 -21
  113. package/packages/datadog-plugin-openai/src/tracing.js +4 -0
  114. package/packages/datadog-plugin-openai-agents/src/integration.js +278 -69
  115. package/packages/datadog-plugin-openai-agents/src/processor.js +16 -4
  116. package/packages/datadog-plugin-pg/src/index.js +66 -1
  117. package/packages/datadog-plugin-playwright/src/index.js +1 -1
  118. package/packages/datadog-plugin-vitest/src/index.js +103 -27
  119. package/packages/datadog-plugin-ws/src/util.js +2 -1
  120. package/packages/datadog-shimmer/src/shimmer.js +6 -2
  121. package/packages/dd-trace/src/aiguard/sdk.js +3 -0
  122. package/packages/dd-trace/src/appsec/api_security/normalized-route.js +453 -0
  123. package/packages/dd-trace/src/appsec/{blocking.js → blocking/index.js} +26 -6
  124. package/packages/dd-trace/src/appsec/handlers/auth.js +62 -0
  125. package/packages/dd-trace/src/appsec/handlers/http-request.js +228 -0
  126. package/packages/dd-trace/src/appsec/handlers/http-response.js +78 -0
  127. package/packages/dd-trace/src/appsec/handlers/http-shared.js +24 -0
  128. package/packages/dd-trace/src/appsec/handlers/payments.js +104 -0
  129. package/packages/dd-trace/src/appsec/iast/analyzers/hsts-header-missing-analyzer.js +1 -1
  130. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
  131. package/packages/dd-trace/src/appsec/index.js +48 -476
  132. package/packages/dd-trace/src/appsec/sdk/track_event.js +9 -0
  133. package/packages/dd-trace/src/carrier.js +356 -0
  134. package/packages/dd-trace/src/ci-visibility/coverage-report-discovery.js +46 -14
  135. package/packages/dd-trace/src/ci-visibility/efd-retry-policy.js +89 -0
  136. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +27 -13
  137. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +17 -0
  138. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +21 -1
  139. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +22 -7
  140. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +9 -14
  141. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +21 -5
  142. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +1 -1
  143. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +5 -1
  144. package/packages/dd-trace/src/config/generated-config-types.d.ts +5 -0
  145. package/packages/dd-trace/src/config/helper.js +2 -0
  146. package/packages/dd-trace/src/config/index.js +16 -5
  147. package/packages/dd-trace/src/config/remote_config.js +1 -1
  148. package/packages/dd-trace/src/config/supported-configurations.json +18 -0
  149. package/packages/dd-trace/src/datastreams/context.js +6 -1
  150. package/packages/dd-trace/src/datastreams/manager.js +5 -1
  151. package/packages/dd-trace/src/datastreams/pathway.js +23 -22
  152. package/packages/dd-trace/src/datastreams/processor.js +3 -2
  153. package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -1
  154. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +0 -1
  155. package/packages/dd-trace/src/guardrails/apply-pm2-env.js +16 -0
  156. package/packages/dd-trace/src/guardrails/log.js +1 -10
  157. package/packages/dd-trace/src/llmobs/constants/tags.js +5 -0
  158. package/packages/dd-trace/src/llmobs/eval-metric.js +249 -0
  159. package/packages/dd-trace/src/llmobs/experiments/index.js +33 -25
  160. package/packages/dd-trace/src/llmobs/experiments/util.js +4 -2
  161. package/packages/dd-trace/src/llmobs/index.js +34 -2
  162. package/packages/dd-trace/src/llmobs/noop.js +2 -0
  163. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +1 -1
  164. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -1
  165. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +5 -5
  166. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +1 -1
  167. package/packages/dd-trace/src/llmobs/sdk.js +149 -68
  168. package/packages/dd-trace/src/llmobs/span_processor.js +16 -0
  169. package/packages/dd-trace/src/llmobs/tagger.js +96 -0
  170. package/packages/dd-trace/src/llmobs/telemetry.js +18 -0
  171. package/packages/dd-trace/src/llmobs/util.js +78 -0
  172. package/packages/dd-trace/src/llmobs/writers/base.js +3 -0
  173. package/packages/dd-trace/src/llmobs/writers/util.js +2 -0
  174. package/packages/dd-trace/src/log/channels.js +1 -9
  175. package/packages/dd-trace/src/log/levels.js +12 -0
  176. package/packages/dd-trace/src/openfeature/writers/exposures.js +1 -1
  177. package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
  178. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +1 -1
  179. package/packages/dd-trace/src/opentelemetry/metrics/meter.js +8 -4
  180. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -1
  181. package/packages/dd-trace/src/opentelemetry/span.js +4 -0
  182. package/packages/dd-trace/src/opentracing/propagation/text_map.js +126 -120
  183. package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +4 -8
  184. package/packages/dd-trace/src/plugin_manager.js +2 -0
  185. package/packages/dd-trace/src/plugins/ci_plugin.js +21 -5
  186. package/packages/dd-trace/src/plugins/index.js +1 -0
  187. package/packages/dd-trace/src/plugins/util/ci.js +6 -0
  188. package/packages/dd-trace/src/plugins/util/git.js +6 -4
  189. package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +2 -2
  190. package/packages/dd-trace/src/plugins/util/ip_extractor.js +1 -1
  191. package/packages/{datadog-plugin-jest/src/util.js → dd-trace/src/plugins/util/jest.js} +3 -62
  192. package/packages/dd-trace/src/plugins/util/stacktrace.js +3 -0
  193. package/packages/dd-trace/src/plugins/util/test.js +397 -74
  194. package/packages/dd-trace/src/plugins/util/url.js +1 -1
  195. package/packages/dd-trace/src/plugins/util/web.js +76 -3
  196. package/packages/dd-trace/src/profiler.js +1 -1
  197. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +1 -1
  198. package/packages/dd-trace/src/profiling/profilers/events.js +2 -2
  199. package/packages/dd-trace/src/proxy.js +1 -0
  200. package/packages/dd-trace/src/ritm.js +5 -0
  201. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +4 -2
  202. package/packages/dd-trace/src/sampling_rule.js +4 -2
  203. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +1 -1
  204. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +1 -1
  205. package/packages/dd-trace/src/telemetry/logs/index.js +3 -0
  206. package/packages/dd-trace/src/telemetry/telemetry.js +1 -1
  207. package/vendor/dist/pprof-format/index.js +1 -1
  208. package/version.js +10 -8
  209. /package/packages/dd-trace/src/appsec/{blocked_templates.js → blocking/templates.js} +0 -0
@@ -4,6 +4,7 @@ const fs = require('node:fs')
4
4
  const path = require('path')
5
5
 
6
6
  const { getArtifactId } = require('../artifact-id')
7
+ const { BLOCKER_CATEGORIES } = require('../blocker-category')
7
8
  const { buildDatadogEnv, buildOfflineCaptureEnv, runCommand } = require('../command-runner')
8
9
  const {
9
10
  cleanupGeneratedRuntimeFiles,
@@ -19,8 +20,10 @@ const { readOfflineOutput } = require('../offline-output')
19
20
  const { sanitizeForReport, sanitizeString } = require('../redaction')
20
21
  const { getGeneratedCommand } = require('../runner-command')
21
22
  const { ensureSafeDirectory, writeFileSafely } = require('../safe-files')
23
+ const { getValidationBlockerEvidence } = require('../validation-blocker')
22
24
 
23
- const ANSI_PATTERN = new RegExp(`${String.fromCharCode(27)}${String.raw`\[[0-?]*[ -/]*[@-~]`}`, 'g')
25
+ const ANSI_PATTERN =
26
+ new RegExp(`${String.fromCharCode(27)}${String.raw`\[[\u0030-\u003F]*[\u0020-\u002F]*[\u0040-\u007E]`}`, 'g')
24
27
 
25
28
  function frameworkOutDir (out, framework, scenario) {
26
29
  return path.join(out, 'runs', getArtifactId(framework.id), scenario)
@@ -193,10 +196,11 @@ async function prepareGeneratedScenario (framework, scenarioId) {
193
196
  function requireGeneratedScenario (framework, scenarioId, scenarioName) {
194
197
  const strategy = framework.generatedTestStrategy
195
198
  if (strategy?.status === 'not_possible') {
196
- return skip(
199
+ return incomplete(
197
200
  framework,
198
201
  scenarioName,
199
- `Skipped because this advanced feature is not eligible: ${strategy.reason}`,
202
+ `The validator cannot create a collectible test for this advanced feature: ${strategy.reason} ` +
203
+ 'No conclusion was reached for this advanced feature.',
200
204
  getGeneratedStrategySkipEvidence(framework, scenarioName, scenarioId)
201
205
  )
202
206
  }
@@ -254,9 +258,9 @@ function getGeneratedStrategySkipEvidence (framework, scenarioName, scenarioId)
254
258
  reasonCode = 'generated-test-strategy-not-verified'
255
259
  }
256
260
 
257
- return {
261
+ const evidence = {
258
262
  featureEligibility: {
259
- eligible: false,
263
+ ...(reasonCode === 'generated-test-strategy-not-possible' ? {} : { eligible: false }),
260
264
  blockedBy: 'generated-test-strategy',
261
265
  reason: strategy?.reason,
262
266
  reasonCode,
@@ -265,6 +269,12 @@ function getGeneratedStrategySkipEvidence (framework, scenarioName, scenarioId)
265
269
  requiredGeneratedScenario: scenarioId,
266
270
  },
267
271
  }
272
+ if (reasonCode === 'generated-test-strategy-not-possible') {
273
+ evidence.blockerCategory = BLOCKER_CATEGORIES.VALIDATOR_LIMITATION
274
+ evidence.recommendation = 'Report that the selected runner configuration cannot collect validator-generated ' +
275
+ 'tests to validator engineering. Basic Reporting remains valid; project setup changes are not required.'
276
+ }
277
+ return evidence
268
278
  }
269
279
 
270
280
  function basicEventEvidence (events) {
@@ -442,6 +452,8 @@ function copy (target, source, key) {
442
452
 
443
453
  function summarizeDebugRerun ({ result, events, offline, outDir }) {
444
454
  const output = `${result.stdout}\n${result.stderr}`
455
+ const testEvents = eventsOfType(events, 'test')
456
+ const sourceFiles = new Set(testEvents.map(test => test.testSourceFile).filter(Boolean))
445
457
 
446
458
  return {
447
459
  ran: true,
@@ -449,8 +461,12 @@ function summarizeDebugRerun ({ result, events, offline, outDir }) {
449
461
  commandTimedOut: result.timedOut,
450
462
  debugCommandFailed: result.exitCode !== 0 || result.timedOut === true,
451
463
  offlineExporterInitialized: offline.initialized,
464
+ settingsLoadedFromCache: offline.inputs.settings?.status === 'loaded',
452
465
  artifactDirectory: outDir,
453
466
  ...basicEventEvidence(events),
467
+ testEventsWithoutSourceFile: testEvents.length - testEvents.filter(test => test.testSourceFile).length,
468
+ testSourceFileCount: sourceFiles.size,
469
+ testSourceFiles: [...sourceFiles].slice(0, 5),
454
470
  debugLines: findInterestingLines(output, [
455
471
  /dd-trace/i,
456
472
  /test optimization/i,
@@ -541,7 +557,15 @@ function inconclusive (framework, scenario, diagnosis, evidence = {}, outDir, ex
541
557
  }
542
558
 
543
559
  function error (framework, scenario, err, outDir = err?.artifactDirectory) {
544
- return result(framework, scenario, 'error', err && err.stack ? err.stack : String(err), {}, outDir)
560
+ const blockerEvidence = getValidationBlockerEvidence(err)
561
+ return result(
562
+ framework,
563
+ scenario,
564
+ blockerEvidence ? 'blocked' : 'error',
565
+ blockerEvidence ? err.message : (err && err.stack ? err.stack : String(err)),
566
+ blockerEvidence || {},
567
+ outDir
568
+ )
545
569
  }
546
570
 
547
571
  function result (framework, scenario, status, diagnosis, evidence, outDir, extraArtifacts) {
@@ -8,6 +8,7 @@ const {
8
8
  getFrameworkDefinitions,
9
9
  runDiagnosis,
10
10
  } = require('../diagnose')
11
+ const { BLOCKER_CATEGORIES } = require('./blocker-category')
11
12
  const { sanitizeForReport } = require('./redaction')
12
13
  const { writeFileSafely } = require('./safe-files')
13
14
 
@@ -45,6 +46,7 @@ function runStaticDiagnosis ({ manifest, out }) {
45
46
  function getStaticBlocker (framework, diagnosis) {
46
47
  if (!SUPPORTED_FRAMEWORKS.has(framework.framework)) {
47
48
  return {
49
+ blockerCategory: BLOCKER_CATEGORIES.VALIDATOR_LIMITATION,
48
50
  reason: `Unsupported test framework detected: ${getUnsupportedFrameworkName(framework)}.`,
49
51
  recommendation: 'Choose Jest, Mocha, Cucumber, Cypress, Playwright, or Vitest for live validation.',
50
52
  }
@@ -56,6 +58,7 @@ function getStaticBlocker (framework, diagnosis) {
56
58
  const version = parseVersion(framework.frameworkVersion)
57
59
  if (version && !satisfies(version, definition.supportedRange)) {
58
60
  return {
61
+ blockerCategory: BLOCKER_CATEGORIES.UNSUPPORTED_VERSION,
59
62
  reason:
60
63
  `${definition.name} ${version} is not supported. Supported range is ${definition.supportedRange}.`,
61
64
  recommendation: definition.recommendation,
@@ -65,6 +68,7 @@ function getStaticBlocker (framework, diagnosis) {
65
68
  const staticVersionError = findStaticVersionError(definition, diagnosis, framework, version)
66
69
  if (staticVersionError) {
67
70
  return {
71
+ blockerCategory: BLOCKER_CATEGORIES.UNSUPPORTED_VERSION,
68
72
  reason: staticVersionError.title,
69
73
  recommendation: staticVersionError.recommendation || staticVersionError.message,
70
74
  }
@@ -117,8 +121,11 @@ function getExactFrameworkLocations (diagnosis, framework) {
117
121
  const locations = new Set()
118
122
  addRelativeFrameworkLocation(locations, diagnosis, framework.project?.packageJson)
119
123
 
120
- for (const configFile of framework.project?.configFiles || []) {
121
- addRelativeFrameworkLocation(locations, diagnosis, configFile)
124
+ const configFiles = framework.project?.configFiles
125
+ if (configFiles) {
126
+ for (const configFile of configFiles) {
127
+ addRelativeFrameworkLocation(locations, diagnosis, configFile)
128
+ }
122
129
  }
123
130
 
124
131
  return locations
@@ -149,7 +156,7 @@ function normalizeRelativePath (location) {
149
156
 
150
157
  function parseVersion (rawVersion) {
151
158
  if (typeof rawVersion !== 'string') return null
152
- const match = rawVersion.match(/\d+\.\d+\.\d+/)
159
+ const match = rawVersion.match(/(?<!\d)\d+\.\d+\.\d+/)
153
160
  return match ? match[0] : null
154
161
  }
155
162
 
@@ -4,7 +4,8 @@ const cypressAdapter = require('./framework-adapters/cypress')
4
4
  const cucumberAdapter = require('./framework-adapters/cucumber')
5
5
  const playwrightAdapter = require('./framework-adapters/playwright')
6
6
 
7
- const ANSI_PATTERN = new RegExp(`${String.fromCharCode(27)}${String.raw`\[[0-?]*[ -/]*[@-~]`}`, 'g')
7
+ const ANSI_PATTERN =
8
+ new RegExp(`${String.fromCharCode(27)}${String.raw`\[[\u0030-\u003F]*[\u0020-\u002F]*[\u0040-\u007E]`}`, 'g')
8
9
 
9
10
  /**
10
11
  * Extracts the final test count from common JavaScript test-runner summaries.
@@ -0,0 +1,21 @@
1
+ 'use strict'
2
+
3
+ function createValidationBlocker (message, { kind, recommendation, suppressReport = false }) {
4
+ const error = new Error(message)
5
+ error.validationBlocker = { kind, recommendation }
6
+ error.validationExitCode = 2
7
+ error.suppressReport = suppressReport
8
+ return error
9
+ }
10
+
11
+ function getValidationBlockerEvidence (error) {
12
+ if (!error?.validationBlocker) return
13
+ return {
14
+ blockerKind: error.validationBlocker.kind,
15
+ domain: 'validator_state',
16
+ recommendation: error.validationBlocker.recommendation,
17
+ validationIncomplete: true,
18
+ }
19
+ }
20
+
21
+ module.exports = { createValidationBlocker, getValidationBlockerEvidence }
@@ -3,17 +3,19 @@ import { afterEach, beforeAll, beforeEach, inject } from 'vitest'
3
3
  // Instrumentation-less setup for DD_EXPERIMENTAL_TEST_OPT_VITEST_NO_WORKER_INIT.
4
4
  // It applies Test Optimization execution changes without initializing dd-trace and also supports Browser Mode.
5
5
  const VITEST_NO_WORKER_INIT_ACTIVE_ENV = 'DD_TEST_OPT_VITEST_NO_WORKER_INIT_ACTIVE'
6
+ const SERIALIZED_CONTEXT_PREFIX = '\u0000dd-vitest-context:'
6
7
  const providedContext = getProvidedContext()
7
8
  const isNoWorkerInitActive = providedContext.isActive ?? getIsNoWorkerInitActive()
8
9
  const attemptToFixTests = providedContext.attemptToFixTests || {}
9
10
  const attemptToFixRetries = providedContext.attemptToFixRetries || 0
10
11
  const disabledTests = providedContext.disabledTests || {}
11
- const earlyFlakeDetectionRetries = providedContext.earlyFlakeDetectionRetries || 0
12
- const earlyFlakeDetectionRetryThresholds = Array.isArray(providedContext.earlyFlakeDetectionRetryThresholds)
13
- ? providedContext.earlyFlakeDetectionRetryThresholds
14
- : []
15
- const earlyFlakeDetectionSlowRetries = providedContext.earlyFlakeDetectionSlowRetries || {}
16
- const hasEarlyFlakeDetectionSlowRetries = Object.keys(earlyFlakeDetectionSlowRetries).length > 0
12
+ const efdSuiteAdmissionBrowserCommand = providedContext.efdSuiteAdmissionBrowserCommand
13
+ const earlyFlakeDetectionRetryPolicy = providedContext.earlyFlakeDetectionRetryPolicy || {
14
+ durationRetryCounts: [],
15
+ schedulingRetryCount: 0,
16
+ }
17
+ const earlyFlakeDetectionRetries = earlyFlakeDetectionRetryPolicy.schedulingRetryCount
18
+ const isEfdSuiteAdmissionEnabled = providedContext.isEfdSuiteAdmissionEnabled === true
17
19
  const isEarlyFlakeDetectionEnabled = providedContext.isEarlyFlakeDetectionEnabled === true
18
20
  const knownTests = providedContext.knownTests || {}
19
21
  const modifiedFiles = providedContext.modifiedFiles || {}
@@ -35,6 +37,7 @@ const earlyFlakeDetectionStartByTask = new WeakMap()
35
37
  const nextAttemptIndexByTask = new WeakMap()
36
38
  const retryAttemptIndexByTask = new WeakMap()
37
39
  const usedRumTestExecutionIds = new Set()
40
+ let browserCommands
38
41
  let now
39
42
  let timeOrigin
40
43
  // Use an unfaked monotonic clock in Node and Vitest's parent frame in Browser Mode.
@@ -49,18 +52,42 @@ if (typeof globalThis.process?.uptime === 'function') {
49
52
  timeOrigin = Number.isFinite(clock?.timeOrigin) ? clock.timeOrigin : Date.now() - now()
50
53
  }
51
54
 
55
+ /**
56
+ * Requests permission from the Vitest main process to schedule Browser Mode EFD retries for one suite.
57
+ *
58
+ * @param {string} testSuite
59
+ * @param {boolean} hasNewTest
60
+ * @returns {Promise<boolean>}
61
+ */
62
+ async function requestBrowserEfdSuiteAdmission (testSuite, hasNewTest) {
63
+ try {
64
+ if (!browserCommands) {
65
+ const vitestBrowser = await import('@vitest/browser/context')
66
+ browserCommands = vitestBrowser.commands
67
+ }
68
+ return await browserCommands[efdSuiteAdmissionBrowserCommand](testSuite, hasNewTest) === true
69
+ } catch (error) {
70
+ // Browser Mode setup runs without dd-trace, so the tracer logger is unavailable.
71
+ globalThis.console?.error('Datadog Test Optimization could not request Vitest EFD suite admission.', error)
72
+ return false
73
+ }
74
+ }
75
+
52
76
  if (isNoWorkerInitActive) {
53
77
  // eslint-disable-next-line no-empty-pattern
54
- beforeAll(function ({}, suite) {
78
+ beforeAll(async function ({}, suite) {
55
79
  suite ||= arguments[0]
56
- applyExecutionChanges(suite)
80
+ const efdSuiteCandidate = getEarlyFlakeDetectionSuiteCandidate(suite)
81
+ const isEfdSuiteAdmissionAllowed = !efdSuiteCandidate || !isEfdSuiteAdmissionEnabled ||
82
+ await requestBrowserEfdSuiteAdmission(efdSuiteCandidate.testSuite, efdSuiteCandidate.hasNewTest)
83
+ applyExecutionChanges(suite, isEfdSuiteAdmissionAllowed)
57
84
  })
58
85
 
59
86
  beforeEach(function ({ onTestFinished, task, skip }) {
60
87
  const testSuite = getTestSuite(task)
61
88
  const testName = getTestName(task)
62
89
  const isAttemptToFixTest = attemptToFixTests[testSuite]?.[testName]
63
- const isEarlyFlakeDetectionTestAttempt = isEarlyFlakeDetectionTest(testSuite, testName)
90
+ const isEarlyFlakeDetectionTestAttempt = isEarlyFlakeDetectionTask(task)
64
91
  const isQuarantinedTest = quarantinedTests[testSuite]?.[testName] && !isAttemptToFixTest
65
92
  const attemptIndex = getNextAttemptIndex(task)
66
93
  const attemptStart = now()
@@ -109,27 +136,30 @@ if (isNoWorkerInitActive) {
109
136
  })
110
137
  }
111
138
 
112
- function applyExecutionChanges (suite) {
113
- for (const task of suite?.tasks || []) {
114
- if (task.type === 'suite') {
115
- applyExecutionChanges(task)
116
- continue
117
- }
139
+ function applyExecutionChanges (suite, isEfdSuiteAdmissionAllowed) {
140
+ const tasks = suite?.tasks
141
+ if (tasks) {
142
+ for (const task of tasks) {
143
+ if (task.type === 'suite') {
144
+ applyExecutionChanges(task, isEfdSuiteAdmissionAllowed)
145
+ continue
146
+ }
118
147
 
119
- const testSuite = getTestSuite(task)
120
- const testName = getTestName(task)
121
- if (attemptToFixTests[testSuite]?.[testName]) {
122
- task.retry = 0
123
- task.repeats = attemptToFixRetries
124
- task.meta.__ddTestOptAtfRetries = attemptToFixRetries
125
- } else if (disabledTests[testSuite]?.[testName]) {
126
- task.mode = 'skip'
127
- } else if (isEarlyFlakeDetectionTest(testSuite, testName)) {
128
- task.retry = 0
129
- task.repeats = earlyFlakeDetectionRetries
130
- task.meta.__ddTestOptEfdRetries = earlyFlakeDetectionRetries
148
+ const testSuite = getTestSuite(task)
149
+ const testName = getTestName(task)
150
+ if (attemptToFixTests[testSuite]?.[testName]) {
151
+ task.retry = 0
152
+ task.repeats = attemptToFixRetries
153
+ task.meta.__ddTestOptAtfRetries = attemptToFixRetries
154
+ } else if (disabledTests[testSuite]?.[testName]) {
155
+ task.mode = 'skip'
156
+ } else if (isEfdSuiteAdmissionAllowed && isEarlyFlakeDetectionTest(testSuite, testName)) {
157
+ task.retry = 0
158
+ task.repeats = earlyFlakeDetectionRetries
159
+ task.meta.__ddTestOptEfdRetries = earlyFlakeDetectionRetries
160
+ }
161
+ wrapRetryCondition(task)
131
162
  }
132
- wrapRetryCondition(task)
133
163
  }
134
164
  }
135
165
 
@@ -331,7 +361,7 @@ function recordTestOptimizationStatus (task, attemptIndex = task.result?.repeatC
331
361
 
332
362
  if (attemptToFixTests[testSuite]?.[testName]) {
333
363
  recordAttemptToFixStatus(task, attemptIndex, onlyIfNewErrors)
334
- } else if (isEarlyFlakeDetectionTest(testSuite, testName)) {
364
+ } else if (isEarlyFlakeDetectionTask(task)) {
335
365
  recordEarlyFlakeDetectionStatus(task, attemptIndex, onlyIfNewErrors)
336
366
  } else if (task.repeats > 0) {
337
367
  recordManualRepeatStatus(task, attemptIndex)
@@ -380,7 +410,7 @@ function recordEarlyFlakeDetectionStatus (task, attemptIndex, onlyIfNewErrors) {
380
410
  earlyFlakeDetectionRetriesByTask.set(task, retryCount)
381
411
  task.repeats = retryCount
382
412
  task.meta.__ddTestOptEfdRetries = retryCount
383
- if (retryCount === 0 && hasEarlyFlakeDetectionSlowRetries) {
413
+ if (retryCount === 0) {
384
414
  task.meta.__ddTestOptEfdAbortReason = 'slow'
385
415
  }
386
416
  }
@@ -508,7 +538,7 @@ function getFinalAttemptIndex (task) {
508
538
  if (attemptToFixTests[testSuite]?.[testName]) {
509
539
  return getAttemptToFixRetryCount(task)
510
540
  }
511
- if (isEarlyFlakeDetectionTest(testSuite, testName)) {
541
+ if (isEarlyFlakeDetectionTask(task)) {
512
542
  return getEarlyFlakeDetectionRetryCountForTask(task)
513
543
  }
514
544
 
@@ -606,7 +636,7 @@ function prepareEarlyFlakeDetectionAttempt (task, attemptIndex) {
606
636
  earlyFlakeDetectionRetriesByTask.set(task, retryCount)
607
637
  task.repeats = retryCount
608
638
  task.meta.__ddTestOptEfdRetries = retryCount
609
- if (retryCount === 0 && hasEarlyFlakeDetectionSlowRetries) {
639
+ if (retryCount === 0) {
610
640
  task.meta.__ddTestOptEfdAbortReason = 'slow'
611
641
  }
612
642
  }
@@ -633,16 +663,10 @@ function prepareEarlyFlakeDetectionAttempt (task, attemptIndex) {
633
663
  }
634
664
 
635
665
  function getEarlyFlakeDetectionRetryCount (task) {
636
- if (!hasEarlyFlakeDetectionSlowRetries) {
637
- return earlyFlakeDetectionRetries
638
- }
639
-
640
666
  const executionStart = earlyFlakeDetectionStartByTask.get(task)
641
667
  const duration = executionStart === undefined ? task.result?.duration ?? 0 : now() - executionStart
642
- for (const { key, limitMs } of earlyFlakeDetectionRetryThresholds) {
643
- if (duration < limitMs) {
644
- return earlyFlakeDetectionSlowRetries[key] ?? 0
645
- }
668
+ for (const { durationLimitMs, retryCount } of earlyFlakeDetectionRetryPolicy.durationRetryCounts) {
669
+ if (duration < durationLimitMs) return retryCount
646
670
  }
647
671
  return 0
648
672
  }
@@ -675,6 +699,64 @@ function isEarlyFlakeDetectionTest (testSuite, testName) {
675
699
  return !testsForSuite.includes(testName)
676
700
  }
677
701
 
702
+ /**
703
+ * Returns whether Datadog admitted this task for EFD retries.
704
+ *
705
+ * @param {object} task
706
+ * @returns {boolean}
707
+ */
708
+ function isEarlyFlakeDetectionTask (task) {
709
+ return task.meta.__ddTestOptEfdRetries !== undefined
710
+ }
711
+
712
+ /**
713
+ * Returns suite admission data when a collected suite contains an EFD candidate.
714
+ *
715
+ * @param {object} suite
716
+ * @returns {{ hasNewTest: boolean, testSuite: string }|undefined}
717
+ */
718
+ function getEarlyFlakeDetectionSuiteCandidate (suite) {
719
+ const tasks = suite?.tasks
720
+ if (!tasks) return
721
+
722
+ let candidate
723
+ for (const task of tasks) {
724
+ if (task.type === 'suite') {
725
+ const nestedCandidate = getEarlyFlakeDetectionSuiteCandidate(task)
726
+ if (nestedCandidate) {
727
+ candidate ||= nestedCandidate
728
+ candidate.hasNewTest ||= nestedCandidate.hasNewTest
729
+ }
730
+ continue
731
+ }
732
+
733
+ if (task.mode === 'skip' || task.mode === 'todo') continue
734
+
735
+ const testSuite = getTestSuite(task)
736
+ const testName = getTestName(task)
737
+ if (attemptToFixTests[testSuite]?.[testName] || disabledTests[testSuite]?.[testName]) continue
738
+ if (!isEarlyFlakeDetectionTest(testSuite, testName)) continue
739
+
740
+ candidate ||= {
741
+ hasNewTest: false,
742
+ testSuite,
743
+ }
744
+ candidate.hasNewTest ||= isNewTest(testSuite, testName)
745
+ }
746
+ return candidate
747
+ }
748
+
749
+ /**
750
+ * Returns whether a collected test is absent from the known-tests response.
751
+ *
752
+ * @param {string} testSuite
753
+ * @param {string} testName
754
+ * @returns {boolean}
755
+ */
756
+ function isNewTest (testSuite, testName) {
757
+ return !(knownTests[testSuite] || []).includes(testName)
758
+ }
759
+
678
760
  function isModifiedTest (testSuite) {
679
761
  return modifiedFiles[testSuite]?.length > 0
680
762
  }
@@ -716,12 +798,28 @@ function getIsNoWorkerInitActive () {
716
798
 
717
799
  function getProvidedContext () {
718
800
  try {
719
- return inject('_ddVitestWorkerSetup') || {}
801
+ return parseProvidedContextValue(inject('_ddVitestWorkerSetup'))
720
802
  } catch {
721
803
  try {
722
- return globalThis.__vitest_worker__.providedContext._ddVitestWorkerSetup || {}
804
+ return parseProvidedContextValue(globalThis.__vitest_worker__.providedContext._ddVitestWorkerSetup)
723
805
  } catch {
724
806
  return {}
725
807
  }
726
808
  }
727
809
  }
810
+
811
+ /**
812
+ * Restore context serialized to keep Vitest's inline browser bootstrap script valid.
813
+ *
814
+ * @param {object|string|undefined} value
815
+ * @returns {object}
816
+ */
817
+ function parseProvidedContextValue (value) {
818
+ if (typeof value !== 'string' || !value.startsWith(SERIALIZED_CONTEXT_PREFIX)) return value || {}
819
+
820
+ try {
821
+ return JSON.parse(value.slice(SERIALIZED_CONTEXT_PREFIX.length))
822
+ } catch {
823
+ return {}
824
+ }
825
+ }
package/index.d.ts CHANGED
@@ -71,7 +71,7 @@ interface Tracer extends opentracing.Tracer {
71
71
  * @param plugin The name of a built-in plugin.
72
72
  * @param config Configuration options. Can also be `false` to disable the plugin.
73
73
  */
74
- use<P extends keyof Plugins> (plugin: P, config?: Plugins[P] | boolean): this;
74
+ use<P extends tracer.PluginName> (plugin: P, config?: tracer.PluginOptions[P] | boolean): this;
75
75
 
76
76
  /**
77
77
  * Returns a reference to the current scope.
@@ -314,6 +314,9 @@ interface Plugins {
314
314
  }
315
315
 
316
316
  declare namespace tracer {
317
+ export interface PluginOptions extends Plugins {}
318
+ export type PluginName = keyof PluginOptions;
319
+
317
320
  export type SpanOptions = Omit<opentracing.SpanOptions, 'childOf'> & {
318
321
  /**
319
322
  * Set childOf to 'null' to create a root span without a parent, even when a parent span
@@ -3711,6 +3714,13 @@ declare namespace tracer {
3711
3714
  */
3712
3715
  submitEvaluation (spanContext: llmobs.ExportedLLMObsSpan, options: llmobs.EvaluationOptions): void
3713
3716
 
3717
+ /**
3718
+ * Submits end-user feedback for a span, trace, session, or customer-defined entity.
3719
+ * Exactly one target must be provided in the options.
3720
+ * @param options An object containing the label, metric type, value, submitter and target of the feedback.
3721
+ */
3722
+ submitFeedback (options: llmobs.FeedbackOptions): void
3723
+
3714
3724
 
3715
3725
  /**
3716
3726
  * Annotates all spans, including auto-instrumented spans, with the provided tags created in the context of the callback function.
@@ -3957,6 +3967,93 @@ declare namespace tracer {
3957
3967
  metadata?: { [key: string]: any }
3958
3968
  }
3959
3969
 
3970
+ interface FeedbackSubmitter {
3971
+ /**
3972
+ * The identifier of the end user who submitted the feedback.
3973
+ */
3974
+ id: string,
3975
+
3976
+ /**
3977
+ * The type of submitter, e.g. 'user'.
3978
+ */
3979
+ type?: string
3980
+ }
3981
+
3982
+ interface FeedbackOptions {
3983
+ /**
3984
+ * The name of the feedback metric
3985
+ */
3986
+ label: string,
3987
+
3988
+ /**
3989
+ * The type of feedback metric, one of 'categorical', 'score', 'boolean', 'json' or 'text'
3990
+ */
3991
+ metricType: 'categorical' | 'score' | 'boolean' | 'json' | 'text',
3992
+
3993
+ /**
3994
+ * The value of the feedback metric.
3995
+ * Must be string for 'categorical' and 'text' metrics, number for 'score' metrics, boolean for 'boolean' metrics and a JSON object for 'json' metrics.
3996
+ */
3997
+ value: string | number | boolean | { [key: string]: any },
3998
+
3999
+ /**
4000
+ * Who submitted the feedback.
4001
+ */
4002
+ submitter: llmobs.FeedbackSubmitter,
4003
+
4004
+ /**
4005
+ * The span context of the span to attach the feedback to, as returned by `llmobs.exportSpan()`.
4006
+ * Exactly one of `span`, `spanId`, `traceId`, `sessionId` or `feedbackJoinKey` must be provided.
4007
+ */
4008
+ span?: llmobs.ExportedLLMObsSpan,
4009
+
4010
+ /**
4011
+ * The ID of the span to attach the feedback to.
4012
+ */
4013
+ spanId?: string,
4014
+
4015
+ /**
4016
+ * The ID of the trace to attach the feedback to.
4017
+ */
4018
+ traceId?: string,
4019
+
4020
+ /**
4021
+ * The ID of the session to attach the feedback to.
4022
+ */
4023
+ sessionId?: string,
4024
+
4025
+ /**
4026
+ * A customer-defined key to attach the feedback to.
4027
+ */
4028
+ feedbackJoinKey?: string,
4029
+
4030
+ /**
4031
+ * An object of string key-value pairs to tag the feedback with.
4032
+ * A `null` or `undefined` value is sent as the string `"null"` or `"undefined"`.
4033
+ */
4034
+ tags?: { [key: string]: string | null | undefined },
4035
+
4036
+ /**
4037
+ * The name of the ML application
4038
+ */
4039
+ mlApp?: string,
4040
+
4041
+ /**
4042
+ * The timestamp in milliseconds when the feedback was generated.
4043
+ */
4044
+ timestampMs?: number,
4045
+
4046
+ /**
4047
+ * Reasoning for the feedback.
4048
+ */
4049
+ reasoning?: string,
4050
+
4051
+ /**
4052
+ * Whether the feedback passed or failed. Valid values are pass and fail.
4053
+ */
4054
+ assessment?: 'pass' | 'fail'
4055
+ }
4056
+
3960
4057
  interface Document {
3961
4058
  /**
3962
4059
  * Document text
@@ -4002,6 +4099,11 @@ declare namespace tracer {
4002
4099
  * Audio segments attached to the message (e.g. speech input/output)
4003
4100
  */
4004
4101
  audioParts?: AudioPart[],
4102
+
4103
+ /**
4104
+ * Images attached to the message (e.g. vision input, generated output)
4105
+ */
4106
+ imageParts?: ImagePart[],
4005
4107
  }
4006
4108
 
4007
4109
  /**
@@ -4019,6 +4121,43 @@ declare namespace tracer {
4019
4121
  content: string,
4020
4122
  }
4021
4123
 
4124
+ /**
4125
+ * Represents an image attached to an LLM chat model message, carrying exactly
4126
+ * one of inline `content` or an `attachmentKey`. Supplying neither or both is
4127
+ * rejected by the tagger, and the union keeps both shapes from type-checking.
4128
+ */
4129
+ type ImagePart = {
4130
+ /**
4131
+ * The MIME type of the image (e.g. "image/png", "image/jpeg")
4132
+ */
4133
+ mimeType: string,
4134
+
4135
+ /**
4136
+ * The image content as a base64-encoded string
4137
+ */
4138
+ content: string,
4139
+
4140
+ /**
4141
+ * Explicitly excluded when inline content is present to maintain type safety.
4142
+ */
4143
+ attachmentKey?: never,
4144
+ } | {
4145
+ /**
4146
+ * The MIME type of the image (e.g. "image/png", "image/jpeg")
4147
+ */
4148
+ mimeType: string,
4149
+
4150
+ /**
4151
+ * Key of an already-uploaded image, in place of inline content
4152
+ */
4153
+ attachmentKey: string,
4154
+
4155
+ /**
4156
+ * Explicitly excluded when an attachment key is present to maintain type safety.
4157
+ */
4158
+ content?: never,
4159
+ }
4160
+
4022
4161
  /**
4023
4162
  * Represents a single tool call for an LLM chat model message
4024
4163
  */
package/init.js CHANGED
@@ -1,22 +1,6 @@
1
1
  'use strict'
2
2
 
3
- // In PM2 cluster mode, per-app env vars arrive as a `pm2_env`
4
- // JSON string after --require has already run so we extract them
5
- // manually if present.
6
- var pm2EnvStr = process.env.pm2_env
7
- if (typeof pm2EnvStr === 'string') {
8
- try {
9
- var pm2Config = JSON.parse(pm2EnvStr)
10
- var pm2Keys = Object.keys(pm2Config)
11
- for (var i = 0; i < pm2Keys.length; i++) {
12
- var k = pm2Keys[i]
13
- var v = pm2Config[k]
14
- if (v != null) {
15
- process.env[k] = String(v)
16
- }
17
- }
18
- } catch (e) {}
19
- }
3
+ require('./packages/dd-trace/src/guardrails/apply-pm2-env')
20
4
 
21
5
  var guard = require('./packages/dd-trace/src/guardrails')
22
6