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.
Files changed (185) hide show
  1. package/README.electron.md +7 -0
  2. package/README.md +17 -0
  3. package/ci/diagnose.js +2100 -0
  4. package/ci/init.js +23 -2
  5. package/ci/runbook.md +198 -0
  6. package/ci/test-optimization-validation/approval-artifacts.js +143 -0
  7. package/ci/test-optimization-validation/approval.js +299 -0
  8. package/ci/test-optimization-validation/artifact-id.js +20 -0
  9. package/ci/test-optimization-validation/bounded-json.js +121 -0
  10. package/ci/test-optimization-validation/ci-command-candidate.js +83 -0
  11. package/ci/test-optimization-validation/ci-discovery.js +181 -0
  12. package/ci/test-optimization-validation/ci-remediation.js +210 -0
  13. package/ci/test-optimization-validation/cli.js +903 -0
  14. package/ci/test-optimization-validation/command-blocker.js +81 -0
  15. package/ci/test-optimization-validation/command-output-policy.js +206 -0
  16. package/ci/test-optimization-validation/command-runner.js +707 -0
  17. package/ci/test-optimization-validation/command-suitability.js +471 -0
  18. package/ci/test-optimization-validation/executable-approval.js +48 -0
  19. package/ci/test-optimization-validation/executable.js +480 -0
  20. package/ci/test-optimization-validation/generated-file-policy.js +63 -0
  21. package/ci/test-optimization-validation/generated-files.js +351 -0
  22. package/ci/test-optimization-validation/generated-verifier.js +196 -0
  23. package/ci/test-optimization-validation/init-probe-preload.js +163 -0
  24. package/ci/test-optimization-validation/init-probe.js +246 -0
  25. package/ci/test-optimization-validation/late-initialization.js +63 -0
  26. package/ci/test-optimization-validation/local-command.js +163 -0
  27. package/ci/test-optimization-validation/manifest-loader.js +133 -0
  28. package/ci/test-optimization-validation/manifest-scaffold.js +738 -0
  29. package/ci/test-optimization-validation/manifest-schema.js +862 -0
  30. package/ci/test-optimization-validation/offline-fixtures.js +327 -0
  31. package/ci/test-optimization-validation/offline-output.js +406 -0
  32. package/ci/test-optimization-validation/payload-normalizer.js +72 -0
  33. package/ci/test-optimization-validation/plan-writer.js +1120 -0
  34. package/ci/test-optimization-validation/preflight-runner.js +114 -0
  35. package/ci/test-optimization-validation/redaction.js +331 -0
  36. package/ci/test-optimization-validation/report-writer.js +1508 -0
  37. package/ci/test-optimization-validation/safe-files.js +203 -0
  38. package/ci/test-optimization-validation/scenarios/auto-test-retries.js +159 -0
  39. package/ci/test-optimization-validation/scenarios/basic-reporting.js +657 -0
  40. package/ci/test-optimization-validation/scenarios/ci-wiring.js +780 -0
  41. package/ci/test-optimization-validation/scenarios/early-flake-detection.js +141 -0
  42. package/ci/test-optimization-validation/scenarios/helpers.js +539 -0
  43. package/ci/test-optimization-validation/scenarios/test-management.js +218 -0
  44. package/ci/test-optimization-validation/setup-runner.js +97 -0
  45. package/ci/test-optimization-validation/static-diagnosis.js +159 -0
  46. package/ci/test-optimization-validation/test-output.js +129 -0
  47. package/ci/test-optimization-validation-manifest.schema.json +1 -0
  48. package/ci/validate-test-optimization.js +3 -0
  49. package/ext/exporters.js +1 -0
  50. package/index.d.ts +121 -8
  51. package/init.js +18 -0
  52. package/initialize.mjs +1 -1
  53. package/loader-hook.mjs +28 -18
  54. package/openfeature.d.ts +1 -0
  55. package/openfeature.js +4 -0
  56. package/package.json +11 -8
  57. package/packages/datadog-instrumentations/src/ai.js +8 -2
  58. package/packages/datadog-instrumentations/src/child_process.js +1 -1
  59. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +1 -1
  60. package/packages/datadog-instrumentations/src/cucumber-worker-threads.js +9 -3
  61. package/packages/datadog-instrumentations/src/cucumber.js +31 -19
  62. package/packages/datadog-instrumentations/src/cypress-config.js +398 -52
  63. package/packages/datadog-instrumentations/src/fastify.js +7 -11
  64. package/packages/datadog-instrumentations/src/helpers/channel.js +74 -0
  65. package/packages/datadog-instrumentations/src/helpers/hook.js +27 -6
  66. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +8 -8
  67. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +28 -2
  68. package/packages/datadog-instrumentations/src/jest.js +44 -17
  69. package/packages/datadog-instrumentations/src/mariadb.js +1 -1
  70. package/packages/datadog-instrumentations/src/mocha/main.js +79 -32
  71. package/packages/datadog-instrumentations/src/mocha/utils.js +0 -4
  72. package/packages/datadog-instrumentations/src/mongodb.js +3 -3
  73. package/packages/datadog-instrumentations/src/mongoose.js +3 -3
  74. package/packages/datadog-instrumentations/src/mquery.js +2 -2
  75. package/packages/datadog-instrumentations/src/mysql.js +1 -1
  76. package/packages/datadog-instrumentations/src/next.js +24 -0
  77. package/packages/datadog-instrumentations/src/nyc.js +0 -2
  78. package/packages/datadog-instrumentations/src/oracledb.js +2 -2
  79. package/packages/datadog-instrumentations/src/pg.js +2 -2
  80. package/packages/datadog-instrumentations/src/playwright.js +162 -46
  81. package/packages/datadog-instrumentations/src/process.js +3 -0
  82. package/packages/datadog-instrumentations/src/router.js +18 -8
  83. package/packages/datadog-instrumentations/src/selenium.js +52 -26
  84. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +3 -3
  85. package/packages/datadog-instrumentations/src/vitest-main.js +14 -28
  86. package/packages/datadog-instrumentations/src/vitest-util.js +0 -7
  87. package/packages/datadog-instrumentations/src/vitest-worker.js +5 -8
  88. package/packages/datadog-instrumentations/src/ws.js +2 -1
  89. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +2 -2
  90. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +2 -1
  91. package/packages/datadog-plugin-aws-sdk/src/util.js +2 -2
  92. package/packages/datadog-plugin-cucumber/src/index.js +3 -2
  93. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +19 -57
  94. package/packages/datadog-plugin-cypress/src/index.js +6 -1
  95. package/packages/datadog-plugin-cypress/src/support.js +92 -26
  96. package/packages/datadog-plugin-electron/src/ipc.js +1 -1
  97. package/packages/datadog-plugin-graphql/src/execute.js +87 -45
  98. package/packages/datadog-plugin-graphql/src/validate.js +2 -2
  99. package/packages/datadog-plugin-http2/src/client.js +1 -1
  100. package/packages/datadog-plugin-langchain/src/handlers/embedding.js +2 -1
  101. package/packages/datadog-plugin-langchain/src/handlers/language_models.js +2 -1
  102. package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
  103. package/packages/datadog-plugin-mocha/src/index.js +2 -1
  104. package/packages/datadog-plugin-playwright/src/index.js +181 -65
  105. package/packages/datadog-plugin-router/src/index.js +11 -2
  106. package/packages/datadog-plugin-selenium/src/index.js +5 -36
  107. package/packages/datadog-plugin-vitest/src/index.js +5 -5
  108. package/packages/datadog-plugin-ws/src/close.js +2 -1
  109. package/packages/datadog-plugin-ws/src/producer.js +2 -1
  110. package/packages/datadog-plugin-ws/src/receiver.js +2 -1
  111. package/packages/datadog-plugin-ws/src/server.js +2 -1
  112. package/packages/dd-trace/src/aiguard/sdk.js +1 -1
  113. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +2 -2
  114. package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +3 -1
  115. package/packages/dd-trace/src/azure_metadata.js +2 -1
  116. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +93 -28
  117. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +1 -1
  118. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/index.js +160 -0
  119. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +288 -0
  120. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/payload-projection.js +84 -0
  121. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +369 -0
  122. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +60 -0
  123. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +41 -3
  124. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +7 -2
  125. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +1 -1
  126. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +6 -2
  127. package/packages/dd-trace/src/ci-visibility/rum.js +7 -0
  128. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +4 -0
  129. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +1 -1
  130. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +137 -9
  131. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +168 -8
  132. package/packages/dd-trace/src/ci-visibility/test-screenshot.js +90 -0
  133. package/packages/dd-trace/src/config/generated-config-types.d.ts +16 -0
  134. package/packages/dd-trace/src/config/helper.js +1 -0
  135. package/packages/dd-trace/src/config/index.js +10 -0
  136. package/packages/dd-trace/src/config/supported-configurations.json +66 -0
  137. package/packages/dd-trace/src/exporter.js +2 -0
  138. package/packages/dd-trace/src/exporters/common/client-library-headers.js +21 -0
  139. package/packages/dd-trace/src/exporters/common/request.js +59 -30
  140. package/packages/dd-trace/src/exporters/common/url.js +15 -1
  141. package/packages/dd-trace/src/feature-registry.js +10 -3
  142. package/packages/dd-trace/src/llmobs/experiments/client.js +113 -0
  143. package/packages/dd-trace/src/llmobs/experiments/dataset.js +154 -0
  144. package/packages/dd-trace/src/llmobs/experiments/experiment.js +283 -0
  145. package/packages/dd-trace/src/llmobs/experiments/index.js +152 -0
  146. package/packages/dd-trace/src/llmobs/experiments/noop.js +30 -0
  147. package/packages/dd-trace/src/llmobs/experiments/result.js +34 -0
  148. package/packages/dd-trace/src/llmobs/noop.js +6 -0
  149. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +20 -3
  150. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +2 -2
  151. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  152. package/packages/dd-trace/src/llmobs/plugins/openai/constants.js +8 -0
  153. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +48 -14
  154. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +43 -0
  155. package/packages/dd-trace/src/llmobs/sdk.js +17 -0
  156. package/packages/dd-trace/src/llmobs/tagger.js +68 -0
  157. package/packages/dd-trace/src/llmobs/telemetry.js +2 -1
  158. package/packages/dd-trace/src/llmobs/util.js +32 -0
  159. package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +322 -0
  160. package/packages/dd-trace/src/openfeature/configuration_source.js +90 -0
  161. package/packages/dd-trace/src/openfeature/flagging_provider.js +14 -23
  162. package/packages/dd-trace/src/openfeature/index.js +0 -2
  163. package/packages/dd-trace/src/openfeature/noop.js +1 -28
  164. package/packages/dd-trace/src/openfeature/register.js +16 -27
  165. package/packages/dd-trace/src/openfeature/remote_config.js +15 -18
  166. package/packages/dd-trace/src/openfeature/require-provider.js +18 -0
  167. package/packages/dd-trace/src/opentelemetry/span-ending-hook.js +10 -0
  168. package/packages/dd-trace/src/opentelemetry/span.js +5 -0
  169. package/packages/dd-trace/src/plugins/ci_plugin.js +20 -6
  170. package/packages/dd-trace/src/plugins/util/git.js +3 -2
  171. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +29 -5
  172. package/packages/dd-trace/src/plugins/util/test.js +56 -4
  173. package/packages/dd-trace/src/profiling/profilers/space.js +1 -1
  174. package/packages/dd-trace/src/profiling/profilers/wall.js +1 -0
  175. package/packages/dd-trace/src/proxy.js +84 -7
  176. package/packages/dd-trace/src/span_processor.js +5 -0
  177. package/packages/dd-trace/src/standalone/index.js +0 -14
  178. package/packages/dd-trace/src/telemetry/send-data.js +2 -2
  179. package/packages/dd-trace/src/util.js +26 -0
  180. package/vendor/dist/@apm-js-collab/code-transformer/index.js +1 -1
  181. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  182. package/vendor/dist/protobufjs/index.js +1 -1
  183. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  184. package/vendor/dist/shell-quote/index.js +1 -1
  185. package/packages/datadog-instrumentations/src/helpers/require-optional-peer.js +0 -17
@@ -0,0 +1,81 @@
1
+ 'use strict'
2
+
3
+ const FILESYSTEM_PERMISSION_PATTERN = /\b(?:EACCES|EPERM|Operation not permitted|Permission denied)\b/i
4
+ const PACKAGE_MANAGER_PATH_PATTERN = /(?:^|[/\\.])(?:corepack|npm|pnpm|yarn)(?:$|[/\\.])/i
5
+ const WATCHMAN_PATTERN = /\bwatchman\b/i
6
+
7
+ /**
8
+ * Identifies toolchain and execution-environment failures that happen before tests start.
9
+ *
10
+ * @param {object} result command result
11
+ * @param {string} [result.stdout] captured stdout
12
+ * @param {string} [result.stderr] captured stderr
13
+ * @returns {object|undefined} structured blocker diagnosis
14
+ */
15
+ function getCommandBlocker (result) {
16
+ const output = `${result.stdout || ''}\n${result.stderr || ''}`
17
+ const yarnVersions = output.match(
18
+ /defines "packageManager": "(yarn@[^"]+)"[\s\S]*?current global version of Yarn is ([0-9][0-9.]*)\./i
19
+ )
20
+ if (yarnVersions) {
21
+ return {
22
+ kind: 'package-manager-version-mismatch',
23
+ summary: `The test command did not start because it resolved Yarn ${yarnVersions[2]}, but package.json ` +
24
+ `requires ${yarnVersions[1]}. No Test Optimization conclusion was reached.`,
25
+ recommendation: 'Run the approved command through the project-declared Yarn version, using its configured ' +
26
+ '`yarnPath` or an explicit Corepack Yarn command, then render and approve a fresh plan.',
27
+ signals: getMatchingLines(output, /packageManager|current global version of Yarn/i),
28
+ toolchainBlocked: true,
29
+ }
30
+ }
31
+
32
+ if (WATCHMAN_PATTERN.test(output) && FILESYSTEM_PERMISSION_PATTERN.test(output)) {
33
+ return {
34
+ kind: 'watchman-filesystem-blocked',
35
+ summary: 'The execution environment blocked Watchman state access before tests started. No CI wiring or ' +
36
+ 'Test Optimization conclusion was reached.',
37
+ recommendation: 'Rerun in an environment where Watchman can access its state directory. If the CI job ' +
38
+ 'itself disables Watchman, preserve that exact setting in the replay command.',
39
+ signals: getMatchingLines(output, /watchman|EACCES|EPERM|Operation not permitted|Permission denied/i),
40
+ blockedByExecutionEnvironment: true,
41
+ }
42
+ }
43
+
44
+ const permissionLines = getMatchingLines(
45
+ output,
46
+ /EACCES|EPERM|Operation not permitted|Permission denied/i
47
+ )
48
+ if (permissionLines.some(line => PACKAGE_MANAGER_PATH_PATTERN.test(line))) {
49
+ return {
50
+ kind: 'package-manager-filesystem-blocked',
51
+ summary: 'The test command did not start because the package manager could not write to its tool or cache ' +
52
+ 'directory in this execution environment. No Test Optimization conclusion was reached.',
53
+ recommendation: 'Rerun with the project package manager already installed and a writable package-manager ' +
54
+ 'home or cache directory. Do not interpret this launcher failure as a Test Optimization problem.',
55
+ signals: permissionLines,
56
+ blockedByExecutionEnvironment: true,
57
+ }
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Returns a small de-duplicated set of matching output lines.
63
+ *
64
+ * @param {string} output command output
65
+ * @param {RegExp} pattern interesting-line pattern
66
+ * @returns {string[]} matching lines
67
+ */
68
+ function getMatchingLines (output, pattern) {
69
+ const lines = []
70
+ const seen = new Set()
71
+ for (const line of output.split(/\r?\n/)) {
72
+ const value = line.trim()
73
+ if (!value || seen.has(value) || !pattern.test(value)) continue
74
+ seen.add(value)
75
+ lines.push(value)
76
+ if (lines.length === 6) break
77
+ }
78
+ return lines
79
+ }
80
+
81
+ module.exports = { getCommandBlocker }
@@ -0,0 +1,206 @@
1
+ 'use strict'
2
+
3
+ const fs = require('node:fs')
4
+ const path = require('node:path')
5
+
6
+ /**
7
+ * Returns command-created paths that must be declared and removed after validation.
8
+ *
9
+ * @param {object} command structured command
10
+ * @returns {string[]} absolute output paths
11
+ */
12
+ function getCommandOutputPaths (command) {
13
+ const paths = new Set((command.outputPaths || []).map(outputPath => path.resolve(command.cwd, outputPath)))
14
+ const tokens = command.usesShell ? tokenizeShell(command.shellCommand) : command.argv || []
15
+ const coverageDirectory = getCoverageDirectory(tokens)
16
+ if (coverageDirectory) paths.add(path.resolve(command.cwd, coverageDirectory))
17
+ return [...paths]
18
+ }
19
+
20
+ /**
21
+ * Refuses pre-existing outputs and records parent identities for fail-closed cleanup.
22
+ *
23
+ * @param {object} input isolation inputs
24
+ * @param {object} input.command structured command
25
+ * @param {string} input.artifactRoot validation results root
26
+ * @param {string} [input.repositoryRoot] repository root
27
+ * @returns {{outputPath: string, repositoryRoot: string, parentIdentities: object[]}[]} cleanup state
28
+ */
29
+ function prepareCommandOutputs ({ command, artifactRoot, repositoryRoot }) {
30
+ repositoryRoot = path.resolve(repositoryRoot || path.dirname(path.resolve(artifactRoot)))
31
+ const states = []
32
+ const outputPaths = getCommandOutputPaths(command)
33
+
34
+ for (const outputPath of outputPaths) {
35
+ assertSafeOutputPath({ outputPath, repositoryRoot, artifactRoot, command })
36
+ }
37
+ for (const outputPath of outputPaths) {
38
+ if (pathExists(outputPath)) {
39
+ throw new Error(
40
+ `Command output path already exists and will not be moved or overwritten: ${outputPath}. ` +
41
+ 'Remove it or choose a command that writes to a fresh output path, then render a new approval plan.'
42
+ )
43
+ }
44
+ states.push({
45
+ outputPath,
46
+ repositoryRoot,
47
+ parentIdentities: captureExistingParentIdentities(outputPath, repositoryRoot),
48
+ })
49
+ }
50
+
51
+ return states
52
+ }
53
+
54
+ /**
55
+ * Removes outputs created by a command after revalidating every parent component.
56
+ *
57
+ * @param {{outputPath: string, repositoryRoot: string, parentIdentities: object[]}[]} states cleanup state
58
+ * @returns {object[]} customer-safe cleanup summary
59
+ */
60
+ function cleanupCommandOutputs (states) {
61
+ const actions = []
62
+ for (let index = states.length - 1; index >= 0; index--) {
63
+ const state = states[index]
64
+ assertOutputParentsUnchanged(state)
65
+ const existed = fs.existsSync(state.outputPath)
66
+ if (existed) fs.rmSync(state.outputPath, { force: true, recursive: true })
67
+ actions.push({ outputPath: state.outputPath, action: existed ? 'removed' : 'absent' })
68
+ }
69
+ return actions.reverse()
70
+ }
71
+
72
+ /**
73
+ * Records every existing directory from repository.root through an output parent.
74
+ *
75
+ * @param {string} outputPath output path
76
+ * @param {string} repositoryRoot repository root
77
+ * @returns {{path: string, dev: number, ino: number}[]} parent identities
78
+ */
79
+ function captureExistingParentIdentities (outputPath, repositoryRoot) {
80
+ const identities = []
81
+ const relative = path.relative(repositoryRoot, path.dirname(outputPath))
82
+ let current = repositoryRoot
83
+ for (const segment of relative ? relative.split(path.sep) : []) {
84
+ const stat = fs.lstatSync(current)
85
+ assertRegularDirectory(stat, current)
86
+ identities.push({ path: current, dev: stat.dev, ino: stat.ino })
87
+ current = path.join(current, segment)
88
+ if (!pathExists(current)) return identities
89
+ }
90
+
91
+ const stat = fs.lstatSync(current)
92
+ assertRegularDirectory(stat, current)
93
+ identities.push({ path: current, dev: stat.dev, ino: stat.ino })
94
+ return identities
95
+ }
96
+
97
+ /**
98
+ * Refuses cleanup if an existing parent changed or a new parent is a symbolic link.
99
+ *
100
+ * @param {object} state cleanup state
101
+ */
102
+ function assertOutputParentsUnchanged (state) {
103
+ for (const identity of state.parentIdentities) {
104
+ const stat = fs.lstatSync(identity.path)
105
+ assertRegularDirectory(stat, identity.path)
106
+ if (stat.dev !== identity.dev || stat.ino !== identity.ino) {
107
+ throw new Error(`Refusing command output cleanup because a parent directory changed: ${identity.path}`)
108
+ }
109
+ }
110
+
111
+ const lastExisting = state.parentIdentities.at(-1).path
112
+ const relative = path.relative(lastExisting, path.dirname(state.outputPath))
113
+ let current = lastExisting
114
+ for (const segment of relative ? relative.split(path.sep) : []) {
115
+ current = path.join(current, segment)
116
+ if (!pathExists(current)) break
117
+ assertRegularDirectory(fs.lstatSync(current), current)
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Refuses symbolic links and non-directory parent components.
123
+ *
124
+ * @param {fs.Stats} stat path status
125
+ * @param {string} directory directory path
126
+ */
127
+ function assertRegularDirectory (stat, directory) {
128
+ if (!stat.isDirectory() || stat.isSymbolicLink()) {
129
+ throw new Error(`Refusing command output cleanup through a non-regular directory: ${directory}`)
130
+ }
131
+ }
132
+
133
+ function pathExists (filename) {
134
+ try {
135
+ fs.lstatSync(filename)
136
+ return true
137
+ } catch (error) {
138
+ if (error.code === 'ENOENT') return false
139
+ throw error
140
+ }
141
+ }
142
+
143
+ function getCoverageDirectory (tokens) {
144
+ let coverageEnabled = false
145
+ for (let index = 0; index < tokens.length; index++) {
146
+ const token = String(tokens[index])
147
+ if (token === '--coverage' || token === '--coverage=true') coverageEnabled = true
148
+ const inline = /^(?:--coverageDirectory|--coverage-directory|--coverage\.reportsDirectory)=(.+)$/.exec(token)
149
+ if (inline) return inline[1]
150
+ if (['--coverageDirectory', '--coverage-directory', '--coverage.reportsDirectory'].includes(token)) {
151
+ return tokens[index + 1]
152
+ }
153
+ }
154
+ return coverageEnabled ? 'coverage' : undefined
155
+ }
156
+
157
+ function tokenizeShell (source) {
158
+ return String(source || '').match(/"[^"]*"|'[^']*'|[^\s]+/g)?.map(token => {
159
+ return token.replace(/^(?:"([\s\S]*)"|'([\s\S]*)')$/, '$1$2')
160
+ }) || []
161
+ }
162
+
163
+ function assertSafeOutputPath ({ outputPath, repositoryRoot, artifactRoot, command }) {
164
+ const relative = path.relative(repositoryRoot, outputPath)
165
+ if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) {
166
+ throw new Error(`Command output path must be a child of repository.root: ${outputPath}`)
167
+ }
168
+ assertPhysicalOutputPathInsideRepository(outputPath, repositoryRoot)
169
+ if (isPathInside(artifactRoot, outputPath) || isPathInside(outputPath, artifactRoot)) {
170
+ throw new Error(`Command output path must not contain or overlap validation artifacts: ${outputPath}`)
171
+ }
172
+ if (path.resolve(command.cwd) === outputPath) {
173
+ throw new Error(`Command output path must not replace the command working directory: ${outputPath}`)
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Rejects an output whose nearest existing ancestor resolves outside the repository.
179
+ *
180
+ * @param {string} outputPath absolute output path
181
+ * @param {string} repositoryRoot absolute repository root
182
+ * @returns {void}
183
+ */
184
+ function assertPhysicalOutputPathInsideRepository (outputPath, repositoryRoot) {
185
+ const physicalRoot = fs.realpathSync(repositoryRoot)
186
+ let existingPath = outputPath
187
+ while (!fs.existsSync(existingPath) && path.dirname(existingPath) !== existingPath) {
188
+ existingPath = path.dirname(existingPath)
189
+ }
190
+
191
+ const physicalExistingPath = fs.realpathSync(existingPath)
192
+ if (!isPathInside(physicalRoot, physicalExistingPath)) {
193
+ throw new Error(`Command output path must not resolve outside physical repository.root: ${outputPath}`)
194
+ }
195
+ }
196
+
197
+ function isPathInside (directory, filename) {
198
+ const relative = path.relative(path.resolve(directory), path.resolve(filename))
199
+ return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative))
200
+ }
201
+
202
+ module.exports = {
203
+ cleanupCommandOutputs,
204
+ getCommandOutputPaths,
205
+ prepareCommandOutputs,
206
+ }