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