dd-trace 6.4.0 → 6.6.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/README.electron.md +7 -0
- package/README.md +17 -0
- package/ci/diagnose.js +2100 -0
- package/ci/init.js +23 -2
- package/ci/runbook.md +198 -0
- package/ci/test-optimization-validation/approval-artifacts.js +143 -0
- package/ci/test-optimization-validation/approval.js +299 -0
- package/ci/test-optimization-validation/artifact-id.js +20 -0
- package/ci/test-optimization-validation/bounded-json.js +121 -0
- package/ci/test-optimization-validation/ci-command-candidate.js +83 -0
- package/ci/test-optimization-validation/ci-discovery.js +181 -0
- package/ci/test-optimization-validation/ci-remediation.js +210 -0
- package/ci/test-optimization-validation/cli.js +903 -0
- package/ci/test-optimization-validation/command-blocker.js +81 -0
- package/ci/test-optimization-validation/command-output-policy.js +206 -0
- package/ci/test-optimization-validation/command-runner.js +707 -0
- package/ci/test-optimization-validation/command-suitability.js +471 -0
- package/ci/test-optimization-validation/executable-approval.js +48 -0
- package/ci/test-optimization-validation/executable.js +480 -0
- package/ci/test-optimization-validation/generated-file-policy.js +63 -0
- package/ci/test-optimization-validation/generated-files.js +351 -0
- package/ci/test-optimization-validation/generated-verifier.js +196 -0
- package/ci/test-optimization-validation/init-probe-preload.js +163 -0
- package/ci/test-optimization-validation/init-probe.js +246 -0
- package/ci/test-optimization-validation/late-initialization.js +63 -0
- package/ci/test-optimization-validation/local-command.js +163 -0
- package/ci/test-optimization-validation/manifest-loader.js +133 -0
- package/ci/test-optimization-validation/manifest-scaffold.js +738 -0
- package/ci/test-optimization-validation/manifest-schema.js +862 -0
- package/ci/test-optimization-validation/offline-fixtures.js +327 -0
- package/ci/test-optimization-validation/offline-output.js +406 -0
- package/ci/test-optimization-validation/payload-normalizer.js +72 -0
- package/ci/test-optimization-validation/plan-writer.js +1120 -0
- package/ci/test-optimization-validation/preflight-runner.js +114 -0
- package/ci/test-optimization-validation/redaction.js +331 -0
- package/ci/test-optimization-validation/report-writer.js +1508 -0
- package/ci/test-optimization-validation/safe-files.js +203 -0
- package/ci/test-optimization-validation/scenarios/auto-test-retries.js +159 -0
- package/ci/test-optimization-validation/scenarios/basic-reporting.js +657 -0
- package/ci/test-optimization-validation/scenarios/ci-wiring.js +780 -0
- package/ci/test-optimization-validation/scenarios/early-flake-detection.js +141 -0
- package/ci/test-optimization-validation/scenarios/helpers.js +539 -0
- package/ci/test-optimization-validation/scenarios/test-management.js +218 -0
- package/ci/test-optimization-validation/setup-runner.js +97 -0
- package/ci/test-optimization-validation/static-diagnosis.js +159 -0
- package/ci/test-optimization-validation/test-output.js +129 -0
- package/ci/test-optimization-validation-manifest.schema.json +1 -0
- package/ci/validate-test-optimization.js +3 -0
- package/ext/exporters.js +1 -0
- package/index.d.ts +121 -8
- package/init.js +18 -0
- package/initialize.mjs +1 -1
- package/loader-hook.mjs +28 -18
- package/openfeature.d.ts +1 -0
- package/openfeature.js +4 -0
- package/package.json +11 -8
- package/packages/datadog-instrumentations/src/ai.js +8 -2
- package/packages/datadog-instrumentations/src/child_process.js +1 -1
- package/packages/datadog-instrumentations/src/claude-agent-sdk.js +1 -1
- package/packages/datadog-instrumentations/src/cucumber-worker-threads.js +9 -3
- package/packages/datadog-instrumentations/src/cucumber.js +31 -19
- package/packages/datadog-instrumentations/src/cypress-config.js +398 -52
- package/packages/datadog-instrumentations/src/fastify.js +7 -11
- package/packages/datadog-instrumentations/src/helpers/channel.js +74 -0
- package/packages/datadog-instrumentations/src/helpers/hook.js +27 -6
- package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +8 -8
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +28 -2
- package/packages/datadog-instrumentations/src/jest.js +44 -17
- package/packages/datadog-instrumentations/src/mariadb.js +1 -1
- package/packages/datadog-instrumentations/src/mocha/main.js +79 -32
- package/packages/datadog-instrumentations/src/mocha/utils.js +0 -4
- package/packages/datadog-instrumentations/src/mongodb.js +3 -3
- package/packages/datadog-instrumentations/src/mongoose.js +3 -3
- package/packages/datadog-instrumentations/src/mquery.js +2 -2
- package/packages/datadog-instrumentations/src/mysql.js +1 -1
- package/packages/datadog-instrumentations/src/next.js +24 -0
- package/packages/datadog-instrumentations/src/nyc.js +0 -2
- package/packages/datadog-instrumentations/src/oracledb.js +2 -2
- package/packages/datadog-instrumentations/src/pg.js +2 -2
- package/packages/datadog-instrumentations/src/playwright.js +162 -46
- package/packages/datadog-instrumentations/src/process.js +3 -0
- package/packages/datadog-instrumentations/src/router.js +18 -8
- package/packages/datadog-instrumentations/src/selenium.js +52 -26
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +3 -3
- package/packages/datadog-instrumentations/src/vitest-main.js +14 -28
- package/packages/datadog-instrumentations/src/vitest-util.js +0 -7
- package/packages/datadog-instrumentations/src/vitest-worker.js +5 -8
- package/packages/datadog-instrumentations/src/ws.js +2 -1
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +2 -2
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +2 -1
- package/packages/datadog-plugin-aws-sdk/src/util.js +2 -2
- package/packages/datadog-plugin-cucumber/src/index.js +3 -2
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +19 -57
- package/packages/datadog-plugin-cypress/src/index.js +6 -1
- package/packages/datadog-plugin-cypress/src/support.js +92 -26
- package/packages/datadog-plugin-electron/src/ipc.js +1 -1
- package/packages/datadog-plugin-graphql/src/execute.js +87 -45
- package/packages/datadog-plugin-graphql/src/validate.js +2 -2
- package/packages/datadog-plugin-http2/src/client.js +1 -1
- package/packages/datadog-plugin-langchain/src/handlers/embedding.js +2 -1
- package/packages/datadog-plugin-langchain/src/handlers/language_models.js +2 -1
- package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
- package/packages/datadog-plugin-mocha/src/index.js +2 -1
- package/packages/datadog-plugin-playwright/src/index.js +181 -65
- package/packages/datadog-plugin-router/src/index.js +11 -2
- package/packages/datadog-plugin-selenium/src/index.js +5 -36
- package/packages/datadog-plugin-vitest/src/index.js +5 -5
- package/packages/datadog-plugin-ws/src/close.js +2 -1
- package/packages/datadog-plugin-ws/src/producer.js +2 -1
- package/packages/datadog-plugin-ws/src/receiver.js +2 -1
- package/packages/datadog-plugin-ws/src/server.js +2 -1
- package/packages/dd-trace/src/aiguard/sdk.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +2 -2
- package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +3 -1
- package/packages/dd-trace/src/azure_metadata.js +2 -1
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +93 -28
- package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/index.js +160 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +288 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/payload-projection.js +84 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +369 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +60 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +41 -3
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +7 -2
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +1 -1
- package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +6 -2
- package/packages/dd-trace/src/ci-visibility/rum.js +7 -0
- package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +4 -0
- package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +1 -1
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +137 -9
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +168 -8
- package/packages/dd-trace/src/ci-visibility/test-screenshot.js +90 -0
- package/packages/dd-trace/src/config/generated-config-types.d.ts +16 -0
- package/packages/dd-trace/src/config/helper.js +1 -0
- package/packages/dd-trace/src/config/index.js +10 -0
- package/packages/dd-trace/src/config/supported-configurations.json +66 -0
- package/packages/dd-trace/src/exporter.js +2 -0
- package/packages/dd-trace/src/exporters/common/client-library-headers.js +21 -0
- package/packages/dd-trace/src/exporters/common/request.js +59 -30
- package/packages/dd-trace/src/exporters/common/url.js +15 -1
- package/packages/dd-trace/src/feature-registry.js +10 -3
- package/packages/dd-trace/src/llmobs/experiments/client.js +113 -0
- package/packages/dd-trace/src/llmobs/experiments/dataset.js +154 -0
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +283 -0
- package/packages/dd-trace/src/llmobs/experiments/index.js +152 -0
- package/packages/dd-trace/src/llmobs/experiments/noop.js +30 -0
- package/packages/dd-trace/src/llmobs/experiments/result.js +34 -0
- package/packages/dd-trace/src/llmobs/noop.js +6 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +20 -3
- package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +2 -2
- package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/openai/constants.js +8 -0
- package/packages/dd-trace/src/llmobs/plugins/openai/index.js +48 -14
- package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +43 -0
- package/packages/dd-trace/src/llmobs/sdk.js +17 -0
- package/packages/dd-trace/src/llmobs/tagger.js +68 -0
- package/packages/dd-trace/src/llmobs/telemetry.js +2 -1
- package/packages/dd-trace/src/llmobs/util.js +32 -0
- package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +322 -0
- package/packages/dd-trace/src/openfeature/configuration_source.js +90 -0
- package/packages/dd-trace/src/openfeature/flagging_provider.js +14 -23
- package/packages/dd-trace/src/openfeature/index.js +0 -2
- package/packages/dd-trace/src/openfeature/noop.js +1 -28
- package/packages/dd-trace/src/openfeature/register.js +16 -27
- package/packages/dd-trace/src/openfeature/remote_config.js +15 -18
- package/packages/dd-trace/src/openfeature/require-provider.js +18 -0
- package/packages/dd-trace/src/opentelemetry/span-ending-hook.js +10 -0
- package/packages/dd-trace/src/opentelemetry/span.js +5 -0
- package/packages/dd-trace/src/plugins/ci_plugin.js +20 -6
- package/packages/dd-trace/src/plugins/util/git.js +3 -2
- package/packages/dd-trace/src/plugins/util/inferred_proxy.js +29 -5
- package/packages/dd-trace/src/plugins/util/test.js +56 -4
- package/packages/dd-trace/src/profiling/profilers/space.js +1 -1
- package/packages/dd-trace/src/profiling/profilers/wall.js +1 -0
- package/packages/dd-trace/src/proxy.js +84 -7
- package/packages/dd-trace/src/span_processor.js +5 -0
- package/packages/dd-trace/src/standalone/index.js +0 -14
- package/packages/dd-trace/src/telemetry/send-data.js +2 -2
- package/packages/dd-trace/src/util.js +26 -0
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +1 -1
- package/vendor/dist/@datadog/sketches-js/index.js +1 -1
- package/vendor/dist/protobufjs/index.js +1 -1
- package/vendor/dist/protobufjs/minimal/index.js +1 -1
- package/vendor/dist/shell-quote/index.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/require-optional-peer.js +0 -17
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { randomUUID } = require('crypto')
|
|
3
4
|
const fs = require('fs')
|
|
4
|
-
const os = require('os')
|
|
5
5
|
const path = require('path')
|
|
6
6
|
const { pathToFileURL } = require('url')
|
|
7
|
+
|
|
8
|
+
const log = require('../../dd-trace/src/log')
|
|
9
|
+
const { getSegment } = require('../../dd-trace/src/util')
|
|
7
10
|
const { channel } = require('./helpers/instrument')
|
|
8
11
|
|
|
9
|
-
const DD_CONFIG_WRAPPED = Symbol('dd-trace.cypress.config.wrapped')
|
|
12
|
+
const DD_CONFIG_WRAPPED = Symbol.for('dd-trace.cypress.config.wrapped')
|
|
13
|
+
const BROWSER_INSTRUMENTATION_NOT_INSTALLED =
|
|
14
|
+
'Browser-side Cypress Test Optimization instrumentation was not installed.'
|
|
15
|
+
const CONFIG_INSTRUMENTATION_NOT_INSTALLED =
|
|
16
|
+
'Cypress configurations that cannot be intercepted through cypress.defineConfig were not auto-instrumented.'
|
|
17
|
+
const generatedFilesForExitCleanup = new Set()
|
|
18
|
+
let exitCleanupRegistered = false
|
|
10
19
|
|
|
11
20
|
const setupNodeEventsCh = channel('ci:cypress:setup-node-events')
|
|
12
21
|
|
|
@@ -27,6 +36,154 @@ const noopTask = {
|
|
|
27
36
|
'dd:log': () => null,
|
|
28
37
|
}
|
|
29
38
|
|
|
39
|
+
/** @typedef {Error & { code?: string, path?: string, syscall?: string }} FileSystemError */
|
|
40
|
+
/** @typedef {{ directory: string, error: FileSystemError }} FileCreationFailure */
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @param {FileSystemError} error filesystem error
|
|
44
|
+
* @param {string} fallbackPath path used when the error does not include one
|
|
45
|
+
* @returns {string} concise error description
|
|
46
|
+
*/
|
|
47
|
+
function formatFileSystemError (error, fallbackPath) {
|
|
48
|
+
const code = error?.code || error?.name || 'UNKNOWN'
|
|
49
|
+
const syscall = error?.syscall ? ` during ${error.syscall}` : ''
|
|
50
|
+
return `${code}${syscall} at ${error?.path || fallbackPath}`
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param {string} artifact artifact that could not be created
|
|
55
|
+
* @param {FileCreationFailure[]} failures failed directory attempts
|
|
56
|
+
* @param {string} consequence effect on Cypress instrumentation
|
|
57
|
+
* @param {boolean} [customerVisible] whether to report the failure without requiring debug logging
|
|
58
|
+
* @returns {void}
|
|
59
|
+
*/
|
|
60
|
+
function warnFileCreationFailures (artifact, failures, consequence, customerVisible = false) {
|
|
61
|
+
const details = failures.map(({ directory, error }) => formatFileSystemError(error, directory)).join('; ')
|
|
62
|
+
const message = 'Datadog could not create %s. Attempts failed: %s. %s'
|
|
63
|
+
|
|
64
|
+
if (customerVisible) {
|
|
65
|
+
// eslint-disable-next-line no-console
|
|
66
|
+
console.error('ERROR: ' + message, artifact, details, consequence)
|
|
67
|
+
} else {
|
|
68
|
+
log.warn(message, artifact, details, consequence)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Reports a definitive browser-instrumentation failure even when dd-trace debug logging is disabled.
|
|
74
|
+
*
|
|
75
|
+
* @param {string} message printf-style error message
|
|
76
|
+
* @param {...unknown} args message arguments
|
|
77
|
+
* @returns {void}
|
|
78
|
+
*/
|
|
79
|
+
function logBrowserInstrumentationError (message, ...args) {
|
|
80
|
+
// eslint-disable-next-line no-console
|
|
81
|
+
console.error('ERROR: ' + message, ...args)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @param {string} filePath generated file to remove
|
|
86
|
+
* @returns {void}
|
|
87
|
+
*/
|
|
88
|
+
function removeGeneratedFile (filePath) {
|
|
89
|
+
try {
|
|
90
|
+
fs.unlinkSync(filePath)
|
|
91
|
+
} catch (error) {
|
|
92
|
+
if (error?.code !== 'ENOENT') {
|
|
93
|
+
log.warn(
|
|
94
|
+
'Datadog could not remove generated Cypress file %s: %s.',
|
|
95
|
+
filePath,
|
|
96
|
+
formatFileSystemError(error, filePath)
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Removes project-local support files when Cypress exits without firing
|
|
104
|
+
* after:run, as happens in open mode without experimental run events.
|
|
105
|
+
*
|
|
106
|
+
* @returns {void}
|
|
107
|
+
*/
|
|
108
|
+
function removeGeneratedFilesAtExit () {
|
|
109
|
+
exitCleanupRegistered = false
|
|
110
|
+
for (const filePath of generatedFilesForExitCleanup) removeGeneratedFile(filePath)
|
|
111
|
+
generatedFilesForExitCleanup.clear()
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @param {string[]} filePaths generated support files
|
|
116
|
+
* @returns {void}
|
|
117
|
+
*/
|
|
118
|
+
function registerGeneratedFilesForExitCleanup (filePaths) {
|
|
119
|
+
for (const filePath of filePaths) generatedFilesForExitCleanup.add(filePath)
|
|
120
|
+
if (exitCleanupRegistered) return
|
|
121
|
+
|
|
122
|
+
exitCleanupRegistered = true
|
|
123
|
+
process.once('exit', removeGeneratedFilesAtExit)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* @param {string[]} filePaths generated support files
|
|
128
|
+
* @returns {void}
|
|
129
|
+
*/
|
|
130
|
+
function cleanupGeneratedFiles (filePaths) {
|
|
131
|
+
for (const filePath of filePaths) {
|
|
132
|
+
generatedFilesForExitCleanup.delete(filePath)
|
|
133
|
+
removeGeneratedFile(filePath)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (generatedFilesForExitCleanup.size === 0 && exitCleanupRegistered) {
|
|
137
|
+
process.removeListener('exit', removeGeneratedFilesAtExit)
|
|
138
|
+
exitCleanupRegistered = false
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Writes a new file without following or overwriting an existing path. If the
|
|
144
|
+
* write fails after creation, removes the partial file before rethrowing.
|
|
145
|
+
*
|
|
146
|
+
* @param {string} filePath generated file path
|
|
147
|
+
* @param {string} content generated file content
|
|
148
|
+
* @returns {void}
|
|
149
|
+
*/
|
|
150
|
+
function writeExclusiveFile (filePath, content) {
|
|
151
|
+
let descriptor
|
|
152
|
+
let operationError
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
descriptor = fs.openSync(filePath, 'wx')
|
|
156
|
+
fs.writeFileSync(descriptor, content)
|
|
157
|
+
} catch (error) {
|
|
158
|
+
operationError = error
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (descriptor !== undefined) {
|
|
162
|
+
try {
|
|
163
|
+
fs.closeSync(descriptor)
|
|
164
|
+
} catch (error) {
|
|
165
|
+
if (!operationError) operationError = error
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (operationError) {
|
|
170
|
+
if (descriptor !== undefined) removeGeneratedFile(filePath)
|
|
171
|
+
throw operationError
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* @param {unknown} handler Cypress task registration
|
|
177
|
+
* @returns {boolean}
|
|
178
|
+
*/
|
|
179
|
+
function isDatadogTaskRegistration (handler) {
|
|
180
|
+
return !!handler && typeof handler === 'object' &&
|
|
181
|
+
typeof handler['dd:testSuiteStart'] === 'function' &&
|
|
182
|
+
typeof handler['dd:beforeEach'] === 'function' &&
|
|
183
|
+
typeof handler['dd:afterEach'] === 'function' &&
|
|
184
|
+
typeof handler['dd:addTags'] === 'function'
|
|
185
|
+
}
|
|
186
|
+
|
|
30
187
|
/**
|
|
31
188
|
* @param {unknown} value
|
|
32
189
|
* @returns {boolean}
|
|
@@ -63,46 +220,190 @@ function mergeReturnedConfig (config, updatedConfig) {
|
|
|
63
220
|
}
|
|
64
221
|
|
|
65
222
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
223
|
+
* @param {string} rootPath parent path
|
|
224
|
+
* @param {string} candidatePath path that should be inside rootPath
|
|
225
|
+
* @returns {boolean}
|
|
226
|
+
*/
|
|
227
|
+
function isPathInside (rootPath, candidatePath) {
|
|
228
|
+
const relativePath = path.relative(path.resolve(rootPath), path.resolve(candidatePath))
|
|
229
|
+
return relativePath === '' || (!relativePath.startsWith(`..${path.sep}`) && relativePath !== '..')
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* @param {string} fromDirectory directory containing the importing file
|
|
234
|
+
* @param {string} importedFile file to import
|
|
235
|
+
* @returns {string}
|
|
236
|
+
*/
|
|
237
|
+
function getRelativeImportPath (fromDirectory, importedFile) {
|
|
238
|
+
let relativePath = path.relative(fromDirectory, importedFile).split(path.sep).join('/')
|
|
239
|
+
if (!relativePath.startsWith('./') && !relativePath.startsWith('../')) {
|
|
240
|
+
relativePath = `./${relativePath}`
|
|
241
|
+
}
|
|
242
|
+
return relativePath
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Creates project-local support files that Cypress's E2E and component
|
|
247
|
+
* bundlers can both serve. The browser hook is copied because an action-style
|
|
248
|
+
* NODE_OPTIONS preload can live outside Vite's allowed filesystem roots.
|
|
249
|
+
*
|
|
250
|
+
* @param {string} directory writable directory inside the Cypress project
|
|
251
|
+
* @param {string|false|undefined} originalSupportFile user's support file
|
|
252
|
+
* @param {string} browserHooksSource Datadog browser-side support hooks
|
|
253
|
+
* @returns {string[]} generated files
|
|
254
|
+
*/
|
|
255
|
+
function createSupportWrapper (directory, originalSupportFile, browserHooksSource) {
|
|
256
|
+
const suffix = `${process.pid}-${randomUUID()}`
|
|
257
|
+
const browserHooksFile = path.join(directory, `dd-cypress-support-hooks-${suffix}.mjs`)
|
|
258
|
+
const wrapperFile = path.join(directory, `dd-cypress-support-${suffix}.mjs`)
|
|
259
|
+
const wrapperImports = [
|
|
260
|
+
`import ${JSON.stringify(getRelativeImportPath(directory, browserHooksFile))}`,
|
|
261
|
+
]
|
|
262
|
+
|
|
263
|
+
if (originalSupportFile) {
|
|
264
|
+
wrapperImports.push(`import ${JSON.stringify(getRelativeImportPath(directory, originalSupportFile))}`)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const generatedFiles = []
|
|
268
|
+
try {
|
|
269
|
+
writeExclusiveFile(browserHooksFile, browserHooksSource)
|
|
270
|
+
generatedFiles.push(browserHooksFile)
|
|
271
|
+
writeExclusiveFile(wrapperFile, `${wrapperImports.join('\n')}\n`)
|
|
272
|
+
generatedFiles.push(wrapperFile)
|
|
273
|
+
} catch (error) {
|
|
274
|
+
for (const generatedFile of generatedFiles) removeGeneratedFile(generatedFile)
|
|
275
|
+
throw error
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return generatedFiles
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Creates temporary project-local support files that load dd-trace's
|
|
283
|
+
* browser-side hooks before the user's original support file. Returns the
|
|
284
|
+
* generated paths for cleanup, or undefined if injection was skipped.
|
|
69
285
|
*
|
|
70
286
|
* @param {object} config Cypress resolved config object
|
|
71
|
-
* @returns {string|undefined}
|
|
287
|
+
* @returns {string[]|undefined} generated file paths, or undefined if skipped
|
|
72
288
|
*/
|
|
73
289
|
function injectSupportFile (config) {
|
|
74
290
|
const originalSupportFile = config.supportFile
|
|
75
|
-
if (!originalSupportFile || originalSupportFile === false) return
|
|
76
291
|
|
|
292
|
+
if (originalSupportFile) {
|
|
293
|
+
try {
|
|
294
|
+
const content = fs.readFileSync(originalSupportFile, 'utf8')
|
|
295
|
+
// Naive check: skip lines starting with // or * to avoid matching commented-out imports.
|
|
296
|
+
const hasActiveDdTraceImport = content.split('\n').some(line => {
|
|
297
|
+
const trimmed = line.trim()
|
|
298
|
+
return trimmed.includes('dd-trace/ci/cypress/support') &&
|
|
299
|
+
!trimmed.startsWith('//') && !trimmed.startsWith('*')
|
|
300
|
+
})
|
|
301
|
+
if (hasActiveDdTraceImport) return
|
|
302
|
+
} catch (error) {
|
|
303
|
+
logBrowserInstrumentationError(
|
|
304
|
+
'Datadog could not read the Cypress support file %s: %s. %s',
|
|
305
|
+
originalSupportFile,
|
|
306
|
+
formatFileSystemError(error, originalSupportFile),
|
|
307
|
+
BROWSER_INSTRUMENTATION_NOT_INSTALLED
|
|
308
|
+
)
|
|
309
|
+
return
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
let browserHooksSource
|
|
314
|
+
let browserHooksPath
|
|
77
315
|
try {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
} catch {
|
|
316
|
+
browserHooksPath = require.resolve('../../datadog-plugin-cypress/src/support')
|
|
317
|
+
browserHooksSource = fs.readFileSync(browserHooksPath, 'utf8')
|
|
318
|
+
} catch (error) {
|
|
319
|
+
logBrowserInstrumentationError(
|
|
320
|
+
'Datadog could not read its Cypress browser support hooks: %s. %s',
|
|
321
|
+
formatFileSystemError(error, browserHooksPath || 'dd-trace Cypress browser support hooks'),
|
|
322
|
+
BROWSER_INSTRUMENTATION_NOT_INSTALLED
|
|
323
|
+
)
|
|
87
324
|
return
|
|
88
325
|
}
|
|
89
326
|
|
|
90
|
-
const
|
|
91
|
-
const
|
|
327
|
+
const projectRoot = config.projectRoot
|
|
328
|
+
const candidateDirectories = []
|
|
329
|
+
if (originalSupportFile) candidateDirectories.push(path.dirname(originalSupportFile))
|
|
330
|
+
if (projectRoot) candidateDirectories.push(projectRoot)
|
|
331
|
+
const failures = []
|
|
92
332
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
333
|
+
for (const directory of new Set(candidateDirectories)) {
|
|
334
|
+
if (projectRoot && !isPathInside(projectRoot, directory)) continue
|
|
335
|
+
try {
|
|
336
|
+
const generatedFiles = createSupportWrapper(directory, originalSupportFile, browserHooksSource)
|
|
337
|
+
config.supportFile = generatedFiles[1]
|
|
338
|
+
return generatedFiles
|
|
339
|
+
} catch (error) {
|
|
340
|
+
failures.push({ directory, error })
|
|
341
|
+
// Try the next directory inside the project.
|
|
342
|
+
}
|
|
343
|
+
}
|
|
96
344
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
345
|
+
if (failures.length > 0) {
|
|
346
|
+
warnFileCreationFailures(
|
|
347
|
+
'the Cypress support wrapper',
|
|
348
|
+
failures,
|
|
349
|
+
BROWSER_INSTRUMENTATION_NOT_INSTALLED,
|
|
350
|
+
true
|
|
351
|
+
)
|
|
352
|
+
} else {
|
|
353
|
+
logBrowserInstrumentationError(
|
|
354
|
+
'Datadog could not create the Cypress support wrapper because no project directory was available. %s',
|
|
355
|
+
BROWSER_INSTRUMENTATION_NOT_INSTALLED
|
|
356
|
+
)
|
|
103
357
|
}
|
|
104
358
|
}
|
|
105
359
|
|
|
360
|
+
/**
|
|
361
|
+
* Registers screenshot handlers collected from a manual plugin call. User
|
|
362
|
+
* handlers run first so a renamed screenshot path reaches the Datadog handler.
|
|
363
|
+
*
|
|
364
|
+
* @param {Function} on Cypress event registration function
|
|
365
|
+
* @param {Function[]} handlers collected after:screenshot handlers
|
|
366
|
+
* @param {Function|undefined} datadogHandler manual Datadog screenshot handler
|
|
367
|
+
* @returns {void}
|
|
368
|
+
*/
|
|
369
|
+
function registerManualAfterScreenshotHandlers (on, handlers, datadogHandler) {
|
|
370
|
+
const userHandlers = handlers.filter(handler => handler !== datadogHandler)
|
|
371
|
+
if (userHandlers.length === 0) {
|
|
372
|
+
if (datadogHandler) on('after:screenshot', datadogHandler)
|
|
373
|
+
return
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
on('after:screenshot', (details) => {
|
|
377
|
+
const chain = userHandlers.reduce(
|
|
378
|
+
(promise, handler) => promise.then((latestDetails) => Promise.resolve(handler(latestDetails)).then(
|
|
379
|
+
returned => (returned == null ? latestDetails : { ...latestDetails, ...returned })
|
|
380
|
+
)),
|
|
381
|
+
Promise.resolve(details)
|
|
382
|
+
)
|
|
383
|
+
return chain.then((finalDetails) => {
|
|
384
|
+
if (!datadogHandler) return finalDetails
|
|
385
|
+
return Promise.resolve(datadogHandler(finalDetails)).then(() => finalDetails)
|
|
386
|
+
})
|
|
387
|
+
})
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Registers one Cypress after:spec handler that runs every collected handler
|
|
392
|
+
* in registration order. Cypress 10+ otherwise keeps only the last handler.
|
|
393
|
+
*
|
|
394
|
+
* @param {Function} on Cypress event registration function
|
|
395
|
+
* @param {Function[]} handlers collected after:spec handlers
|
|
396
|
+
* @returns {void}
|
|
397
|
+
*/
|
|
398
|
+
function registerAfterSpecHandlers (on, handlers) {
|
|
399
|
+
if (handlers.length === 0) return
|
|
400
|
+
|
|
401
|
+
on('after:spec', (spec, results) => handlers.reduce(
|
|
402
|
+
(chain, handler) => chain.then(() => handler(spec, results)),
|
|
403
|
+
Promise.resolve()
|
|
404
|
+
))
|
|
405
|
+
}
|
|
406
|
+
|
|
106
407
|
/**
|
|
107
408
|
* Registers dd-trace's Cypress hooks (before:run, after:spec, after:run, tasks)
|
|
108
409
|
* and injects the support file. Communicates with the plugin layer via
|
|
@@ -114,15 +415,22 @@ function injectSupportFile (config) {
|
|
|
114
415
|
* @param {Function[]} userAfterSpecHandlers user's after:spec handlers collected from wrappedOn
|
|
115
416
|
* @param {Function[]} userAfterRunHandlers user's after:run handlers collected from wrappedOn
|
|
116
417
|
* @param {Function[]} userAfterScreenshotHandlers user's after:screenshot handlers collected from wrappedOn
|
|
418
|
+
* @param {object} manualPlugin manual plugin registration state
|
|
117
419
|
* @returns {object} the config object (possibly modified)
|
|
118
420
|
*/
|
|
119
|
-
function registerDdTraceHooks (
|
|
120
|
-
|
|
421
|
+
function registerDdTraceHooks (
|
|
422
|
+
on,
|
|
423
|
+
config,
|
|
424
|
+
userAfterSpecHandlers,
|
|
425
|
+
userAfterRunHandlers,
|
|
426
|
+
userAfterScreenshotHandlers,
|
|
427
|
+
manualPlugin
|
|
428
|
+
) {
|
|
429
|
+
const generatedSupportFiles = injectSupportFile(config)
|
|
430
|
+
if (generatedSupportFiles) registerGeneratedFilesForExitCleanup(generatedSupportFiles)
|
|
121
431
|
|
|
122
432
|
const cleanupWrapper = () => {
|
|
123
|
-
if (
|
|
124
|
-
try { fs.unlinkSync(wrapperFile) } catch { /* best effort */ }
|
|
125
|
-
}
|
|
433
|
+
if (generatedSupportFiles) cleanupGeneratedFiles(generatedSupportFiles)
|
|
126
434
|
}
|
|
127
435
|
|
|
128
436
|
const registerAfterRunWithCleanup = () => {
|
|
@@ -136,12 +444,19 @@ function registerDdTraceHooks (on, config, userAfterSpecHandlers, userAfterRunHa
|
|
|
136
444
|
}
|
|
137
445
|
|
|
138
446
|
const registerNoopHandlers = () => {
|
|
139
|
-
|
|
447
|
+
registerAfterSpecHandlers(on, userAfterSpecHandlers)
|
|
140
448
|
for (const h of userAfterScreenshotHandlers) on('after:screenshot', h)
|
|
141
449
|
registerAfterRunWithCleanup()
|
|
142
450
|
on('task', noopTask)
|
|
143
451
|
}
|
|
144
452
|
|
|
453
|
+
if (manualPlugin.detected) {
|
|
454
|
+
registerAfterSpecHandlers(on, userAfterSpecHandlers)
|
|
455
|
+
registerManualAfterScreenshotHandlers(on, userAfterScreenshotHandlers, manualPlugin.afterScreenshotHandler)
|
|
456
|
+
registerAfterRunWithCleanup()
|
|
457
|
+
return config
|
|
458
|
+
}
|
|
459
|
+
|
|
145
460
|
if (!setupNodeEventsCh.hasSubscribers) {
|
|
146
461
|
registerNoopHandlers()
|
|
147
462
|
return config
|
|
@@ -180,6 +495,10 @@ function wrapSetupNodeEvents (originalSetupNodeEvents) {
|
|
|
180
495
|
const userAfterSpecHandlers = []
|
|
181
496
|
const userAfterRunHandlers = []
|
|
182
497
|
const userAfterScreenshotHandlers = []
|
|
498
|
+
const manualPlugin = {
|
|
499
|
+
detected: false,
|
|
500
|
+
afterScreenshotHandler: undefined,
|
|
501
|
+
}
|
|
183
502
|
|
|
184
503
|
const wrappedOn = (event, handler) => {
|
|
185
504
|
if (event === 'after:spec') {
|
|
@@ -189,6 +508,10 @@ function wrapSetupNodeEvents (originalSetupNodeEvents) {
|
|
|
189
508
|
} else if (event === 'after:screenshot') {
|
|
190
509
|
userAfterScreenshotHandlers.push(handler)
|
|
191
510
|
} else {
|
|
511
|
+
if (event === 'task' && isDatadogTaskRegistration(handler)) {
|
|
512
|
+
manualPlugin.detected = true
|
|
513
|
+
manualPlugin.afterScreenshotHandler = userAfterScreenshotHandlers.at(-1)
|
|
514
|
+
}
|
|
192
515
|
on(event, handler)
|
|
193
516
|
}
|
|
194
517
|
}
|
|
@@ -204,7 +527,8 @@ function wrapSetupNodeEvents (originalSetupNodeEvents) {
|
|
|
204
527
|
mergeReturnedConfig(config, result),
|
|
205
528
|
userAfterSpecHandlers,
|
|
206
529
|
userAfterRunHandlers,
|
|
207
|
-
userAfterScreenshotHandlers
|
|
530
|
+
userAfterScreenshotHandlers,
|
|
531
|
+
manualPlugin
|
|
208
532
|
)
|
|
209
533
|
})
|
|
210
534
|
}
|
|
@@ -214,7 +538,8 @@ function wrapSetupNodeEvents (originalSetupNodeEvents) {
|
|
|
214
538
|
mergeReturnedConfig(config, maybePromise),
|
|
215
539
|
userAfterSpecHandlers,
|
|
216
540
|
userAfterRunHandlers,
|
|
217
|
-
userAfterScreenshotHandlers
|
|
541
|
+
userAfterScreenshotHandlers,
|
|
542
|
+
manualPlugin
|
|
218
543
|
)
|
|
219
544
|
}
|
|
220
545
|
}
|
|
@@ -261,30 +586,32 @@ function isUnderEsmPackage (filePath) {
|
|
|
261
586
|
|
|
262
587
|
/**
|
|
263
588
|
* @param {string} originalConfigFile absolute path to the original config file
|
|
589
|
+
* @param {string} wrapperDirectory directory for the generated wrapper
|
|
264
590
|
* @returns {string} path to the generated wrapper file
|
|
265
591
|
*/
|
|
266
|
-
function createConfigWrapper (originalConfigFile) {
|
|
267
|
-
//
|
|
268
|
-
//
|
|
269
|
-
// keeps the loader it would have used (notably the ts-node registration
|
|
270
|
-
// for `.ts` configs), and (2) the wrapper body parses in that mode.
|
|
592
|
+
function createConfigWrapper (originalConfigFile, wrapperDirectory) {
|
|
593
|
+
// Match the module mode Cypress would use for the user's original config
|
|
594
|
+
// so the generated wrapper body parses and imports it correctly.
|
|
271
595
|
const originalExt = path.extname(originalConfigFile)
|
|
272
596
|
const isEsm = originalExt === '.mjs' || originalExt === '.mts' ||
|
|
273
597
|
(originalExt !== '.cjs' && originalExt !== '.cts' && isUnderEsmPackage(originalConfigFile))
|
|
274
598
|
|
|
275
|
-
// Preserve
|
|
276
|
-
//
|
|
277
|
-
//
|
|
599
|
+
// Preserve explicit TypeScript extensions. If an ambiguous `.ts` wrapper
|
|
600
|
+
// falls back across package scopes, make its original module mode explicit
|
|
601
|
+
// instead of inheriting the fallback scope.
|
|
278
602
|
let wrapperExt
|
|
279
|
-
if (originalExt === '.ts'
|
|
603
|
+
if (originalExt === '.ts') {
|
|
604
|
+
const wrapperIsEsm = isUnderEsmPackage(path.join(wrapperDirectory, 'wrapper.ts'))
|
|
605
|
+
wrapperExt = wrapperIsEsm === isEsm ? originalExt : (isEsm ? '.mts' : '.cts')
|
|
606
|
+
} else if (originalExt === '.cts' || originalExt === '.mts') {
|
|
280
607
|
wrapperExt = originalExt
|
|
281
608
|
} else {
|
|
282
609
|
wrapperExt = isEsm ? '.mjs' : '.cjs'
|
|
283
610
|
}
|
|
284
611
|
|
|
285
612
|
const wrapperFile = path.join(
|
|
286
|
-
|
|
287
|
-
`.dd-cypress-config-${process.pid}${wrapperExt}`
|
|
613
|
+
wrapperDirectory,
|
|
614
|
+
`.dd-cypress-config-${process.pid}-${randomUUID()}${wrapperExt}`
|
|
288
615
|
)
|
|
289
616
|
|
|
290
617
|
const cypressConfigPath = require.resolve('./cypress-config')
|
|
@@ -314,7 +641,7 @@ function createConfigWrapper (originalConfigFile) {
|
|
|
314
641
|
'',
|
|
315
642
|
].join('\n')
|
|
316
643
|
|
|
317
|
-
|
|
644
|
+
writeExclusiveFile(wrapperFile, body)
|
|
318
645
|
return wrapperFile
|
|
319
646
|
}
|
|
320
647
|
|
|
@@ -327,7 +654,7 @@ function isTypeScript6OrNewer (projectRoot) {
|
|
|
327
654
|
// eslint-disable-next-line n/no-unpublished-require
|
|
328
655
|
const packageJsonPath = require.resolve('typescript/package.json', { paths: [projectRoot] })
|
|
329
656
|
const { version } = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
|
|
330
|
-
const major = Number(String(version)
|
|
657
|
+
const major = Number(getSegment(String(version), '.', 0))
|
|
331
658
|
return major >= 6
|
|
332
659
|
} catch {
|
|
333
660
|
return false
|
|
@@ -408,20 +735,39 @@ function wrapCliConfigFileOptions (options) {
|
|
|
408
735
|
|
|
409
736
|
if (!configFilePath || !fs.existsSync(configFilePath)) return noop
|
|
410
737
|
|
|
738
|
+
let wrapperFile
|
|
739
|
+
const failures = []
|
|
740
|
+
for (const wrapperDirectory of new Set([path.dirname(configFilePath), projectRoot])) {
|
|
741
|
+
try {
|
|
742
|
+
wrapperFile = createConfigWrapper(configFilePath, wrapperDirectory)
|
|
743
|
+
break
|
|
744
|
+
} catch (error) {
|
|
745
|
+
failures.push({ directory: wrapperDirectory, error })
|
|
746
|
+
// Try the project root as the fallback location.
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
if (!wrapperFile) {
|
|
751
|
+
warnFileCreationFailures(
|
|
752
|
+
'the Cypress configuration wrapper',
|
|
753
|
+
failures,
|
|
754
|
+
CONFIG_INSTRUMENTATION_NOT_INSTALLED
|
|
755
|
+
)
|
|
756
|
+
return noop
|
|
757
|
+
}
|
|
758
|
+
|
|
411
759
|
try {
|
|
412
|
-
const wrapperFile = createConfigWrapper(configFilePath)
|
|
413
760
|
const restoreTsNodeCompilerOptions = configureTsNodeForTypeScript6(projectRoot, configFilePath)
|
|
414
761
|
|
|
415
762
|
return {
|
|
416
763
|
options: { ...options, configFile: wrapperFile },
|
|
417
764
|
cleanup: () => {
|
|
418
765
|
restoreTsNodeCompilerOptions()
|
|
419
|
-
|
|
766
|
+
removeGeneratedFile(wrapperFile)
|
|
420
767
|
},
|
|
421
768
|
}
|
|
422
769
|
} catch {
|
|
423
|
-
|
|
424
|
-
// The defineConfig shimmer will still handle configs that use defineConfig.
|
|
770
|
+
removeGeneratedFile(wrapperFile)
|
|
425
771
|
return noop
|
|
426
772
|
}
|
|
427
773
|
}
|
|
@@ -58,17 +58,11 @@ function wrapAddHook (addHook) {
|
|
|
58
58
|
if (typeof fn !== 'function') return addHook.apply(this, arguments)
|
|
59
59
|
|
|
60
60
|
arguments[arguments.length - 1] = shimmer.wrapFunction(fn, fn => function wrappedHook () {
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
// V8's CallApplyArguments fast path intact.
|
|
67
|
-
//
|
|
68
|
-
// The previous shape mutated `arguments[arguments.length - 1]` to swap `done`.
|
|
69
|
-
// That mutation materialises the magical arguments object and disables V8
|
|
70
|
-
// inlining of the enclosing function. The slow path below builds a fresh args
|
|
71
|
-
// array instead so the hot fast path keeps a clean forward.
|
|
61
|
+
// Every fastify request invokes each addHook'd handler, so this wrapper runs in the
|
|
62
|
+
// user's hot path. When none of the three channels below has a subscriber (the default
|
|
63
|
+
// plugin config, and the steady state once appsec / cookie subscribers detach), forward
|
|
64
|
+
// `arguments` untouched: no args array is materialised and V8's CallApplyArguments fast
|
|
65
|
+
// path stays intact. The slow path copies/indexes the args only when it has work to do.
|
|
72
66
|
if (errorChannel.hasSubscribers || cookieParserReadCh.hasSubscribers || callbackFinishCh.hasSubscribers) {
|
|
73
67
|
return invokeHookWithContext(name, fn, this, arguments)
|
|
74
68
|
}
|
|
@@ -98,6 +92,8 @@ function invokeHookWithContext (name, fn, thisArg, args) {
|
|
|
98
92
|
const ctx = { req }
|
|
99
93
|
|
|
100
94
|
try {
|
|
95
|
+
// `args` is the wrapper's `arguments` object, which has no `Array#at`.
|
|
96
|
+
// eslint-disable-next-line unicorn/prefer-at
|
|
101
97
|
const lastArg = args[args.length - 1]
|
|
102
98
|
|
|
103
99
|
if (typeof lastArg === 'function') {
|