dd-trace 5.116.0 → 5.118.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ci/diagnose.js +162 -21
- package/ci/init.js +0 -1
- package/ci/runbook.md +187 -180
- package/ci/test-optimization-validation/approval-artifacts.js +3 -1
- package/ci/test-optimization-validation/approval.js +52 -15
- package/ci/test-optimization-validation/ci-command-candidate.js +26 -32
- package/ci/test-optimization-validation/ci-discovery.js +1 -1
- package/ci/test-optimization-validation/ci-package-scripts.js +141 -0
- package/ci/test-optimization-validation/ci-remediation.js +112 -39
- package/ci/test-optimization-validation/cli.js +516 -681
- package/ci/test-optimization-validation/command-blocker.js +188 -33
- package/ci/test-optimization-validation/command-output-policy.js +3 -26
- package/ci/test-optimization-validation/command-runner.js +189 -245
- package/ci/test-optimization-validation/environment.js +90 -0
- package/ci/test-optimization-validation/executable.js +159 -388
- package/ci/test-optimization-validation/framework-adapters/cucumber.js +119 -0
- package/ci/test-optimization-validation/framework-adapters/cypress.js +107 -0
- package/ci/test-optimization-validation/framework-adapters/playwright.js +181 -0
- package/ci/test-optimization-validation/generated-files.js +75 -13
- package/ci/test-optimization-validation/generated-test-contract.js +283 -0
- package/ci/test-optimization-validation/generated-verifier.js +49 -16
- package/ci/test-optimization-validation/literal-environment.js +57 -0
- package/ci/test-optimization-validation/manifest-loader.js +2 -22
- package/ci/test-optimization-validation/manifest-scaffold.js +816 -515
- package/ci/test-optimization-validation/manifest-schema.js +494 -702
- package/ci/test-optimization-validation/offline-fixtures.js +4 -1
- package/ci/test-optimization-validation/plan-writer.js +243 -984
- package/ci/test-optimization-validation/preflight-runner.js +49 -55
- package/ci/test-optimization-validation/redaction.js +2 -1
- package/ci/test-optimization-validation/report-writer.js +391 -1357
- package/ci/test-optimization-validation/result-semantics.js +86 -0
- package/ci/test-optimization-validation/runner-command.js +249 -0
- package/ci/test-optimization-validation/runner-contract.js +664 -0
- package/ci/test-optimization-validation/scenarios/auto-test-retries.js +29 -3
- package/ci/test-optimization-validation/scenarios/basic-reporting.js +240 -543
- package/ci/test-optimization-validation/scenarios/ci-wiring.js +450 -670
- package/ci/test-optimization-validation/scenarios/early-flake-detection.js +4 -3
- package/ci/test-optimization-validation/scenarios/helpers.js +67 -9
- package/ci/test-optimization-validation/scenarios/test-management.js +4 -3
- package/ci/test-optimization-validation/source-text.js +87 -0
- package/ci/test-optimization-validation/test-output.js +12 -22
- package/ext/tags.d.ts +1 -0
- package/index.d.ts +7 -0
- package/package.json +3 -3
- package/packages/datadog-instrumentations/src/cucumber.js +11 -12
- package/packages/datadog-instrumentations/src/helpers/channel.js +74 -0
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/openai-agents.js +31 -0
- package/packages/datadog-instrumentations/src/jest.js +18 -17
- package/packages/datadog-instrumentations/src/mocha/main.js +79 -30
- package/packages/datadog-instrumentations/src/openai-agents.js +159 -0
- package/packages/datadog-instrumentations/src/playwright.js +3 -24
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +1 -1
- package/packages/datadog-instrumentations/src/vitest-main.js +14 -26
- package/packages/datadog-instrumentations/src/vitest-util.js +0 -7
- package/packages/datadog-instrumentations/src/vitest-worker.js +5 -8
- package/packages/datadog-plugin-cucumber/src/index.js +2 -1
- package/packages/datadog-plugin-mocha/src/index.js +2 -1
- package/packages/datadog-plugin-openai-agents/src/index.js +74 -0
- package/packages/datadog-plugin-openai-agents/src/integration.js +503 -0
- package/packages/datadog-plugin-openai-agents/src/processor.js +108 -0
- package/packages/datadog-plugin-openai-agents/src/util.js +60 -0
- package/packages/datadog-plugin-playwright/src/index.js +4 -1
- package/packages/datadog-plugin-vitest/src/index.js +5 -5
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +48 -49
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +161 -33
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +6 -4
- package/packages/dd-trace/src/config/generated-config-types.d.ts +2 -0
- package/packages/dd-trace/src/config/supported-configurations.json +7 -0
- package/packages/dd-trace/src/encode/span-stats.js +7 -1
- package/packages/dd-trace/src/exporter.js +16 -9
- package/packages/dd-trace/src/llmobs/index.js +9 -1
- package/packages/dd-trace/src/llmobs/plugins/ai/util.js +104 -22
- package/packages/dd-trace/src/llmobs/plugins/openai/index.js +5 -9
- package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +20 -0
- package/packages/dd-trace/src/llmobs/plugins/openai-agents/utils.js +321 -0
- package/packages/dd-trace/src/llmobs/sdk.js +2 -1
- package/packages/dd-trace/src/llmobs/span_processor.js +7 -2
- package/packages/dd-trace/src/llmobs/tagger.js +23 -3
- package/packages/dd-trace/src/llmobs/util.js +56 -3
- package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +3 -6
- package/packages/dd-trace/src/openfeature/configuration_source.js +5 -8
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +5 -1
- package/packages/dd-trace/src/plugins/ci_plugin.js +9 -3
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/outbound.js +4 -1
- 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/span_processor.js +1 -1
- package/packages/dd-trace/src/span_stats.js +22 -4
- package/ci/test-optimization-validation/command-suitability.js +0 -471
- package/ci/test-optimization-validation/init-probe-preload.js +0 -163
- package/ci/test-optimization-validation/init-probe.js +0 -246
- package/ci/test-optimization-validation/late-initialization.js +0 -63
- package/ci/test-optimization-validation/local-command.js +0 -163
- package/ci/test-optimization-validation/setup-runner.js +0 -97
- package/ci/test-optimization-validation-manifest.schema.json +0 -1
|
@@ -1,75 +1,71 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const {
|
|
4
|
-
getCommandDetails,
|
|
5
|
-
serializeDisplayCommand,
|
|
6
|
-
} = require('./command-runner')
|
|
7
3
|
const { sanitizeEnv } = require('./redaction')
|
|
8
4
|
|
|
9
5
|
/**
|
|
10
|
-
* Builds the normalized CI
|
|
6
|
+
* Builds the normalized static CI configuration metadata shared by reports.
|
|
11
7
|
*
|
|
12
8
|
* @param {object} framework manifest framework entry
|
|
13
|
-
* @returns {object|undefined} CI
|
|
9
|
+
* @returns {object|undefined} CI configuration context when available
|
|
14
10
|
*/
|
|
15
11
|
function buildCiCommandCandidate (framework) {
|
|
16
12
|
const ciWiring = framework.ciWiring || {}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (!command && !hasCiWiringContext(ciWiring)) return
|
|
13
|
+
if (Object.keys(ciWiring).length === 0) return
|
|
20
14
|
|
|
21
15
|
return removeUndefined({
|
|
22
|
-
provider: ciWiring.provider
|
|
23
|
-
configFile: ciWiring.configFile
|
|
24
|
-
workflow: ciWiring.workflow
|
|
25
|
-
job: ciWiring.job
|
|
26
|
-
step: ciWiring.step
|
|
27
|
-
runner: ciWiring.runner
|
|
28
|
-
shell: ciWiring.shell
|
|
29
|
-
command: command ?
|
|
30
|
-
cwd:
|
|
16
|
+
provider: ciWiring.provider,
|
|
17
|
+
configFile: ciWiring.configFile,
|
|
18
|
+
workflow: ciWiring.workflow,
|
|
19
|
+
job: ciWiring.job,
|
|
20
|
+
step: ciWiring.step,
|
|
21
|
+
runner: ciWiring.runner,
|
|
22
|
+
shell: ciWiring.shell,
|
|
23
|
+
command: typeof ciWiring.command === 'string' ? ciWiring.command : undefined,
|
|
24
|
+
cwd: ciWiring.workingDirectory,
|
|
25
|
+
terminalTestCommand: ciWiring.terminalTestCommand,
|
|
31
26
|
whySelected: ciWiring.whySelected || ciWiring.selectionReason || ciWiring.diagnosis,
|
|
32
|
-
replayability: ciWiring.replayability,
|
|
33
|
-
replayBlocker: ciWiring.replayBlocker,
|
|
34
27
|
initialization: ciWiring.initialization,
|
|
35
|
-
|
|
28
|
+
transport: ciWiring.transport,
|
|
29
|
+
env: buildCiEnvSummary(ciWiring),
|
|
36
30
|
packageScriptExpansionChain: getFirstArray(
|
|
37
31
|
ciWiring.packageScriptExpansionChain,
|
|
38
32
|
ciWiring.scriptExpansionChain,
|
|
39
33
|
ciWiring.commandExpansion
|
|
40
34
|
),
|
|
41
35
|
runnerToolChain: getFirstArray(
|
|
36
|
+
ciWiring.wrapperChain,
|
|
42
37
|
ciWiring.runnerToolChain,
|
|
43
38
|
ciWiring.toolChain,
|
|
44
39
|
ciWiring.commandChain
|
|
45
40
|
),
|
|
46
41
|
setupCommandIds: ciWiring.setupCommandIds,
|
|
47
42
|
unresolved: ciWiring.unresolved,
|
|
48
|
-
commandDetails: command && getCommandDetails(command),
|
|
49
43
|
})
|
|
50
44
|
}
|
|
51
45
|
|
|
52
|
-
function buildCiEnvSummary (ciWiring
|
|
46
|
+
function buildCiEnvSummary (ciWiring) {
|
|
53
47
|
const summary = removeUndefined({
|
|
54
48
|
workflow: sanitizeEnv(ciWiring.workflowEnv || ciWiring.env?.workflow),
|
|
55
49
|
job: sanitizeEnv(ciWiring.jobEnv || ciWiring.env?.job),
|
|
56
|
-
step: sanitizeEnv(ciWiring.stepEnv ||
|
|
50
|
+
step: sanitizeEnv(ciWiring.stepEnv || ciWiring.env?.step),
|
|
57
51
|
inherited: sanitizeEnv(ciWiring.inheritedEnv),
|
|
58
52
|
})
|
|
59
53
|
|
|
60
54
|
return Object.keys(summary).length > 0 ? summary : undefined
|
|
61
55
|
}
|
|
62
56
|
|
|
63
|
-
function hasCiWiringContext (ciWiring) {
|
|
64
|
-
return Object.keys(ciWiring).length > 0
|
|
65
|
-
}
|
|
66
|
-
|
|
67
57
|
function getFirstArray (...values) {
|
|
68
58
|
for (const value of values) {
|
|
69
|
-
if (Array.isArray(value) && value.length > 0) return value
|
|
59
|
+
if (Array.isArray(value) && value.length > 0) return value.map(formatChainEntry)
|
|
70
60
|
}
|
|
71
61
|
}
|
|
72
62
|
|
|
63
|
+
function formatChainEntry (entry) {
|
|
64
|
+
if (typeof entry === 'string') return entry
|
|
65
|
+
if (!entry || typeof entry !== 'object') return String(entry)
|
|
66
|
+
return entry.source ? `${entry.source}: ${entry.command}` : entry.command
|
|
67
|
+
}
|
|
68
|
+
|
|
73
69
|
function removeUndefined (object) {
|
|
74
70
|
const result = {}
|
|
75
71
|
for (const [key, value] of Object.entries(object)) {
|
|
@@ -78,6 +74,4 @@ function removeUndefined (object) {
|
|
|
78
74
|
return result
|
|
79
75
|
}
|
|
80
76
|
|
|
81
|
-
module.exports = {
|
|
82
|
-
buildCiCommandCandidate,
|
|
83
|
-
}
|
|
77
|
+
module.exports = { buildCiCommandCandidate }
|
|
@@ -86,7 +86,7 @@ function getFrameworkCiDiscoveryContradiction (framework, manifest) {
|
|
|
86
86
|
reason: 'CI workflow files were found by validator static diagnosis, but this manifest entry says no CI ' +
|
|
87
87
|
'workflow was found. The manifest cannot support a "no CI workflow found" conclusion.',
|
|
88
88
|
recommendation: 'Inspect the discovered CI files with hidden-directory-aware discovery, then update ciWiring, ' +
|
|
89
|
-
'
|
|
89
|
+
'ciWiring configuration evidence, omittedTestCommands, notes, or unresolved blockers before validation.',
|
|
90
90
|
ciDiscovery,
|
|
91
91
|
}
|
|
92
92
|
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { parseLiteralEnvironmentPrefix } = require('./literal-environment')
|
|
4
|
+
|
|
5
|
+
const MAX_SCRIPT_EXPANSIONS = 16
|
|
6
|
+
const DYNAMIC_VALUE_PATTERN = /[$`]|\r|\n|%[^%\s]+%|![^!\s]+!/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Expands bounded local package-script references without executing them.
|
|
10
|
+
*
|
|
11
|
+
* @param {string} command selected CI command
|
|
12
|
+
* @param {Record<string, string>} scripts package.json scripts
|
|
13
|
+
* @returns {{error?: string, lifecycleScripts: string[], terminals: object[]}} expansion
|
|
14
|
+
*/
|
|
15
|
+
function expandLocalPackageScripts (command, scripts) {
|
|
16
|
+
const state = { count: 0, lifecycleScripts: new Set() }
|
|
17
|
+
const result = expandCommand(command, scripts || {}, [], new Set(), state)
|
|
18
|
+
return {
|
|
19
|
+
...(result.error ? { error: result.error } : {}),
|
|
20
|
+
lifecycleScripts: [...state.lifecycleScripts],
|
|
21
|
+
terminals: result.terminals || [],
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function expandCommand (command, scripts, path, stack, state) {
|
|
26
|
+
if (++state.count > MAX_SCRIPT_EXPANSIONS) {
|
|
27
|
+
return { error: `local package-script expansion exceeded ${MAX_SCRIPT_EXPANSIONS} commands` }
|
|
28
|
+
}
|
|
29
|
+
const segments = splitLiteralAndChain(command)
|
|
30
|
+
if (!segments) return { error: 'the command contains unsupported dynamic shell syntax' }
|
|
31
|
+
|
|
32
|
+
const terminals = []
|
|
33
|
+
for (const segment of segments) {
|
|
34
|
+
if (hasStatefulShellSemantics(segment)) {
|
|
35
|
+
return { error: 'the command contains unsupported stateful shell semantics' }
|
|
36
|
+
}
|
|
37
|
+
const invocation = getPackageScriptInvocation(segment)
|
|
38
|
+
if (!invocation) {
|
|
39
|
+
terminals.push({ command: segment, path: [...path, segment] })
|
|
40
|
+
continue
|
|
41
|
+
}
|
|
42
|
+
if (typeof scripts[invocation.script] !== 'string') {
|
|
43
|
+
return { error: `local package script ${invocation.script} is unavailable` }
|
|
44
|
+
}
|
|
45
|
+
if (stack.has(invocation.script)) {
|
|
46
|
+
return { error: `local package-script cycle includes ${invocation.script}` }
|
|
47
|
+
}
|
|
48
|
+
const lifecycleNames = invocation.manager === 'npm'
|
|
49
|
+
? [`pre${invocation.script}`, `post${invocation.script}`]
|
|
50
|
+
.filter(name => typeof scripts[name] === 'string')
|
|
51
|
+
: []
|
|
52
|
+
for (const lifecycle of lifecycleNames) state.lifecycleScripts.add(lifecycle)
|
|
53
|
+
|
|
54
|
+
for (const scriptName of [
|
|
55
|
+
...(lifecycleNames.includes(`pre${invocation.script}`) ? [`pre${invocation.script}`] : []),
|
|
56
|
+
invocation.script,
|
|
57
|
+
...(lifecycleNames.includes(`post${invocation.script}`) ? [`post${invocation.script}`] : []),
|
|
58
|
+
]) {
|
|
59
|
+
if (stack.has(scriptName)) return { error: `local package-script cycle includes ${scriptName}` }
|
|
60
|
+
const nextStack = new Set(stack)
|
|
61
|
+
nextStack.add(invocation.script)
|
|
62
|
+
nextStack.add(scriptName)
|
|
63
|
+
const expanded = expandCommand(
|
|
64
|
+
scripts[scriptName],
|
|
65
|
+
scripts,
|
|
66
|
+
[...path, segment],
|
|
67
|
+
nextStack,
|
|
68
|
+
state
|
|
69
|
+
)
|
|
70
|
+
if (expanded.error) return expanded
|
|
71
|
+
terminals.push(...expanded.terminals)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return { terminals }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Splits only literal `&&` chains and rejects other shell control flow or expansion.
|
|
79
|
+
*
|
|
80
|
+
* @param {string} command command text
|
|
81
|
+
* @returns {string[]|undefined} literal command segments
|
|
82
|
+
*/
|
|
83
|
+
function splitLiteralAndChain (command) {
|
|
84
|
+
const source = String(command || '').trim()
|
|
85
|
+
if (!source || DYNAMIC_VALUE_PATTERN.test(source)) return
|
|
86
|
+
const segments = []
|
|
87
|
+
let quote
|
|
88
|
+
let start = 0
|
|
89
|
+
|
|
90
|
+
for (let index = 0; index < source.length; index++) {
|
|
91
|
+
const character = source[index]
|
|
92
|
+
if (character === '\\') {
|
|
93
|
+
index++
|
|
94
|
+
continue
|
|
95
|
+
}
|
|
96
|
+
if (quote) {
|
|
97
|
+
if (character === quote) quote = undefined
|
|
98
|
+
continue
|
|
99
|
+
}
|
|
100
|
+
if (character === '"' || character === "'") {
|
|
101
|
+
quote = character
|
|
102
|
+
continue
|
|
103
|
+
}
|
|
104
|
+
if (character === '&' && source[index + 1] === '&') {
|
|
105
|
+
const segment = source.slice(start, index).trim()
|
|
106
|
+
if (!segment) return
|
|
107
|
+
segments.push(segment)
|
|
108
|
+
start = ++index + 1
|
|
109
|
+
continue
|
|
110
|
+
}
|
|
111
|
+
if (';&|()'.includes(character)) return
|
|
112
|
+
}
|
|
113
|
+
if (quote) return
|
|
114
|
+
const finalSegment = source.slice(start).trim()
|
|
115
|
+
if (!finalSegment) return
|
|
116
|
+
segments.push(finalSegment)
|
|
117
|
+
return segments
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function hasStatefulShellSemantics (command) {
|
|
121
|
+
const prefix = parseLiteralEnvironmentPrefix(command)
|
|
122
|
+
const executable = command.slice(prefix.length).trim()
|
|
123
|
+
return !executable ||
|
|
124
|
+
/^(?:cd|eval|exec|export|popd|pushd|set|source|unset)\b/.test(executable) ||
|
|
125
|
+
/^\.(?:\s|$)/.test(executable)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function getPackageScriptInvocation (command) {
|
|
129
|
+
const literalPrefix = parseLiteralEnvironmentPrefix(command)
|
|
130
|
+
const source = command.slice(literalPrefix.length).trim()
|
|
131
|
+
.replace(/^(?:c8|nyc)(?:\.cmd)?\s+/, '')
|
|
132
|
+
const match = /^(npm(?:\.cmd)?|pnpm(?:\.cmd)?|yarn(?:pkg)?(?:\.cmd)?)\s+(?:(run|run-script)\s+)?([\w:-]+)$/
|
|
133
|
+
.exec(source)
|
|
134
|
+
if (!match) return
|
|
135
|
+
|
|
136
|
+
const manager = match[1].replace(/\.cmd$/i, '')
|
|
137
|
+
if (manager === 'npm' && !match[2] && !['restart', 'start', 'stop', 'test'].includes(match[3])) return
|
|
138
|
+
return { manager, script: match[3] }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
module.exports = { expandLocalPackageScripts }
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
const path = require('node:path')
|
|
4
4
|
|
|
5
|
-
const {
|
|
5
|
+
const { findEnvironmentEntry } = require('./environment')
|
|
6
|
+
const { splitNodeOptions } = require('./executable')
|
|
7
|
+
const { removeEmptyLiteralEnvironmentAssignments } = require('./literal-environment')
|
|
6
8
|
|
|
7
9
|
const GITHUB_API_KEY_REFERENCE = '$' + '{{ secrets.DD_API_KEY }}'
|
|
8
10
|
const AGENTLESS_ENV = {
|
|
@@ -31,7 +33,7 @@ function buildCiRemediation (framework) {
|
|
|
31
33
|
const location = getCiLocation(ciWiring)
|
|
32
34
|
const nodeOptions = getNodeOptions(framework)
|
|
33
35
|
const recommendedValues = getRecommendedValues(framework)
|
|
34
|
-
const variants = getVariants(transport, ciWiring,
|
|
36
|
+
const variants = getVariants(transport, ciWiring, recommendedValues, nodeOptions)
|
|
35
37
|
|
|
36
38
|
return {
|
|
37
39
|
provider: ciWiring.provider || 'unknown',
|
|
@@ -47,25 +49,8 @@ function buildCiRemediation (framework) {
|
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
function getConfiguredTransport (framework) {
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
if (env.DD_AGENT_HOST || env.DD_TRACE_AGENT_URL || env.DD_TRACE_AGENT_HOSTNAME) return 'agent'
|
|
53
|
-
return 'unknown'
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function collectCiEnv (framework) {
|
|
57
|
-
const ciWiring = framework.ciWiring || {}
|
|
58
|
-
return {
|
|
59
|
-
...ciWiring.workflowEnv,
|
|
60
|
-
...ciWiring.jobEnv,
|
|
61
|
-
...ciWiring.stepEnv,
|
|
62
|
-
...ciWiring.inheritedEnv,
|
|
63
|
-
...framework.ciWiringCommand?.env,
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function isTrue (value) {
|
|
68
|
-
return ['1', 'true'].includes(String(value || '').toLowerCase())
|
|
52
|
+
const mode = framework.ciWiring?.transport?.mode
|
|
53
|
+
return ['agentless', 'agent', 'none'].includes(mode) ? mode : 'unknown'
|
|
69
54
|
}
|
|
70
55
|
|
|
71
56
|
function getCiLocation (ciWiring) {
|
|
@@ -106,12 +91,12 @@ function getAgentAlternative () {
|
|
|
106
91
|
'DD_CIVISIBILITY_AGENTLESS_ENABLED.'
|
|
107
92
|
}
|
|
108
93
|
|
|
109
|
-
function getVariants (transport, ciWiring,
|
|
110
|
-
if (transport === 'agent') return [getVariant('agent', ciWiring,
|
|
111
|
-
return [getVariant('agentless', ciWiring,
|
|
94
|
+
function getVariants (transport, ciWiring, recommendedValues, nodeOptions) {
|
|
95
|
+
if (transport === 'agent') return [getVariant('agent', ciWiring, recommendedValues, nodeOptions)]
|
|
96
|
+
return [getVariant('agentless', ciWiring, recommendedValues, nodeOptions)]
|
|
112
97
|
}
|
|
113
98
|
|
|
114
|
-
function getVariant (transport, ciWiring,
|
|
99
|
+
function getVariant (transport, ciWiring, recommendedValues, nodeOptions) {
|
|
115
100
|
const transportEnv = transport === 'agentless' ? AGENTLESS_ENV : {}
|
|
116
101
|
const requiredEnv = { NODE_OPTIONS: nodeOptions, ...transportEnv }
|
|
117
102
|
const recommendedEnv = Object.fromEntries(recommendedValues.map(({ name, value }) => [name, value]))
|
|
@@ -128,13 +113,77 @@ function getVariant (transport, ciWiring, command, recommendedValues, nodeOption
|
|
|
128
113
|
})),
|
|
129
114
|
recommendedValues,
|
|
130
115
|
optionalValues: OPTIONAL_VALUES[transport],
|
|
131
|
-
snippet: formatSnippet({ ...requiredEnv, ...recommendedEnv }, ciWiring
|
|
116
|
+
snippet: formatSnippet({ ...requiredEnv, ...recommendedEnv }, ciWiring),
|
|
132
117
|
}
|
|
133
118
|
}
|
|
134
119
|
|
|
135
120
|
function getNodeOptions (framework) {
|
|
136
|
-
|
|
137
|
-
|
|
121
|
+
const existing = getEffectiveNodeOptions(framework.ciWiring)
|
|
122
|
+
const options = existing ? [existing] : []
|
|
123
|
+
if (framework.framework === 'vitest' &&
|
|
124
|
+
!hasNodeModuleOption(existing, ['--import'], isDatadogRegisterSpecifier)) {
|
|
125
|
+
options.push('--import dd-trace/register.js')
|
|
126
|
+
}
|
|
127
|
+
if (!hasNodeModuleOption(existing, ['-r', '--require'], isDatadogCiInitSpecifier)) {
|
|
128
|
+
options.push('-r dd-trace/ci/init')
|
|
129
|
+
}
|
|
130
|
+
return options.join(' ')
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function getEffectiveNodeOptions (ciWiring = {}) {
|
|
134
|
+
const platform = getCiPlatform(ciWiring)
|
|
135
|
+
let value
|
|
136
|
+
for (const field of ['inheritedEnv', 'workflowEnv', 'jobEnv', 'stepEnv']) {
|
|
137
|
+
const entry = findEnvironmentEntry(ciWiring[field], 'NODE_OPTIONS', platform)
|
|
138
|
+
if (entry) value = entry[1]
|
|
139
|
+
}
|
|
140
|
+
if (typeof value !== 'string') return ''
|
|
141
|
+
const literal = value.trim()
|
|
142
|
+
return hasDynamicEnvironmentReference(literal) ? '' : literal
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function hasNodeModuleOption (value, optionNames, matchesSpecifier) {
|
|
146
|
+
if (!value) return false
|
|
147
|
+
let args
|
|
148
|
+
try {
|
|
149
|
+
args = splitNodeOptions(value)
|
|
150
|
+
} catch {
|
|
151
|
+
return false
|
|
152
|
+
}
|
|
153
|
+
for (let index = 0; index < args.length; index++) {
|
|
154
|
+
const argument = args[index]
|
|
155
|
+
let specifier
|
|
156
|
+
if (optionNames.includes(argument)) {
|
|
157
|
+
specifier = args[++index]
|
|
158
|
+
} else {
|
|
159
|
+
for (const optionName of optionNames) {
|
|
160
|
+
const prefix = `${optionName}=`
|
|
161
|
+
if (argument.startsWith(prefix)) specifier = argument.slice(prefix.length)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (matchesSpecifier(specifier)) return true
|
|
165
|
+
}
|
|
166
|
+
return false
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function isDatadogCiInitSpecifier (specifier) {
|
|
170
|
+
return isDatadogModuleSpecifier(specifier, 'ci/init')
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function isDatadogRegisterSpecifier (specifier) {
|
|
174
|
+
return isDatadogModuleSpecifier(specifier, 'register')
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function isDatadogModuleSpecifier (specifier, entrypoint) {
|
|
178
|
+
if (typeof specifier !== 'string') return false
|
|
179
|
+
const normalized = specifier.replaceAll('\\', '/')
|
|
180
|
+
return [`dd-trace/${entrypoint}`, `dd-trace/${entrypoint}.js`].some(candidate => {
|
|
181
|
+
return normalized === candidate || normalized.endsWith(`/${candidate}`)
|
|
182
|
+
})
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function hasDynamicEnvironmentReference (value) {
|
|
186
|
+
return /(?:\$[A-Za-z_{]|\$\(|%[A-Za-z_][A-Za-z0-9_]*%)/.test(value)
|
|
138
187
|
}
|
|
139
188
|
|
|
140
189
|
function getRecommendedValues (framework) {
|
|
@@ -142,8 +191,7 @@ function getRecommendedValues (framework) {
|
|
|
142
191
|
const context = [
|
|
143
192
|
framework.ciWiring?.step,
|
|
144
193
|
framework.ciWiring?.job,
|
|
145
|
-
framework.
|
|
146
|
-
framework.ciWiringCommand?.description,
|
|
194
|
+
framework.ciWiring?.command,
|
|
147
195
|
].filter(Boolean).join(' ')
|
|
148
196
|
const testKind = /\bunit\b/i.test(context)
|
|
149
197
|
? 'unit-tests'
|
|
@@ -172,17 +220,27 @@ function normalizeName (value) {
|
|
|
172
220
|
.replaceAll(/^-|-$/g, '') || 'test'
|
|
173
221
|
}
|
|
174
222
|
|
|
175
|
-
function formatSnippet (env, ciWiring
|
|
223
|
+
function formatSnippet (env, ciWiring) {
|
|
176
224
|
if (ciWiring.provider === 'github-actions') {
|
|
177
|
-
const testCommand = getTestCommand(ciWiring
|
|
178
|
-
|
|
225
|
+
const testCommand = getTestCommand(ciWiring)
|
|
226
|
+
const lines = [
|
|
179
227
|
ciWiring.configFile ? `# ${formatPath(ciWiring.configFile)}` : '# GitHub Actions workflow',
|
|
180
228
|
ciWiring.job ? `# Job: ${ciWiring.job}` : '# Selected test job',
|
|
229
|
+
]
|
|
230
|
+
if (!testCommand) {
|
|
231
|
+
return [
|
|
232
|
+
...lines,
|
|
233
|
+
'env:',
|
|
234
|
+
...Object.entries(env).map(([name, value]) => ` ${name}: ${quoteYamlValue(value)}`),
|
|
235
|
+
].join('\n')
|
|
236
|
+
}
|
|
237
|
+
return [
|
|
238
|
+
...lines,
|
|
181
239
|
`- name: ${quoteYamlValue(ciWiring.step || 'Run tests with Datadog')}`,
|
|
182
240
|
' env:',
|
|
183
241
|
...Object.entries(env).map(([name, value]) => ` ${name}: ${quoteYamlValue(value)}`),
|
|
184
242
|
' run: |',
|
|
185
|
-
...
|
|
243
|
+
...testCommand.split(/\r?\n/).map(line => ` ${line}`),
|
|
186
244
|
].join('\n')
|
|
187
245
|
}
|
|
188
246
|
|
|
@@ -196,10 +254,22 @@ function quoteShellValue (value) {
|
|
|
196
254
|
return JSON.stringify(String(value))
|
|
197
255
|
}
|
|
198
256
|
|
|
199
|
-
function getTestCommand (ciWiring
|
|
200
|
-
if (command
|
|
201
|
-
|
|
202
|
-
|
|
257
|
+
function getTestCommand (ciWiring) {
|
|
258
|
+
if (typeof ciWiring.command === 'string' && ciWiring.command.trim()) {
|
|
259
|
+
return removeEmptyLiteralEnvironmentAssignments(
|
|
260
|
+
ciWiring.command,
|
|
261
|
+
'NODE_OPTIONS',
|
|
262
|
+
getCiPlatform(ciWiring)
|
|
263
|
+
)
|
|
264
|
+
}
|
|
265
|
+
return ciWiring.packageScriptExpansionChain?.[0] || ciWiring.runnerToolChain?.[0]
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function getCiPlatform (ciWiring) {
|
|
269
|
+
const shellName = path.basename(String(ciWiring.shell || '')).toLowerCase()
|
|
270
|
+
return ['cmd', 'cmd.exe', 'powershell', 'powershell.exe', 'pwsh', 'pwsh.exe'].includes(shellName)
|
|
271
|
+
? 'win32'
|
|
272
|
+
: process.platform
|
|
203
273
|
}
|
|
204
274
|
|
|
205
275
|
function quoteYamlValue (value) {
|
|
@@ -207,4 +277,7 @@ function quoteYamlValue (value) {
|
|
|
207
277
|
return JSON.stringify(String(value))
|
|
208
278
|
}
|
|
209
279
|
|
|
210
|
-
module.exports = {
|
|
280
|
+
module.exports = {
|
|
281
|
+
buildCiRemediation,
|
|
282
|
+
getConfiguredTransport,
|
|
283
|
+
}
|