dd-trace 5.93.0 → 5.95.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 (59) hide show
  1. package/LICENSE-3rdparty.csv +46 -44
  2. package/index.d.ts +182 -13
  3. package/package.json +14 -10
  4. package/packages/datadog-instrumentations/src/anthropic.js +1 -1
  5. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +23 -0
  6. package/packages/datadog-instrumentations/src/helpers/rewriter/{orchestrion/compiler.js → compiler.js} +4 -13
  7. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +16 -2
  8. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/langgraph.js +2 -2
  9. package/packages/datadog-instrumentations/src/helpers/rewriter/{orchestrion/transforms.js → transforms.js} +3 -89
  10. package/packages/datadog-instrumentations/src/jest.js +118 -32
  11. package/packages/datadog-instrumentations/src/mocha/main.js +6 -0
  12. package/packages/datadog-instrumentations/src/mocha/utils.js +89 -5
  13. package/packages/datadog-instrumentations/src/playwright.js +10 -0
  14. package/packages/datadog-instrumentations/src/vitest.js +119 -0
  15. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +12 -0
  16. package/packages/datadog-plugin-dd-trace-api/src/index.js +1 -4
  17. package/packages/datadog-plugin-jest/src/index.js +6 -0
  18. package/packages/datadog-plugin-mocha/src/index.js +11 -0
  19. package/packages/datadog-plugin-playwright/src/index.js +9 -0
  20. package/packages/datadog-plugin-vitest/src/index.js +9 -0
  21. package/packages/datadog-webpack/index.js +187 -0
  22. package/packages/datadog-webpack/src/loader.js +27 -0
  23. package/packages/datadog-webpack/src/log.js +32 -0
  24. package/packages/dd-trace/src/azure_metadata.js +15 -15
  25. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +176 -33
  26. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +10 -21
  27. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +76 -1
  28. package/packages/dd-trace/src/ci-visibility/requests/fs-cache.js +259 -0
  29. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +56 -0
  30. package/packages/dd-trace/src/config/config-base.d.ts +7 -0
  31. package/packages/dd-trace/src/config/config-base.js +5 -0
  32. package/packages/dd-trace/src/config/config-types.d.ts +78 -0
  33. package/packages/dd-trace/src/config/generated-config-types.d.ts +582 -0
  34. package/packages/dd-trace/src/config/supported-configurations.json +7 -0
  35. package/packages/dd-trace/src/llmobs/constants/tags.js +1 -0
  36. package/packages/dd-trace/src/llmobs/constants/text.js +1 -1
  37. package/packages/dd-trace/src/llmobs/constants/writers.js +1 -1
  38. package/packages/dd-trace/src/llmobs/plugins/anthropic.js +11 -2
  39. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +4 -1
  40. package/packages/dd-trace/src/llmobs/writers/spans.js +1 -1
  41. package/packages/dd-trace/src/opentracing/span.js +5 -0
  42. package/packages/dd-trace/src/plugin_manager.js +10 -7
  43. package/packages/dd-trace/src/plugins/util/test.js +76 -0
  44. package/packages/dd-trace/src/priority_sampler.js +1 -1
  45. package/packages/dd-trace/src/profiling/profilers/wall.js +35 -28
  46. package/packages/dd-trace/src/rate_limiter.js +2 -1
  47. package/packages/dd-trace/src/tagger.js +31 -35
  48. package/vendor/dist/@apm-js-collab/code-transformer/LICENSE +28 -0
  49. package/vendor/dist/@apm-js-collab/code-transformer/index.js +133 -0
  50. package/vendor/dist/@opentelemetry/core/index.js +1 -1
  51. package/vendor/dist/@opentelemetry/resources/index.js +1 -1
  52. package/vendor/dist/esquery/index.js +1 -1
  53. package/vendor/dist/meriyah/index.js +1 -1
  54. package/webpack.js +3 -0
  55. package/packages/datadog-instrumentations/src/helpers/rewriter/orchestrion/index.js +0 -43
  56. package/packages/datadog-instrumentations/src/helpers/rewriter/orchestrion/matcher.js +0 -49
  57. package/packages/datadog-instrumentations/src/helpers/rewriter/orchestrion/transformer.js +0 -121
  58. package/vendor/dist/astring/LICENSE +0 -19
  59. package/vendor/dist/astring/index.js +0 -1
@@ -0,0 +1,259 @@
1
+ 'use strict'
2
+
3
+ const fs = require('node:fs')
4
+ const path = require('node:path')
5
+ const { createHash } = require('node:crypto')
6
+ const { tmpdir } = require('node:os')
7
+
8
+ const log = require('../../log')
9
+ const { getValueFromEnvSources } = require('../../config/helper')
10
+
11
+ const CACHE_TTL_MS = 30 * 60 * 1000 // 30 minutes
12
+ const CACHE_LOCK_POLL_MS = 500
13
+ const CACHE_LOCK_TIMEOUT_MS = 120_000 // 2 minutes
14
+ const CACHE_LOCK_HEARTBEAT_MS = 30_000 // 30 seconds
15
+
16
+ /**
17
+ * Returns whether the filesystem cache is enabled via the env var.
18
+ *
19
+ * @returns {boolean}
20
+ */
21
+ function isCacheEnabled () {
22
+ const { isTrue } = require('../../util')
23
+ return isTrue(getValueFromEnvSources('DD_EXPERIMENTAL_TEST_REQUESTS_FS_CACHE'))
24
+ }
25
+
26
+ /**
27
+ * Builds a deterministic cache key by hashing arbitrary key-value parts.
28
+ *
29
+ * @param {string} prefix - Cache file prefix (e.g. 'known-tests', 'skippable', 'test-mgmt')
30
+ * @param {Array<unknown>} parts - Values that uniquely identify the cached response
31
+ * @returns {string}
32
+ */
33
+ function buildCacheKey (prefix, parts) {
34
+ const hash = createHash('sha256').update(JSON.stringify(parts)).digest('hex').slice(0, 16)
35
+ return `${prefix}-${hash}`
36
+ }
37
+
38
+ /**
39
+ * @param {string} cacheKey
40
+ * @returns {string}
41
+ */
42
+ function getCachePath (cacheKey) {
43
+ return path.join(tmpdir(), `dd-${cacheKey}.json`)
44
+ }
45
+
46
+ /**
47
+ * @param {string} cacheKey
48
+ * @returns {string}
49
+ */
50
+ function getLockPath (cacheKey) {
51
+ return path.join(tmpdir(), `dd-${cacheKey}.lock`)
52
+ }
53
+
54
+ /**
55
+ * Attempts to read cached data from the filesystem.
56
+ *
57
+ * @param {string} cacheKey
58
+ * @returns {{ data: unknown } | undefined}
59
+ */
60
+ function readFromCache (cacheKey) {
61
+ const cachePath = getCachePath(cacheKey)
62
+ try {
63
+ const raw = fs.readFileSync(cachePath, 'utf8')
64
+ const parsed = JSON.parse(raw)
65
+ if (!Object.hasOwn(parsed, 'data')) {
66
+ log.debug('%s cache file has no data field, ignoring', cacheKey)
67
+ return
68
+ }
69
+ const { timestamp, data } = parsed
70
+ if (Date.now() - timestamp > CACHE_TTL_MS) {
71
+ log.debug('%s cache expired (age: %d ms)', cacheKey, Date.now() - timestamp)
72
+ return
73
+ }
74
+ log.debug('%s cache hit', cacheKey)
75
+ return { data }
76
+ } catch {
77
+ // Cache file missing, corrupt, or unreadable — treat as cache miss
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Writes data to the filesystem cache atomically.
83
+ *
84
+ * @param {string} cacheKey
85
+ * @param {unknown} data
86
+ */
87
+ function writeToCache (cacheKey, data) {
88
+ if (!cacheKey) return
89
+ const cachePath = getCachePath(cacheKey)
90
+ const tmpPath = cachePath + '.tmp.' + process.pid
91
+ try {
92
+ fs.writeFileSync(tmpPath, JSON.stringify({ timestamp: Date.now(), data }), 'utf8')
93
+ fs.renameSync(tmpPath, cachePath)
94
+ log.debug('Cache written: %s', cachePath)
95
+ } catch (err) {
96
+ log.error('Failed to write cache %s: %s', cacheKey, err.message)
97
+ try { fs.unlinkSync(tmpPath) } catch { /* ignore */ }
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Attempts to acquire an exclusive lock using O_CREAT|O_EXCL.
103
+ *
104
+ * @param {string} cacheKey
105
+ * @returns {boolean}
106
+ */
107
+ function tryAcquireLock (cacheKey) {
108
+ const lockPath = getLockPath(cacheKey)
109
+ try {
110
+ const fd = fs.openSync(lockPath, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_WRONLY)
111
+ fs.writeSync(fd, String(Date.now()))
112
+ fs.closeSync(fd)
113
+ return true
114
+ } catch {
115
+ return false
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Removes the lock file.
121
+ *
122
+ * @param {string} cacheKey
123
+ */
124
+ function releaseLock (cacheKey) {
125
+ try { fs.unlinkSync(getLockPath(cacheKey)) } catch { /* ignore */ }
126
+ }
127
+
128
+ /**
129
+ * Updates the lock file timestamp so waiters know the owner is still alive.
130
+ *
131
+ * @param {string} cacheKey
132
+ */
133
+ function touchLock (cacheKey) {
134
+ const lockPath = getLockPath(cacheKey)
135
+ const tmpPath = lockPath + '.tmp.' + process.pid
136
+ try {
137
+ fs.writeFileSync(tmpPath, String(Date.now()))
138
+ fs.renameSync(tmpPath, lockPath)
139
+ } catch {
140
+ try { fs.unlinkSync(tmpPath) } catch { /* ignore */ }
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Starts a periodic heartbeat that touches the lock file.
146
+ * Returns a function that stops the heartbeat and releases the lock.
147
+ *
148
+ * @param {string} cacheKey
149
+ * @returns {Function}
150
+ */
151
+ function startLockHeartbeat (cacheKey) {
152
+ const interval = setInterval(() => touchLock(cacheKey), CACHE_LOCK_HEARTBEAT_MS)
153
+ interval.unref()
154
+ return () => {
155
+ clearInterval(interval)
156
+ try { fs.unlinkSync(getLockPath(cacheKey)) } catch { /* ignore */ }
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Checks whether the lock file is stale (older than the lock timeout).
162
+ *
163
+ * @param {string} cacheKey
164
+ * @returns {boolean}
165
+ */
166
+ function isLockStale (cacheKey) {
167
+ try {
168
+ const content = fs.readFileSync(getLockPath(cacheKey), 'utf8')
169
+ return Date.now() - Number(content) > CACHE_LOCK_TIMEOUT_MS
170
+ } catch {
171
+ return true
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Polls until the cache file appears or the timeout is reached.
177
+ *
178
+ * @param {string} cacheKey
179
+ * @param {Function} fetchFn - function(done) that fetches from the API
180
+ * @param {Function} done - callback(err, ...results)
181
+ */
182
+ function waitForCache (cacheKey, fetchFn, done) {
183
+ const poll = () => {
184
+ const cached = readFromCache(cacheKey)
185
+ if (cached) {
186
+ return done(null, cached.data)
187
+ }
188
+ if (isLockStale(cacheKey)) {
189
+ log.debug('%s lock is stale, attempting takeover', cacheKey)
190
+ releaseLock(cacheKey)
191
+ if (!tryAcquireLock(cacheKey)) {
192
+ return setTimeout(poll, CACHE_LOCK_POLL_MS)
193
+ }
194
+
195
+ const cachedAfterTakeover = readFromCache(cacheKey)
196
+ if (cachedAfterTakeover) {
197
+ releaseLock(cacheKey)
198
+ return done(null, cachedAfterTakeover.data)
199
+ }
200
+
201
+ const stopHeartbeat = startLockHeartbeat(cacheKey)
202
+ return fetchFn((err, ...results) => {
203
+ stopHeartbeat()
204
+ done(err, ...results)
205
+ })
206
+ }
207
+ setTimeout(poll, CACHE_LOCK_POLL_MS)
208
+ }
209
+ poll()
210
+ }
211
+
212
+ /**
213
+ * Wraps a fetch function with filesystem-based caching and cross-process deduplication.
214
+ *
215
+ * When cache is disabled (env var not set), calls fetchFn directly.
216
+ * When enabled, checks cache → acquires lock → fetches → writes cache → releases lock.
217
+ *
218
+ * @param {string} cacheKey - Unique cache key for this request
219
+ * @param {Function} fetchFn - function(cacheKey, done) that performs the API request.
220
+ * Must call writeToCache(cacheKey, data) on success before calling done(null, data).
221
+ * @param {Function} done - callback(err, ...results)
222
+ */
223
+ function withCache (cacheKey, fetchFn, done) {
224
+ if (!isCacheEnabled()) {
225
+ return fetchFn(null, done)
226
+ }
227
+
228
+ // Fast path: cache hit
229
+ const cached = readFromCache(cacheKey)
230
+ if (cached) {
231
+ return done(null, cached.data)
232
+ }
233
+
234
+ // Try to become the fetcher (lock owner)
235
+ const isLockOwner = tryAcquireLock(cacheKey)
236
+
237
+ if (!isLockOwner) {
238
+ log.debug('%s lock held by another process, waiting for cache', cacheKey)
239
+ return waitForCache(cacheKey, (cb) => fetchFn(cacheKey, cb), done)
240
+ }
241
+
242
+ // This process owns the lock — start heartbeat and fetch
243
+ const stopHeartbeat = startLockHeartbeat(cacheKey)
244
+
245
+ fetchFn(cacheKey, (err, ...results) => {
246
+ stopHeartbeat()
247
+ done(err, ...results)
248
+ })
249
+ }
250
+
251
+ module.exports = {
252
+ isCacheEnabled,
253
+ buildCacheKey,
254
+ readFromCache,
255
+ writeToCache,
256
+ withCache,
257
+ getCachePath,
258
+ getLockPath,
259
+ }
@@ -15,6 +15,8 @@ const {
15
15
  TELEMETRY_TEST_MANAGEMENT_TESTS_RESPONSE_BYTES,
16
16
  } = require('../telemetry')
17
17
 
18
+ const { buildCacheKey, writeToCache, withCache } = require('../requests/fs-cache')
19
+
18
20
  // Calculate the number of tests from the test management tests response, which has a shape like:
19
21
  // { module: { suites: { suite: { tests: { testName: { properties: {...} } } } } } }
20
22
  function getNumFromTestManagementTests (testManagementTests) {
@@ -48,6 +50,58 @@ function getTestManagementTests ({
48
50
  commitHeadSha,
49
51
  commitHeadMessage,
50
52
  branch,
53
+ }, done) {
54
+ const effectiveSha = commitHeadSha || sha
55
+ const cacheKey = buildCacheKey('test-mgmt', [
56
+ effectiveSha, repositoryUrl, branch,
57
+ ])
58
+
59
+ withCache(cacheKey, (activeCacheKey, cb) => {
60
+ fetchFromApi({
61
+ url,
62
+ isEvpProxy,
63
+ evpProxyPrefix,
64
+ isGzipCompatible,
65
+ repositoryUrl,
66
+ commitMessage,
67
+ sha,
68
+ commitHeadSha,
69
+ commitHeadMessage,
70
+ branch,
71
+ cacheKey: activeCacheKey,
72
+ }, cb)
73
+ }, done)
74
+ }
75
+
76
+ /**
77
+ * Fetches test management tests from the API and writes the result to cache on success.
78
+ *
79
+ * @param {object} params
80
+ * @param {string} params.url
81
+ * @param {boolean} params.isEvpProxy
82
+ * @param {string} params.evpProxyPrefix
83
+ * @param {boolean} params.isGzipCompatible
84
+ * @param {string} params.repositoryUrl
85
+ * @param {string} [params.commitMessage]
86
+ * @param {string} params.sha
87
+ * @param {string} [params.commitHeadSha]
88
+ * @param {string} [params.commitHeadMessage]
89
+ * @param {string} [params.branch]
90
+ * @param {string | null} params.cacheKey
91
+ * @param {Function} done
92
+ */
93
+ function fetchFromApi ({
94
+ url,
95
+ isEvpProxy,
96
+ evpProxyPrefix,
97
+ isGzipCompatible,
98
+ repositoryUrl,
99
+ commitMessage,
100
+ sha,
101
+ commitHeadSha,
102
+ commitHeadMessage,
103
+ branch,
104
+ cacheKey,
51
105
  }, done) {
52
106
  const options = {
53
107
  path: '/api/v2/test/libraries/test-management/tests',
@@ -110,6 +164,8 @@ function getTestManagementTests ({
110
164
 
111
165
  log.debug('Test management tests received: %j', testManagementTests)
112
166
 
167
+ writeToCache(cacheKey, testManagementTests)
168
+
113
169
  done(null, testManagementTests)
114
170
  } catch (err) {
115
171
  done(err)
@@ -0,0 +1,7 @@
1
+ import type { ConfigProperties } from './config-types'
2
+
3
+ declare class ConfigBase {}
4
+
5
+ interface ConfigBase extends ConfigProperties {}
6
+
7
+ export = ConfigBase
@@ -0,0 +1,5 @@
1
+ 'use strict'
2
+
3
+ class ConfigBase {}
4
+
5
+ module.exports = ConfigBase
@@ -0,0 +1,78 @@
1
+ import type { GeneratedConfig } from './generated-config-types'
2
+
3
+ type PayloadTaggingRules = ReturnType<typeof import('../payload-tagging/config').appendRules> | []
4
+
5
+ export interface ConfigProperties extends GeneratedConfig {
6
+ cloudPayloadTagging: GeneratedConfig['cloudPayloadTagging'] & {
7
+ requestsEnabled: boolean
8
+ responsesEnabled: boolean
9
+ rules: PayloadTaggingRules
10
+ }
11
+ commitSHA: string | undefined
12
+ debug: boolean
13
+ gcpPubSubPushSubscriptionEnabled: boolean
14
+ instrumentationSource: 'manual' | 'ssi'
15
+ isAzureFunction: boolean
16
+ isCiVisibility: boolean
17
+ isGCPFunction: boolean
18
+ isServiceNameInferred: boolean
19
+ isServiceUserProvided: boolean
20
+ logger: import('../../../../index').TracerOptions['logger'] | undefined
21
+ lookup: NonNullable<import('../../../../index').TracerOptions['lookup']>
22
+ readonly parsedDdTags: Record<string, string>
23
+ plugins: boolean
24
+ repositoryUrl: string | undefined
25
+ rules: import('../../../../index').SamplingRule[]
26
+ sampler: {
27
+ rateLimit: number
28
+ rules: import('../../../../index').SamplingRule[]
29
+ sampleRate: number | undefined
30
+ spanSamplingRules: import('../../../../index').SpanSamplingRule[] | undefined
31
+ }
32
+ stableConfig: {
33
+ fleetEntries: Record<string, string>
34
+ localEntries: Record<string, string>
35
+ warnings: string[] | undefined
36
+ }
37
+ tracePropagationStyle: GeneratedConfig['tracePropagationStyle']
38
+ }
39
+
40
+ type Primitive = bigint | boolean | null | number | string | symbol | undefined
41
+ type Terminal = Date | Function | Primitive | RegExp | URL
42
+
43
+ type KnownStringKeys<T> = Extract<{
44
+ [K in keyof T]:
45
+ K extends string
46
+ ? string extends K
47
+ ? never
48
+ : K
49
+ : never
50
+ }[keyof T], string>
51
+
52
+ type NestedConfigPath<T> = [NonNullable<T>] extends [Terminal]
53
+ ? never
54
+ : [NonNullable<T>] extends [readonly unknown[]]
55
+ ? never
56
+ : [NonNullable<T>] extends [object]
57
+ ? ConfigPathFor<NonNullable<T>>
58
+ : never
59
+
60
+ type ConfigPathFor<T> = {
61
+ [K in KnownStringKeys<T>]:
62
+ | K
63
+ | (NestedConfigPath<T[K]> extends never ? never : `${K}.${NestedConfigPath<T[K]>}`)
64
+ }[KnownStringKeys<T>]
65
+
66
+ type ConfigPathValueFor<T, TPath extends string> =
67
+ TPath extends `${infer TKey}.${infer TRest}`
68
+ ? TKey extends KnownStringKeys<T>
69
+ ? ConfigPathValueFor<NonNullable<T[TKey]>, TRest>
70
+ : never
71
+ : TPath extends KnownStringKeys<T>
72
+ ? T[TPath]
73
+ : never
74
+
75
+ export type ConfigKey = KnownStringKeys<ConfigProperties>
76
+ export type ConfigPath = ConfigPathFor<ConfigProperties>
77
+ export type ConfigPathValue<TPath extends ConfigPath> = ConfigPathValueFor<ConfigProperties, TPath>
78
+ export type ConfigDefaults = Partial<{ [TPath in ConfigPath]: ConfigPathValue<TPath> }>