dd-trace 5.117.0 → 5.118.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 (83) hide show
  1. package/ci/diagnose.js +162 -21
  2. package/ci/init.js +0 -1
  3. package/ci/runbook.md +187 -180
  4. package/ci/test-optimization-validation/approval-artifacts.js +3 -1
  5. package/ci/test-optimization-validation/approval.js +52 -15
  6. package/ci/test-optimization-validation/ci-command-candidate.js +26 -32
  7. package/ci/test-optimization-validation/ci-discovery.js +1 -1
  8. package/ci/test-optimization-validation/ci-package-scripts.js +141 -0
  9. package/ci/test-optimization-validation/ci-remediation.js +112 -39
  10. package/ci/test-optimization-validation/cli.js +516 -681
  11. package/ci/test-optimization-validation/command-blocker.js +188 -33
  12. package/ci/test-optimization-validation/command-output-policy.js +3 -26
  13. package/ci/test-optimization-validation/command-runner.js +189 -245
  14. package/ci/test-optimization-validation/environment.js +90 -0
  15. package/ci/test-optimization-validation/executable.js +159 -388
  16. package/ci/test-optimization-validation/framework-adapters/cucumber.js +119 -0
  17. package/ci/test-optimization-validation/framework-adapters/cypress.js +107 -0
  18. package/ci/test-optimization-validation/framework-adapters/playwright.js +181 -0
  19. package/ci/test-optimization-validation/generated-files.js +75 -13
  20. package/ci/test-optimization-validation/generated-test-contract.js +283 -0
  21. package/ci/test-optimization-validation/generated-verifier.js +49 -16
  22. package/ci/test-optimization-validation/literal-environment.js +57 -0
  23. package/ci/test-optimization-validation/manifest-loader.js +2 -22
  24. package/ci/test-optimization-validation/manifest-scaffold.js +816 -515
  25. package/ci/test-optimization-validation/manifest-schema.js +494 -702
  26. package/ci/test-optimization-validation/offline-fixtures.js +4 -1
  27. package/ci/test-optimization-validation/plan-writer.js +243 -984
  28. package/ci/test-optimization-validation/preflight-runner.js +49 -55
  29. package/ci/test-optimization-validation/redaction.js +2 -1
  30. package/ci/test-optimization-validation/report-writer.js +391 -1357
  31. package/ci/test-optimization-validation/result-semantics.js +86 -0
  32. package/ci/test-optimization-validation/runner-command.js +249 -0
  33. package/ci/test-optimization-validation/runner-contract.js +664 -0
  34. package/ci/test-optimization-validation/scenarios/auto-test-retries.js +29 -3
  35. package/ci/test-optimization-validation/scenarios/basic-reporting.js +240 -543
  36. package/ci/test-optimization-validation/scenarios/ci-wiring.js +450 -670
  37. package/ci/test-optimization-validation/scenarios/early-flake-detection.js +4 -3
  38. package/ci/test-optimization-validation/scenarios/helpers.js +67 -9
  39. package/ci/test-optimization-validation/scenarios/test-management.js +4 -3
  40. package/ci/test-optimization-validation/source-text.js +87 -0
  41. package/ci/test-optimization-validation/test-output.js +12 -22
  42. package/ext/tags.d.ts +1 -0
  43. package/index.d.ts +7 -0
  44. package/package.json +2 -2
  45. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  46. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  47. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/openai-agents.js +31 -0
  48. package/packages/datadog-instrumentations/src/openai-agents.js +159 -0
  49. package/packages/datadog-plugin-openai-agents/src/index.js +74 -0
  50. package/packages/datadog-plugin-openai-agents/src/integration.js +503 -0
  51. package/packages/datadog-plugin-openai-agents/src/processor.js +108 -0
  52. package/packages/datadog-plugin-openai-agents/src/util.js +60 -0
  53. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +48 -49
  54. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +161 -33
  55. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +6 -4
  56. package/packages/dd-trace/src/config/generated-config-types.d.ts +2 -0
  57. package/packages/dd-trace/src/config/supported-configurations.json +7 -0
  58. package/packages/dd-trace/src/encode/span-stats.js +7 -1
  59. package/packages/dd-trace/src/exporter.js +16 -9
  60. package/packages/dd-trace/src/llmobs/index.js +9 -1
  61. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +104 -22
  62. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +5 -9
  63. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +20 -0
  64. package/packages/dd-trace/src/llmobs/plugins/openai-agents/utils.js +321 -0
  65. package/packages/dd-trace/src/llmobs/sdk.js +2 -1
  66. package/packages/dd-trace/src/llmobs/span_processor.js +7 -2
  67. package/packages/dd-trace/src/llmobs/tagger.js +23 -3
  68. package/packages/dd-trace/src/llmobs/util.js +56 -3
  69. package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +3 -6
  70. package/packages/dd-trace/src/openfeature/configuration_source.js +5 -8
  71. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +5 -1
  72. package/packages/dd-trace/src/plugins/ci_plugin.js +9 -3
  73. package/packages/dd-trace/src/plugins/index.js +1 -0
  74. package/packages/dd-trace/src/plugins/outbound.js +4 -1
  75. package/packages/dd-trace/src/span_processor.js +1 -1
  76. package/packages/dd-trace/src/span_stats.js +22 -4
  77. package/ci/test-optimization-validation/command-suitability.js +0 -471
  78. package/ci/test-optimization-validation/init-probe-preload.js +0 -163
  79. package/ci/test-optimization-validation/init-probe.js +0 -246
  80. package/ci/test-optimization-validation/late-initialization.js +0 -63
  81. package/ci/test-optimization-validation/local-command.js +0 -163
  82. package/ci/test-optimization-validation/setup-runner.js +0 -97
  83. package/ci/test-optimization-validation-manifest.schema.json +0 -1
package/ci/diagnose.js CHANGED
@@ -34,6 +34,7 @@ const SKIPPED_DIRECTORIES = new Set([
34
34
  '.yarn',
35
35
  'build',
36
36
  'coverage',
37
+ 'dd-test-optimization-validation-results',
37
38
  'dist',
38
39
  'node_modules',
39
40
  'out',
@@ -90,6 +91,14 @@ const WATCH_MODE_RE = /(?:^|\s)(?:watch|--watch|--watchAll)(?!(?:=false)(?:\s|$)
90
91
  const CYPRESS_MANUAL_PLUGIN_RE = /dd-trace\/ci\/cypress\/(?:plugin|after-run|after-spec)\b/
91
92
  const CYPRESS_SUPPORT_RE = /dd-trace\/ci\/cypress\/support\b/
92
93
  const CYPRESS_SUPPORT_DISABLED_RE = /supportFile\s*:\s*false|"supportFile"\s*:\s*false/
94
+ const CUCUMBER_RUNNER_COMMAND_RE = new RegExp(
95
+ String.raw`(?:^|(?:&&|\|\||[;|])\s*)` +
96
+ String.raw`(?:(?:[A-Za-z_][A-Za-z0-9_]*=(?:"[^"]*"|'[^']*'|[^\s;&|]+)|` +
97
+ String.raw`cross-env|env|npx|nyc|c8|npm\s+exec|pnpm\s+exec|yarn\s+exec|--?[^\s;&|]+)\s+)*` +
98
+ String.raw`(?:(?:[^\s"';&|]+[\/\\])?(?:cucumber-js(?:\.cmd)?|cucumber(?:\.cmd)?)|` +
99
+ String.raw`node(?:\.exe)?\s+(?:[^\s"';&|]+[\/\\])?bin[\/\\]cucumber\.js)` +
100
+ String.raw`(?=$|[\s"';&|])`
101
+ )
93
102
  const CUCUMBER_PARALLEL_RE =
94
103
  /\bcucumber(?:-js)?\b[\s\S]{0,200}\s--parallel\b|--parallel\b[\s\S]{0,200}\bcucumber(?:-js)?\b/
95
104
  const JEST_FORCE_EXIT_RE = /\bforceExit\s*:\s*true\b|--forceExit\b|"forceExit"\s*:\s*true/
@@ -123,9 +132,8 @@ function getFrameworkDefinitions (ddMajor) {
123
132
  commandPatterns: [/\bjest\b/],
124
133
  configPatterns: [/^jest\.config\./, /^config-jest\./],
125
134
  supportedRange: ddMajor >= 6 ? '>=28.0.0' : '>=24.8.0',
126
- recommendation: ddMajor >= 6
127
- ? 'Upgrade Jest to >=28.0.0, or use dd-trace v5 for older Jest versions.'
128
- : 'Upgrade Jest to >=24.8.0.',
135
+ recommendation: 'Use a Jest and dd-trace combination whose documented support ranges overlap; this ' +
136
+ `dd-trace version requires Jest ${ddMajor >= 6 ? '>=28.0.0' : '>=24.8.0'}.`,
129
137
  },
130
138
  {
131
139
  id: 'mocha',
@@ -134,9 +142,8 @@ function getFrameworkDefinitions (ddMajor) {
134
142
  commandPatterns: [/\bmocha\b/],
135
143
  configPatterns: [/^\.mocharc\./],
136
144
  supportedRange: ddMajor >= 6 ? '>=8.0.0' : '>=5.2.0',
137
- recommendation: ddMajor >= 6
138
- ? 'Upgrade Mocha to >=8.0.0, or use dd-trace v5 for older Mocha versions.'
139
- : 'Upgrade Mocha to >=5.2.0.',
145
+ recommendation: 'Use a Mocha and dd-trace combination whose documented support ranges overlap; this ' +
146
+ `dd-trace version requires Mocha ${ddMajor >= 6 ? '>=8.0.0' : '>=5.2.0'}.`,
140
147
  notes: [
141
148
  'Impacted tests are detected at suite level for Mocha.',
142
149
  ],
@@ -145,7 +152,7 @@ function getFrameworkDefinitions (ddMajor) {
145
152
  id: 'cucumber',
146
153
  name: 'Cucumber',
147
154
  packages: ['@cucumber/cucumber'],
148
- commandPatterns: [/\bcucumber-js\b/, /\bcucumber\b/],
155
+ commandPatterns: [CUCUMBER_RUNNER_COMMAND_RE],
149
156
  configPatterns: [/^cucumber\./],
150
157
  supportedRange: '>=7.0.0',
151
158
  recommendation: 'Upgrade @cucumber/cucumber to >=7.0.0.',
@@ -158,9 +165,8 @@ function getFrameworkDefinitions (ddMajor) {
158
165
  configPatterns: [/^cypress\.config\./, /^cypress\.json$/],
159
166
  supportedRange: ddMajor >= 6 ? '>=12.0.0' : '>=6.7.0',
160
167
  autoInstrumentationRange: ddMajor >= 6 ? '>=12.0.0' : '>=10.2.0',
161
- recommendation: ddMajor >= 6
162
- ? 'Upgrade Cypress to >=12.0.0, or use dd-trace v5 for older Cypress versions.'
163
- : 'Upgrade Cypress to >=6.7.0.',
168
+ recommendation: 'Use a Cypress and dd-trace combination whose documented support ranges overlap; this ' +
169
+ `dd-trace version requires Cypress ${ddMajor >= 6 ? '>=12.0.0' : '>=6.7.0'}.`,
164
170
  },
165
171
  {
166
172
  id: 'playwright',
@@ -169,9 +175,8 @@ function getFrameworkDefinitions (ddMajor) {
169
175
  commandPatterns: [/\bplaywright\s+test\b/],
170
176
  configPatterns: [/^playwright\.config\./],
171
177
  supportedRange: ddMajor >= 6 ? '>=1.38.0' : '>=1.18.0',
172
- recommendation: ddMajor >= 6
173
- ? 'Upgrade Playwright to >=1.38.0, or use dd-trace v5 for older Playwright versions.'
174
- : 'Upgrade Playwright to >=1.18.0.',
178
+ recommendation: 'Use a Playwright and dd-trace combination whose documented support ranges overlap; this ' +
179
+ `dd-trace version requires Playwright ${ddMajor >= 6 ? '>=1.38.0' : '>=1.18.0'}.`,
175
180
  notes: [
176
181
  'Test Impact Analysis suite skipping is not supported for Playwright.',
177
182
  'Impacted tests are detected at suite level for Playwright.',
@@ -199,7 +204,7 @@ const UNSUPPORTED_FRAMEWORKS = [
199
204
  id: 'node-test',
200
205
  name: 'Node.js test runner',
201
206
  packages: [],
202
- commandPatterns: [/\bnode\s+--test\b/, /\bnode\s+--experimental-test-coverage\b/],
207
+ commandPatterns: [/\bnode\s+--test\b/, /\bnode\s+--experimental-test-coverage\b/, /\bbnt\b/],
203
208
  },
204
209
  { id: 'ava', name: 'AVA', packages: ['ava'], commandPatterns: [/\bava\b/] },
205
210
  { id: 'tap', name: 'tap', packages: ['tap'], commandPatterns: [/\btap\b/] },
@@ -1106,9 +1111,13 @@ function collectScripts (manifests) {
1106
1111
  */
1107
1112
  function detectSupportedFrameworks (root, definitions, manifests, scripts, textFiles) {
1108
1113
  const frameworks = []
1114
+ const projectPreferenceScores = getProjectPreferenceScores(root, manifests)
1109
1115
 
1110
1116
  for (const definition of definitions) {
1111
- const dependencyEntries = findDependencyEntries(manifests, definition.packages)
1117
+ const dependencyEntries = [
1118
+ ...findDependencyEntries(manifests, definition.packages),
1119
+ ...findPackageIdentityEntries(manifests, definition.packages),
1120
+ ]
1112
1121
  const scriptMatches = findScriptMatches(scripts, definition.commandPatterns)
1113
1122
  const configMatches = findConfigMatches(textFiles, definition.configPatterns)
1114
1123
 
@@ -1119,6 +1128,7 @@ function detectSupportedFrameworks (root, definitions, manifests, scripts, textF
1119
1128
  dependencyEntries,
1120
1129
  scriptMatches,
1121
1130
  configMatches,
1131
+ projectPreferenceScores,
1122
1132
  locations: unique([
1123
1133
  ...dependencyEntries.map(entry => entry.relativePath),
1124
1134
  ...scriptMatches.map(script => script.relativePath),
@@ -1193,23 +1203,117 @@ function getSupportedVersionDetection (framework, relativePath) {
1193
1203
  * @returns {object|undefined} eligible command match
1194
1204
  */
1195
1205
  function getEligibleCommandMatch (framework) {
1196
- const scriptMatches = framework.scriptMatches || []
1206
+ const scriptMatches = [...(framework.scriptMatches || [])].sort((left, right) => {
1207
+ return compareProjectScope(left, right, framework.projectPreferenceScores) ||
1208
+ getFrameworkCommandPreference(framework.id, left) - getFrameworkCommandPreference(framework.id, right) ||
1209
+ left.relativePath.localeCompare(right.relativePath)
1210
+ })
1197
1211
 
1198
1212
  for (const script of scriptMatches) {
1199
1213
  if (isIneligibleFrameworkCommand(framework.id, script.command)) continue
1200
1214
  return script
1201
1215
  }
1202
1216
 
1203
- if (scriptMatches.length > 0) return
1204
-
1205
- if (framework.id === 'jest' || framework.id === 'mocha' || framework.id === 'vitest') {
1206
- return framework.dependencyEntries?.[0] && {
1217
+ if (framework.id === 'cucumber' || framework.id === 'cypress' || framework.id === 'jest' ||
1218
+ framework.id === 'mocha' || framework.id === 'playwright' || framework.id === 'vitest') {
1219
+ const dependencyEntries = [...(framework.dependencyEntries || [])].sort((left, right) => {
1220
+ return compareProjectPreference(left, right, framework.projectPreferenceScores)
1221
+ })
1222
+ return dependencyEntries[0] && {
1207
1223
  command: `direct ${framework.id} binary`,
1208
- relativePath: framework.dependencyEntries[0].relativePath,
1224
+ relativePath: dependencyEntries[0].relativePath,
1209
1225
  }
1210
1226
  }
1211
1227
  }
1212
1228
 
1229
+ /**
1230
+ * Prefers direct, single-stage framework scripts over aggregate coverage or setup chains.
1231
+ *
1232
+ * @param {string} frameworkId framework id
1233
+ * @param {object} script package script
1234
+ * @returns {number} lower is preferred
1235
+ */
1236
+ function getFrameworkCommandPreference (frameworkId, script) {
1237
+ let score = 0
1238
+ if (/coverage|all|full/i.test(script.name)) score += 4
1239
+ if (/[\r\n;&|`]|\$\(/.test(script.command)) score += 4
1240
+ const invokesFramework = frameworkId === 'cucumber'
1241
+ ? isCucumberRunnerCommand(script.command)
1242
+ : script.command.toLowerCase().includes(frameworkId.toLowerCase())
1243
+ if (invokesFramework) score--
1244
+ return score
1245
+ }
1246
+
1247
+ /**
1248
+ * Scores package manifests by how closely their names match the repository directory name.
1249
+ *
1250
+ * @param {string} root repository root
1251
+ * @param {Array<object>} manifests package manifests
1252
+ * @returns {Map<string, number>} preference score by package.json path
1253
+ */
1254
+ function getProjectPreferenceScores (root, manifests) {
1255
+ const repositoryTokens = getIdentityTokens(path.basename(root))
1256
+ const scores = new Map()
1257
+
1258
+ for (const manifest of manifests) {
1259
+ const projectName = manifest.json.name || (manifest.relativePath === 'package.json'
1260
+ ? path.basename(root)
1261
+ : path.basename(path.dirname(manifest.relativePath)))
1262
+ const projectTokens = getIdentityTokens(projectName)
1263
+ let score = 0
1264
+ for (const token of projectTokens) {
1265
+ if (repositoryTokens.has(token)) score++
1266
+ }
1267
+ scores.set(manifest.relativePath, score)
1268
+ }
1269
+
1270
+ return scores
1271
+ }
1272
+
1273
+ /**
1274
+ * Normalizes repository and package names for conservative identity comparison.
1275
+ *
1276
+ * @param {string} value repository or package name
1277
+ * @returns {Set<string>} normalized identity tokens
1278
+ */
1279
+ function getIdentityTokens (value) {
1280
+ const tokens = String(value || '').toLowerCase().split(/[^a-z0-9]+/).filter(Boolean)
1281
+ const normalized = new Set()
1282
+ for (const token of tokens) {
1283
+ if (['monorepo', 'package', 'packages', 'project', 'repo', 'root', 'workspace'].includes(token)) continue
1284
+ normalized.add(token.endsWith('js') && token.length > 2 ? token.slice(0, -2) : token)
1285
+ }
1286
+ return normalized
1287
+ }
1288
+
1289
+ /**
1290
+ * Orders command owners by repository identity, then by scope and stable path.
1291
+ *
1292
+ * @param {object} left first command or dependency entry
1293
+ * @param {object} right second command or dependency entry
1294
+ * @param {Map<string, number>|undefined} scores package preference scores
1295
+ * @returns {number} sort order
1296
+ */
1297
+ function compareProjectPreference (left, right, scores) {
1298
+ return compareProjectScope(left, right, scores) ||
1299
+ left.relativePath.localeCompare(right.relativePath)
1300
+ }
1301
+
1302
+ /**
1303
+ * Orders command owners by repository identity and package depth.
1304
+ *
1305
+ * @param {object} left first command or dependency entry
1306
+ * @param {object} right second command or dependency entry
1307
+ * @param {Map<string, number>|undefined} scores package preference scores
1308
+ * @returns {number} sort order before stable path tie-breaking
1309
+ */
1310
+ function compareProjectScope (left, right, scores) {
1311
+ const scoreDifference = (scores?.get(right.relativePath) || 0) - (scores?.get(left.relativePath) || 0)
1312
+ if (scoreDifference !== 0) return scoreDifference
1313
+
1314
+ return left.relativePath.split('/').length - right.relativePath.split('/').length
1315
+ }
1316
+
1213
1317
  /**
1214
1318
  * Checks whether a framework command is ineligible for live validation.
1215
1319
  *
@@ -1218,12 +1322,23 @@ function getEligibleCommandMatch (framework) {
1218
1322
  * @returns {boolean} whether the command is ineligible
1219
1323
  */
1220
1324
  function isIneligibleFrameworkCommand (frameworkId, command) {
1325
+ if (frameworkId === 'cucumber' && !isCucumberRunnerCommand(command)) return true
1221
1326
  if (frameworkId === 'vitest' && /\bvitest\s+bench\b/.test(command)) return true
1222
1327
  if (WATCH_MODE_RE.test(command)) return true
1223
1328
 
1224
1329
  return false
1225
1330
  }
1226
1331
 
1332
+ /**
1333
+ * Checks whether a package command invokes the Cucumber runner instead of merely naming a Cucumber config file.
1334
+ *
1335
+ * @param {string} command package script command
1336
+ * @returns {boolean} whether the command invokes Cucumber
1337
+ */
1338
+ function isCucumberRunnerCommand (command) {
1339
+ return CUCUMBER_RUNNER_COMMAND_RE.test(String(command || ''))
1340
+ }
1341
+
1227
1342
  /**
1228
1343
  * Detects unsupported test frameworks.
1229
1344
  *
@@ -1317,6 +1432,28 @@ function findDependencyEntries (manifests, packageNames) {
1317
1432
  return entries
1318
1433
  }
1319
1434
 
1435
+ /**
1436
+ * Treats a repository containing a framework's own package as a local version source.
1437
+ *
1438
+ * @param {Array<object>} manifests package manifests
1439
+ * @param {string[]} packageNames framework package names
1440
+ * @returns {Array<object>} local package identities
1441
+ */
1442
+ function findPackageIdentityEntries (manifests, packageNames) {
1443
+ const packageSet = new Set(packageNames)
1444
+ const entries = []
1445
+ for (const manifest of manifests) {
1446
+ if (!packageSet.has(manifest.json.name) || typeof manifest.json.version !== 'string') continue
1447
+ entries.push({
1448
+ packageName: manifest.json.name,
1449
+ rawVersion: manifest.json.version,
1450
+ section: 'package',
1451
+ relativePath: manifest.relativePath,
1452
+ })
1453
+ }
1454
+ return entries
1455
+ }
1456
+
1320
1457
  /**
1321
1458
  * Resolves framework package versions from installed modules and manifest ranges.
1322
1459
  *
@@ -2011,6 +2148,8 @@ function formatLocations (locations) {
2011
2148
  * @returns {object} serializable framework summary
2012
2149
  */
2013
2150
  function serializeSupportedFramework (framework) {
2151
+ const eligibleCommand = getEligibleCommandMatch(framework)
2152
+ const supportedVersion = getSupportedVersionDetection(framework, eligibleCommand?.relativePath)
2014
2153
  return {
2015
2154
  id: framework.id,
2016
2155
  name: framework.name,
@@ -2018,6 +2157,8 @@ function serializeSupportedFramework (framework) {
2018
2157
  supportedRange: framework.supportedRange,
2019
2158
  locations: framework.locations,
2020
2159
  versionDetections: framework.versionDetections,
2160
+ eligibleCommand,
2161
+ supportedVersion,
2021
2162
  }
2022
2163
  }
2023
2164
 
package/ci/init.js CHANGED
@@ -75,7 +75,6 @@ if (isTestWorker) {
75
75
  exporter: EXPORTER_MAP[testWorkerType],
76
76
  }
77
77
  } else if (isValidationMode) {
78
- baseOptions.telemetry = { enabled: false }
79
78
  baseOptions.experimental = {
80
79
  exporter: 'ci_validation',
81
80
  }