dd-trace 6.4.0 → 6.5.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 +10 -7
- 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 +20 -7
- 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/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 +26 -0
- package/packages/datadog-instrumentations/src/mariadb.js +1 -1
- package/packages/datadog-instrumentations/src/mocha/main.js +0 -2
- 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 +159 -22
- 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 +2 -2
- package/packages/datadog-instrumentations/src/vitest-main.js +0 -2
- 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 +1 -1
- 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-playwright/src/index.js +177 -64
- package/packages/datadog-plugin-router/src/index.js +11 -2
- package/packages/datadog-plugin-selenium/src/index.js +5 -36
- 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/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
|
@@ -0,0 +1,707 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/* eslint-disable no-console, eslint-rules/eslint-process-env */
|
|
4
|
+
|
|
5
|
+
const path = require('path')
|
|
6
|
+
const { spawn } = require('child_process')
|
|
7
|
+
|
|
8
|
+
const { cleanupCommandOutputs, prepareCommandOutputs } = require('./command-output-policy')
|
|
9
|
+
const {
|
|
10
|
+
getExecutableForSpawn,
|
|
11
|
+
isEnvExecutable,
|
|
12
|
+
parseArgv,
|
|
13
|
+
} = require('./executable')
|
|
14
|
+
const { sanitizeConsoleText, sanitizeString } = require('./redaction')
|
|
15
|
+
const { ensureSafeDirectory, writeFileSafely } = require('./safe-files')
|
|
16
|
+
|
|
17
|
+
const INIT_PATH = path.resolve(__dirname, '..', 'init.js')
|
|
18
|
+
const REGISTER_PATH = path.resolve(__dirname, '..', '..', 'register.js')
|
|
19
|
+
const VALIDATION_MODE_ENV = '_DD_TEST_OPTIMIZATION_VALIDATION_MODE'
|
|
20
|
+
const VALIDATION_MANIFEST_ENV = '_DD_TEST_OPTIMIZATION_VALIDATION_MANIFEST_FILE'
|
|
21
|
+
const VALIDATION_OUTPUT_ENV = '_DD_TEST_OPTIMIZATION_VALIDATION_OUTPUT_DIR'
|
|
22
|
+
const VALIDATION_CAPTURE_MODE_ENV = '_DD_TEST_OPTIMIZATION_VALIDATION_CAPTURE_MODE'
|
|
23
|
+
const APM_AGENTLESS_ENV = '_DD_APM_TRACING_AGENTLESS_ENABLED'
|
|
24
|
+
const VALIDATION_RESERVED_ENV_NAMES = [
|
|
25
|
+
'NODE_OPTIONS',
|
|
26
|
+
VALIDATION_MANIFEST_ENV,
|
|
27
|
+
VALIDATION_MODE_ENV,
|
|
28
|
+
VALIDATION_OUTPUT_ENV,
|
|
29
|
+
VALIDATION_CAPTURE_MODE_ENV,
|
|
30
|
+
APM_AGENTLESS_ENV,
|
|
31
|
+
]
|
|
32
|
+
const CLEAN_ENV_ALLOWLIST = new Set([
|
|
33
|
+
'COMSPEC',
|
|
34
|
+
'ComSpec',
|
|
35
|
+
'HOME',
|
|
36
|
+
'LOGNAME',
|
|
37
|
+
'PATH',
|
|
38
|
+
'Path',
|
|
39
|
+
'PATHEXT',
|
|
40
|
+
'SHELL',
|
|
41
|
+
'SystemRoot',
|
|
42
|
+
'TEMP',
|
|
43
|
+
'TMP',
|
|
44
|
+
'TMPDIR',
|
|
45
|
+
'USER',
|
|
46
|
+
'USERNAME',
|
|
47
|
+
'VOLTA_HOME',
|
|
48
|
+
'WINDIR',
|
|
49
|
+
'windir',
|
|
50
|
+
])
|
|
51
|
+
const VALIDATION_SUPPRESSION_ENV = {
|
|
52
|
+
DD_AGENTLESS_LOG_SUBMISSION_ENABLED: 'false',
|
|
53
|
+
DD_APPSEC_ENABLED: 'false',
|
|
54
|
+
DD_CRASHTRACKING_ENABLED: 'false',
|
|
55
|
+
DD_DATA_STREAMS_ENABLED: 'false',
|
|
56
|
+
DD_DYNAMIC_INSTRUMENTATION_ENABLED: 'false',
|
|
57
|
+
DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED: 'false',
|
|
58
|
+
DD_EXPERIMENTAL_FLAGGING_PROVIDER_ENABLED: 'false',
|
|
59
|
+
DD_HEAP_SNAPSHOT_COUNT: '0',
|
|
60
|
+
DD_IAST_ENABLED: 'false',
|
|
61
|
+
DD_INSTRUMENTATION_TELEMETRY_ENABLED: 'false',
|
|
62
|
+
DD_LLMOBS_ENABLED: 'false',
|
|
63
|
+
DD_LOGS_OTEL_ENABLED: 'false',
|
|
64
|
+
DD_METRICS_OTEL_ENABLED: 'false',
|
|
65
|
+
DD_PROFILING_ENABLED: 'false',
|
|
66
|
+
DD_REMOTE_CONFIGURATION_ENABLED: 'false',
|
|
67
|
+
DD_RUNTIME_METRICS_ENABLED: 'false',
|
|
68
|
+
DD_TRACE_OTEL_ENABLED: 'false',
|
|
69
|
+
DD_TRACE_SPAN_LEAK_DEBUG: '0',
|
|
70
|
+
// Offline validation only needs test-cycle events and explicitly configured filesystem inputs.
|
|
71
|
+
DD_CIVISIBILITY_GIT_UPLOAD_ENABLED: 'false',
|
|
72
|
+
DD_CIVISIBILITY_GIT_UNSHALLOW_ENABLED: 'false',
|
|
73
|
+
DD_CIVISIBILITY_IMPACTED_TESTS_DETECTION_ENABLED: 'false',
|
|
74
|
+
DD_EXPERIMENTAL_TEST_REQUESTS_FS_CACHE: 'false',
|
|
75
|
+
DD_TEST_FAILED_TEST_REPLAY_ENABLED: 'false',
|
|
76
|
+
}
|
|
77
|
+
const DEFAULT_MAX_OUTPUT_BYTES = 1024 * 1024
|
|
78
|
+
const EARLY_STOP_KILL_GRACE_MS = 500
|
|
79
|
+
const TIMEOUT_KILL_GRACE_MS = 5000
|
|
80
|
+
const TIMEOUT_FINALIZE_GRACE_MS = 1000
|
|
81
|
+
|
|
82
|
+
function runCommand (command, options = {}) {
|
|
83
|
+
const {
|
|
84
|
+
env = {},
|
|
85
|
+
envMode = 'inherit',
|
|
86
|
+
outDir,
|
|
87
|
+
label,
|
|
88
|
+
repositoryRoot,
|
|
89
|
+
requireExecutableApproval = false,
|
|
90
|
+
stopWhen,
|
|
91
|
+
verbose = false,
|
|
92
|
+
} = options
|
|
93
|
+
const artifactRoot = options.artifactRoot || path.dirname(outDir)
|
|
94
|
+
const startedAt = Date.now()
|
|
95
|
+
const {
|
|
96
|
+
maxOutputBytes,
|
|
97
|
+
timeoutFinalizeGraceMs,
|
|
98
|
+
timeoutKillGraceMs,
|
|
99
|
+
timeoutMs,
|
|
100
|
+
} = getCommandExecutionSettings(command)
|
|
101
|
+
const result = {
|
|
102
|
+
label,
|
|
103
|
+
command: serializeCommand(command),
|
|
104
|
+
displayCommand: serializeDisplayCommand(command),
|
|
105
|
+
commandDetails: getCommandDetails(command),
|
|
106
|
+
cwd: command.cwd,
|
|
107
|
+
exitCode: null,
|
|
108
|
+
signal: null,
|
|
109
|
+
stoppedEarly: false,
|
|
110
|
+
durationMs: 0,
|
|
111
|
+
timedOut: false,
|
|
112
|
+
stdout: '',
|
|
113
|
+
stdoutTruncated: false,
|
|
114
|
+
stderr: '',
|
|
115
|
+
stderrTruncated: false,
|
|
116
|
+
artifacts: {},
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
ensureSafeDirectory(artifactRoot, outDir, 'command artifact directory')
|
|
120
|
+
try {
|
|
121
|
+
assertNoInlineValidationEnvOverrides(command, env)
|
|
122
|
+
} catch (error) {
|
|
123
|
+
return Promise.reject(error)
|
|
124
|
+
}
|
|
125
|
+
const outputStates = prepareCommandOutputs({ command, artifactRoot, outDir, repositoryRoot })
|
|
126
|
+
|
|
127
|
+
return new Promise((resolve) => {
|
|
128
|
+
let finalized = false
|
|
129
|
+
let processGroupCleanupPending = false
|
|
130
|
+
let pendingCloseResult
|
|
131
|
+
const childEnv = {
|
|
132
|
+
...getBaseEnv(envMode),
|
|
133
|
+
...command.env,
|
|
134
|
+
...env,
|
|
135
|
+
}
|
|
136
|
+
if (command.env?.NODE_OPTIONS && env.NODE_OPTIONS) {
|
|
137
|
+
childEnv.NODE_OPTIONS = mergeNodeOptions(env.NODE_OPTIONS, command.env.NODE_OPTIONS)
|
|
138
|
+
}
|
|
139
|
+
for (const [name, value] of Object.entries(childEnv)) {
|
|
140
|
+
if (value === undefined) delete childEnv[name]
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const useProcessGroup = shouldUseProcessGroup()
|
|
144
|
+
let child
|
|
145
|
+
try {
|
|
146
|
+
const executable = getExecutableForSpawn(command, { requireApproval: requireExecutableApproval })
|
|
147
|
+
const argv0 = process.platform === 'win32' ? {} : { argv0: executable.argv0 }
|
|
148
|
+
child = command.usesShell
|
|
149
|
+
? spawn(command.shellCommand, {
|
|
150
|
+
...argv0,
|
|
151
|
+
cwd: command.cwd,
|
|
152
|
+
detached: useProcessGroup,
|
|
153
|
+
env: childEnv,
|
|
154
|
+
shell: executable.path,
|
|
155
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
156
|
+
})
|
|
157
|
+
: spawn(executable.path, command.argv.slice(1), {
|
|
158
|
+
...argv0,
|
|
159
|
+
cwd: command.cwd,
|
|
160
|
+
detached: useProcessGroup,
|
|
161
|
+
env: childEnv,
|
|
162
|
+
shell: false,
|
|
163
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
164
|
+
})
|
|
165
|
+
} catch (error) {
|
|
166
|
+
result.stderr = `${error.stack || error}\n`
|
|
167
|
+
result.durationMs = Date.now() - startedAt
|
|
168
|
+
try {
|
|
169
|
+
result.commandOutputPaths = cleanupCommandOutputs(outputStates)
|
|
170
|
+
} catch (cleanupError) {
|
|
171
|
+
result.outputCleanupError = cleanupError?.message || String(cleanupError)
|
|
172
|
+
}
|
|
173
|
+
resolve(result)
|
|
174
|
+
return
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (verbose) {
|
|
178
|
+
console.log(sanitizeConsoleText(
|
|
179
|
+
`[test-optimization-validator] running ${label || serializeCommand(command)}`
|
|
180
|
+
))
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
let killTimer
|
|
184
|
+
let finalizeTimer
|
|
185
|
+
let stopTimer
|
|
186
|
+
const timeout = setTimeout(() => {
|
|
187
|
+
result.timedOut = true
|
|
188
|
+
processGroupCleanupPending = useProcessGroup
|
|
189
|
+
signalChild(child, 'SIGTERM', useProcessGroup)
|
|
190
|
+
killTimer = setTimeout(() => {
|
|
191
|
+
signalChild(child, 'SIGKILL', useProcessGroup)
|
|
192
|
+
finishProcessGroupCleanup('SIGKILL')
|
|
193
|
+
}, timeoutKillGraceMs)
|
|
194
|
+
}, timeoutMs)
|
|
195
|
+
|
|
196
|
+
if (stopWhen) {
|
|
197
|
+
stopTimer = setInterval(() => {
|
|
198
|
+
let shouldStop = false
|
|
199
|
+
try {
|
|
200
|
+
shouldStop = stopWhen()
|
|
201
|
+
} catch {}
|
|
202
|
+
if (!shouldStop) return
|
|
203
|
+
|
|
204
|
+
clearInterval(stopTimer)
|
|
205
|
+
result.stoppedEarly = true
|
|
206
|
+
processGroupCleanupPending = useProcessGroup
|
|
207
|
+
signalChild(child, 'SIGTERM', useProcessGroup)
|
|
208
|
+
killTimer = setTimeout(() => {
|
|
209
|
+
signalChild(child, 'SIGKILL', useProcessGroup)
|
|
210
|
+
finishProcessGroupCleanup('SIGKILL')
|
|
211
|
+
}, EARLY_STOP_KILL_GRACE_MS)
|
|
212
|
+
}, 25)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
child.stdout.on('data', chunk => {
|
|
216
|
+
const capture = appendCapturedOutput(result.stdout, chunk, maxOutputBytes)
|
|
217
|
+
result.stdout = capture.output
|
|
218
|
+
result.stdoutTruncated = result.stdoutTruncated || capture.truncated
|
|
219
|
+
})
|
|
220
|
+
child.stderr.on('data', chunk => {
|
|
221
|
+
const capture = appendCapturedOutput(result.stderr, chunk, maxOutputBytes)
|
|
222
|
+
result.stderr = capture.output
|
|
223
|
+
result.stderrTruncated = result.stderrTruncated || capture.truncated
|
|
224
|
+
})
|
|
225
|
+
child.on('error', err => {
|
|
226
|
+
result.stderr += `${err.stack || err}\n`
|
|
227
|
+
finalize(null, null)
|
|
228
|
+
})
|
|
229
|
+
child.on('close', (code, signal) => {
|
|
230
|
+
if (processGroupCleanupPending) {
|
|
231
|
+
pendingCloseResult = { code, signal }
|
|
232
|
+
return
|
|
233
|
+
}
|
|
234
|
+
finalize(code, signal)
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
function finishProcessGroupCleanup (fallbackSignal) {
|
|
238
|
+
processGroupCleanupPending = false
|
|
239
|
+
if (pendingCloseResult) {
|
|
240
|
+
finalize(pendingCloseResult.code, pendingCloseResult.signal || fallbackSignal)
|
|
241
|
+
return
|
|
242
|
+
}
|
|
243
|
+
finalizeTimer = setTimeout(() => finalize(null, fallbackSignal), timeoutFinalizeGraceMs)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function finalize (code, signal) {
|
|
247
|
+
if (finalized) return
|
|
248
|
+
finalized = true
|
|
249
|
+
clearTimeout(timeout)
|
|
250
|
+
clearTimeout(killTimer)
|
|
251
|
+
clearTimeout(finalizeTimer)
|
|
252
|
+
clearInterval(stopTimer)
|
|
253
|
+
result.exitCode = code
|
|
254
|
+
result.signal = signal
|
|
255
|
+
result.durationMs = Date.now() - startedAt
|
|
256
|
+
|
|
257
|
+
try {
|
|
258
|
+
result.commandOutputPaths = cleanupCommandOutputs(outputStates)
|
|
259
|
+
} catch (err) {
|
|
260
|
+
result.outputCleanupError = err && err.message ? err.message : String(err)
|
|
261
|
+
result.stderr += '\n[test-optimization-validator] could not clean up command outputs: ' +
|
|
262
|
+
`${result.outputCleanupError}\n`
|
|
263
|
+
if (result.exitCode === 0) result.exitCode = 1
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
result.artifacts.stdout = path.join(outDir, 'stdout.txt')
|
|
267
|
+
result.artifacts.stderr = path.join(outDir, 'stderr.txt')
|
|
268
|
+
result.artifacts.command = path.join(outDir, 'command.json')
|
|
269
|
+
|
|
270
|
+
try {
|
|
271
|
+
writeFileSafely(
|
|
272
|
+
artifactRoot,
|
|
273
|
+
result.artifacts.stdout,
|
|
274
|
+
sanitizeString(formatCapturedOutput(result.stdout, result.stdoutTruncated, maxOutputBytes)),
|
|
275
|
+
'command stdout artifact'
|
|
276
|
+
)
|
|
277
|
+
writeFileSafely(
|
|
278
|
+
artifactRoot,
|
|
279
|
+
result.artifacts.stderr,
|
|
280
|
+
sanitizeString(formatCapturedOutput(result.stderr, result.stderrTruncated, maxOutputBytes)),
|
|
281
|
+
'command stderr artifact'
|
|
282
|
+
)
|
|
283
|
+
writeFileSafely(artifactRoot, result.artifacts.command, `${JSON.stringify({
|
|
284
|
+
command: sanitizeString(result.command),
|
|
285
|
+
displayCommand: sanitizeString(result.displayCommand),
|
|
286
|
+
commandDetails: result.commandDetails,
|
|
287
|
+
cwd: result.cwd,
|
|
288
|
+
exitCode: result.exitCode,
|
|
289
|
+
signal: result.signal,
|
|
290
|
+
durationMs: result.durationMs,
|
|
291
|
+
timedOut: result.timedOut,
|
|
292
|
+
stoppedEarly: result.stoppedEarly,
|
|
293
|
+
stdoutTruncated: result.stdoutTruncated,
|
|
294
|
+
stderrTruncated: result.stderrTruncated,
|
|
295
|
+
maxOutputBytes,
|
|
296
|
+
commandOutputPaths: result.commandOutputPaths,
|
|
297
|
+
outputCleanupError: result.outputCleanupError,
|
|
298
|
+
}, null, 2)}\n`, 'command metadata artifact')
|
|
299
|
+
} catch (error) {
|
|
300
|
+
result.artifactWriteError = error?.message || String(error)
|
|
301
|
+
result.stderr += '\n[test-optimization-validator] could not write command artifacts: ' +
|
|
302
|
+
`${result.artifactWriteError}\n`
|
|
303
|
+
if (!Number.isInteger(result.exitCode) || result.exitCode === 0) result.exitCode = 1
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
resolve(result)
|
|
307
|
+
}
|
|
308
|
+
})
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Returns the effective bounded execution settings used for one project command.
|
|
313
|
+
*
|
|
314
|
+
* @param {object} command structured command
|
|
315
|
+
* @returns {{maxOutputBytes: number, timeoutFinalizeGraceMs: number, timeoutKillGraceMs: number, timeoutMs: number}}
|
|
316
|
+
* execution settings
|
|
317
|
+
*/
|
|
318
|
+
function getCommandExecutionSettings (command) {
|
|
319
|
+
return {
|
|
320
|
+
maxOutputBytes: command.maxOutputBytes || DEFAULT_MAX_OUTPUT_BYTES,
|
|
321
|
+
timeoutFinalizeGraceMs: command.timeoutFinalizeGraceMs || TIMEOUT_FINALIZE_GRACE_MS,
|
|
322
|
+
timeoutKillGraceMs: command.timeoutKillGraceMs || TIMEOUT_KILL_GRACE_MS,
|
|
323
|
+
timeoutMs: command.timeoutMs || 300_000,
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Appends output while retaining only the latest bytes for diagnostic artifacts.
|
|
329
|
+
*
|
|
330
|
+
* @param {string} current currently captured output
|
|
331
|
+
* @param {Buffer|string} chunk new output chunk
|
|
332
|
+
* @param {number} maxBytes maximum retained bytes
|
|
333
|
+
* @returns {{output: string, truncated: boolean}} retained output and truncation flag
|
|
334
|
+
*/
|
|
335
|
+
function appendCapturedOutput (current, chunk, maxBytes) {
|
|
336
|
+
const next = Buffer.concat([
|
|
337
|
+
Buffer.from(current),
|
|
338
|
+
Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)),
|
|
339
|
+
])
|
|
340
|
+
|
|
341
|
+
if (next.length <= maxBytes) {
|
|
342
|
+
return {
|
|
343
|
+
output: next.toString('utf8'),
|
|
344
|
+
truncated: false,
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
output: next.subarray(next.length - maxBytes).toString('utf8'),
|
|
350
|
+
truncated: true,
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Adds truncation context to a captured command output artifact.
|
|
356
|
+
*
|
|
357
|
+
* @param {string} output captured output
|
|
358
|
+
* @param {boolean} truncated whether earlier output was omitted
|
|
359
|
+
* @param {number} maxBytes maximum retained bytes
|
|
360
|
+
* @returns {string} output artifact content
|
|
361
|
+
*/
|
|
362
|
+
function formatCapturedOutput (output, truncated, maxBytes) {
|
|
363
|
+
if (!truncated) return output
|
|
364
|
+
return `[test-optimization-validator] output truncated to last ${maxBytes} bytes\n${output}`
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function shouldUseProcessGroup () {
|
|
368
|
+
return process.platform !== 'win32'
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function signalChild (child, signal, useProcessGroup) {
|
|
372
|
+
try {
|
|
373
|
+
if (useProcessGroup) {
|
|
374
|
+
process.kill(-child.pid, signal)
|
|
375
|
+
return
|
|
376
|
+
}
|
|
377
|
+
} catch {}
|
|
378
|
+
|
|
379
|
+
child.kill(signal)
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function getBaseEnv (envMode) {
|
|
383
|
+
if (envMode !== 'clean') return process.env
|
|
384
|
+
|
|
385
|
+
const cleanEnv = {}
|
|
386
|
+
for (const name of CLEAN_ENV_ALLOWLIST) {
|
|
387
|
+
if (process.env[name] !== undefined) cleanEnv[name] = process.env[name]
|
|
388
|
+
}
|
|
389
|
+
return cleanEnv
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function buildDatadogEnv ({ fixture, outputRoot, scenario, framework }) {
|
|
393
|
+
const offline = buildOfflineValidationEnv({ fixture, outputRoot })
|
|
394
|
+
return {
|
|
395
|
+
...offline,
|
|
396
|
+
DD_CIVISIBILITY_AGENTLESS_ENABLED: '0',
|
|
397
|
+
DD_CIVISIBILITY_ENABLED: '1',
|
|
398
|
+
DD_TRACE_ENABLED: 'true',
|
|
399
|
+
...VALIDATION_SUPPRESSION_ENV,
|
|
400
|
+
DD_SERVICE: 'dd-test-optimization-validation',
|
|
401
|
+
DD_ENV: 'local-validation',
|
|
402
|
+
DD_CIVISIBILITY_FLAKY_RETRY_COUNT: '2',
|
|
403
|
+
DD_TAGS: `test_optimization.validation.scenario:${scenario}`,
|
|
404
|
+
NODE_OPTIONS: withCiPreloads('', framework),
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function buildCiWiringEnv ({ fixture, outputRoot }) {
|
|
409
|
+
return {
|
|
410
|
+
...buildOfflineValidationEnv({ fixture, outputRoot }),
|
|
411
|
+
[VALIDATION_CAPTURE_MODE_ENV]: 'sample',
|
|
412
|
+
DD_TRACE_DEBUG: '1',
|
|
413
|
+
DD_TRACE_LOG_LEVEL: 'debug',
|
|
414
|
+
...VALIDATION_SUPPRESSION_ENV,
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Builds the private environment used by the filesystem-only validation exporter.
|
|
420
|
+
*
|
|
421
|
+
* @param {object} input offline validation inputs
|
|
422
|
+
* @param {{manifestPath: string}} input.fixture authoritative cache fixture
|
|
423
|
+
* @param {string} input.outputRoot pre-created payload output root
|
|
424
|
+
* @returns {NodeJS.ProcessEnv} validation transport environment
|
|
425
|
+
*/
|
|
426
|
+
function buildOfflineValidationEnv ({ fixture, outputRoot }) {
|
|
427
|
+
return {
|
|
428
|
+
DD_AGENT_HOST: undefined,
|
|
429
|
+
DD_API_KEY: undefined,
|
|
430
|
+
DD_APP_KEY: undefined,
|
|
431
|
+
DATADOG_API_KEY: undefined,
|
|
432
|
+
[APM_AGENTLESS_ENV]: undefined,
|
|
433
|
+
DD_CIVISIBILITY_AGENTLESS_ENABLED: '0',
|
|
434
|
+
DD_CIVISIBILITY_AGENTLESS_URL: undefined,
|
|
435
|
+
DD_EXPERIMENTAL_TEST_OPT_SETTINGS_CACHE: undefined,
|
|
436
|
+
DD_TRACE_AGENT_HOSTNAME: undefined,
|
|
437
|
+
DD_TRACE_AGENT_PORT: undefined,
|
|
438
|
+
DD_TRACE_AGENT_UNIX_DOMAIN_SOCKET: undefined,
|
|
439
|
+
DD_TRACE_AGENT_URL: undefined,
|
|
440
|
+
OTEL_LOGS_EXPORTER: undefined,
|
|
441
|
+
OTEL_METRICS_EXPORTER: undefined,
|
|
442
|
+
OTEL_TRACES_EXPORTER: undefined,
|
|
443
|
+
[VALIDATION_MANIFEST_ENV]: fixture.manifestPath,
|
|
444
|
+
[VALIDATION_MODE_ENV]: '1',
|
|
445
|
+
[VALIDATION_OUTPUT_ENV]: outputRoot,
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Rejects command-local assignments that can bypass validator-controlled offline routing.
|
|
451
|
+
*
|
|
452
|
+
* @param {object} command command to execute
|
|
453
|
+
* @param {NodeJS.ProcessEnv} env validator environment overrides
|
|
454
|
+
*/
|
|
455
|
+
function assertNoInlineValidationEnvOverrides (command, env) {
|
|
456
|
+
if (!env[VALIDATION_MODE_ENV]) return
|
|
457
|
+
const reservedEnvNames = new Set([
|
|
458
|
+
...VALIDATION_RESERVED_ENV_NAMES,
|
|
459
|
+
...Object.keys(env).filter(name => {
|
|
460
|
+
return name.startsWith('DD_') || name.startsWith('_DD_') || name.startsWith('OTEL_')
|
|
461
|
+
}),
|
|
462
|
+
])
|
|
463
|
+
|
|
464
|
+
if (command.usesShell) {
|
|
465
|
+
rejectReservedShellAssignments(command.shellCommand, reservedEnvNames)
|
|
466
|
+
return
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const parsed = parseArgv(command.argv)
|
|
470
|
+
rejectReservedEnvSplitStrings(command.argv, reservedEnvNames)
|
|
471
|
+
if (parsed.ignoreEnvironment) throwEnvironmentReset()
|
|
472
|
+
if (parsed.unsupportedEnvOption) throwUnsupportedEnvOption(parsed.unsupportedEnvOption)
|
|
473
|
+
for (const name of Object.keys(parsed.prefixEnv)) {
|
|
474
|
+
if (reservedEnvNames.has(name)) throwReservedEnvOverride(name)
|
|
475
|
+
}
|
|
476
|
+
for (const name of parsed.unsetEnvNames) {
|
|
477
|
+
if (reservedEnvNames.has(name)) throwReservedEnvOverride(name)
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if (isPosixShellExecutable(command.argv[parsed.commandIndex])) {
|
|
481
|
+
for (let index = parsed.commandIndex + 1; index < command.argv.length - 1; index++) {
|
|
482
|
+
const value = command.argv[index]
|
|
483
|
+
if (isShellCommandFlag(value) && typeof command.argv[index + 1] === 'string') {
|
|
484
|
+
rejectReservedShellAssignments(command.argv[index + 1], reservedEnvNames)
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Rejects reserved variable assignments and removals in shell source.
|
|
492
|
+
*
|
|
493
|
+
* @param {string} shellCommand shell source
|
|
494
|
+
* @param {Set<string>} reservedEnvNames validator-controlled environment names
|
|
495
|
+
*/
|
|
496
|
+
function rejectReservedShellAssignments (shellCommand, reservedEnvNames) {
|
|
497
|
+
const source = String(shellCommand || '')
|
|
498
|
+
const environmentReset =
|
|
499
|
+
/\benv(?:\.exe)?\s+(?:(?![;&|()]).)*?(?:-(?=\s|$)|-i\b|--ignore-environment\b)/i
|
|
500
|
+
|
|
501
|
+
if (environmentReset.test(source)) throwEnvironmentReset()
|
|
502
|
+
|
|
503
|
+
for (const name of reservedEnvNames) {
|
|
504
|
+
const escapedName = escapeRegExp(name)
|
|
505
|
+
const assignment = new RegExp(
|
|
506
|
+
String.raw`(?:^|[\s;&|()'"])(?:export\s+|set\s+)?(?:\$env:)?${escapedName}\s*\+?=`,
|
|
507
|
+
'i'
|
|
508
|
+
)
|
|
509
|
+
const removal = new RegExp(
|
|
510
|
+
String.raw`(?:\bunset(?:\s+(?:-[A-Za-z]+|[A-Za-z_][A-Za-z0-9_]*))*\s+|` +
|
|
511
|
+
String.raw`\benv(?:\.exe)?\s+(?:(?![;&|()]).)*?(?:-u\s*|--unset(?:=|\s+))|` +
|
|
512
|
+
String.raw`\bRemove-Item\s+(?:[^;&|]*\s)?env:)${escapedName}\b`,
|
|
513
|
+
'i'
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
if (assignment.test(source) || removal.test(source)) throwReservedEnvOverride(name)
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Rejects reserved environment changes hidden inside env --split-string arguments.
|
|
522
|
+
*
|
|
523
|
+
* @param {string[]} argv structured command arguments
|
|
524
|
+
* @param {Set<string>} reservedEnvNames validator-controlled environment names
|
|
525
|
+
* @returns {void}
|
|
526
|
+
*/
|
|
527
|
+
function rejectReservedEnvSplitStrings (argv, reservedEnvNames) {
|
|
528
|
+
if (!Array.isArray(argv) || !isEnvExecutable(argv[0])) return
|
|
529
|
+
|
|
530
|
+
for (let index = 1; index < argv.length; index++) {
|
|
531
|
+
const argument = argv[index]
|
|
532
|
+
if (argument === '-S' || argument === '--split-string') {
|
|
533
|
+
if (typeof argv[index + 1] === 'string') {
|
|
534
|
+
rejectReservedShellAssignments(`env ${argv[index + 1]}`, reservedEnvNames)
|
|
535
|
+
}
|
|
536
|
+
index++
|
|
537
|
+
continue
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const splitString = /^(?:-S|--split-string=)(.+)$/.exec(argument)?.[1]
|
|
541
|
+
if (splitString !== undefined) rejectReservedShellAssignments(`env ${splitString}`, reservedEnvNames)
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function isShellCommandFlag (value) {
|
|
546
|
+
return /^-[A-Za-z]*c[A-Za-z]*$/.test(value)
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function isPosixShellExecutable (value) {
|
|
550
|
+
return /^(?:a|ba|da|k|z)?sh$/.test(path.basename(String(value || '')).toLowerCase())
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Throws a customer-facing error for unsafe inline validation environment changes.
|
|
555
|
+
*
|
|
556
|
+
* @param {string} name reserved environment variable
|
|
557
|
+
*/
|
|
558
|
+
function throwReservedEnvOverride (name) {
|
|
559
|
+
throw new Error(
|
|
560
|
+
`Refusing inline ${name} changes during live validation because they can bypass the offline validation mode. ` +
|
|
561
|
+
'Record CI-provided values in command.env so the validator can apply its private diagnostic settings.'
|
|
562
|
+
)
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function throwEnvironmentReset () {
|
|
566
|
+
throw new Error(
|
|
567
|
+
'Refusing to clear the command environment during live validation because this would remove the offline ' +
|
|
568
|
+
'validation and Datadog initialization settings.'
|
|
569
|
+
)
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function throwUnsupportedEnvOption (option) {
|
|
573
|
+
throw new Error(
|
|
574
|
+
`Refusing unsupported env option ${option} during live validation because its environment effects cannot be ` +
|
|
575
|
+
'verified safely.'
|
|
576
|
+
)
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Escapes a literal for use in a regular expression.
|
|
581
|
+
*
|
|
582
|
+
* @param {string} value literal value
|
|
583
|
+
* @returns {string} escaped value
|
|
584
|
+
*/
|
|
585
|
+
function escapeRegExp (value) {
|
|
586
|
+
return value.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`)
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function withCiPreloads (nodeOptions = '', framework) {
|
|
590
|
+
let result = nodeOptions.trim()
|
|
591
|
+
|
|
592
|
+
if (framework?.framework === 'vitest' && !hasRegister(result)) {
|
|
593
|
+
result = `--import ${formatNodeRequire(REGISTER_PATH)}${result ? ` ${result}` : ''}`
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
if (!hasCiInit(result)) {
|
|
597
|
+
result = `${result ? `${result} ` : ''}-r ${formatNodeRequire(INIT_PATH)}`
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
return result
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function mergeNodeOptions (...nodeOptions) {
|
|
604
|
+
return nodeOptions
|
|
605
|
+
.map(value => String(value || '').trim())
|
|
606
|
+
.filter(Boolean)
|
|
607
|
+
.join(' ')
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function hasCiInit (nodeOptions) {
|
|
611
|
+
return nodeOptions.includes('dd-trace/ci/init') || nodeOptions.includes(INIT_PATH)
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
function hasRegister (nodeOptions) {
|
|
615
|
+
return nodeOptions.includes('dd-trace/register.js') || nodeOptions.includes(REGISTER_PATH)
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
function formatNodeRequire (filename) {
|
|
619
|
+
if (!/\s/.test(filename)) return filename
|
|
620
|
+
return JSON.stringify(filename)
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function serializeCommand (command) {
|
|
624
|
+
return command.usesShell ? command.shellCommand : command.argv.join(' ')
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* Renders the command that will actually execute without trusting display-only manifest fields.
|
|
629
|
+
*
|
|
630
|
+
* @param {object} command command to render
|
|
631
|
+
* @returns {string} unambiguous customer-facing command
|
|
632
|
+
*/
|
|
633
|
+
function serializeApprovalCommand (command) {
|
|
634
|
+
if (command.usesShell) return command.shellCommand
|
|
635
|
+
return command.argv.map(formatApprovalArgument).join(' ')
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* Quotes arguments whose boundaries would otherwise be ambiguous in an approval plan.
|
|
640
|
+
*
|
|
641
|
+
* @param {string} value argument value
|
|
642
|
+
* @returns {string} visible argument
|
|
643
|
+
*/
|
|
644
|
+
function formatApprovalArgument (value) {
|
|
645
|
+
const argument = String(value)
|
|
646
|
+
if (/^[A-Za-z0-9_@%+=:,./\\-]+$/.test(argument)) return argument
|
|
647
|
+
if (process.platform === 'win32') return JSON.stringify(argument)
|
|
648
|
+
return `'${argument.replaceAll('\'', '\'"\'"\'')}'`
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
function serializeDisplayCommand (command) {
|
|
652
|
+
if (typeof command.displayCommand === 'string' && command.displayCommand.trim()) {
|
|
653
|
+
return command.displayCommand.trim()
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
if (command.usesShell) return command.shellCommand
|
|
657
|
+
|
|
658
|
+
return getDisplayArgv(command.argv).join(' ')
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
function getCommandDetails (command) {
|
|
662
|
+
if (command.usesShell) return
|
|
663
|
+
|
|
664
|
+
const details = getDisplayDetails(command.argv)
|
|
665
|
+
if (!details.exactCommandCollapsed) return
|
|
666
|
+
|
|
667
|
+
return details
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
function getDisplayArgv (argv) {
|
|
671
|
+
const { prefixAssignments, commandIndex, corepackIndex } = parseArgv(argv)
|
|
672
|
+
if (corepackIndex !== -1) return [...prefixAssignments, ...argv.slice(corepackIndex + 1)]
|
|
673
|
+
return [...prefixAssignments, ...argv.slice(commandIndex)]
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
function getDisplayDetails (argv) {
|
|
677
|
+
const { commandIndex, corepackIndex, pathAdjusted } = parseArgv(argv)
|
|
678
|
+
const displayArgv = getDisplayArgv(argv)
|
|
679
|
+
const details = {
|
|
680
|
+
exactCommandCollapsed: displayArgv.join(' ') !== argv.join(' '),
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
if (pathAdjusted) details.pathAdjusted = true
|
|
684
|
+
|
|
685
|
+
if (corepackIndex !== -1) {
|
|
686
|
+
details.runtimeWrapper = 'node/corepack'
|
|
687
|
+
details.packageManager = argv[corepackIndex + 1]
|
|
688
|
+
} else if (commandIndex > 0) {
|
|
689
|
+
details.runtimeWrapper = 'env'
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
return details
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
module.exports = {
|
|
696
|
+
runCommand,
|
|
697
|
+
buildCiWiringEnv,
|
|
698
|
+
buildDatadogEnv,
|
|
699
|
+
getBaseEnv,
|
|
700
|
+
getCommandDetails,
|
|
701
|
+
getCommandExecutionSettings,
|
|
702
|
+
serializeApprovalCommand,
|
|
703
|
+
serializeCommand,
|
|
704
|
+
serializeDisplayCommand,
|
|
705
|
+
withCiPreloads,
|
|
706
|
+
mergeNodeOptions,
|
|
707
|
+
}
|