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.
Files changed (178) 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 +10 -7
  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 +20 -7
  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/hook.js +27 -6
  65. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +8 -8
  66. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +28 -2
  67. package/packages/datadog-instrumentations/src/jest.js +26 -0
  68. package/packages/datadog-instrumentations/src/mariadb.js +1 -1
  69. package/packages/datadog-instrumentations/src/mocha/main.js +0 -2
  70. package/packages/datadog-instrumentations/src/mocha/utils.js +0 -4
  71. package/packages/datadog-instrumentations/src/mongodb.js +3 -3
  72. package/packages/datadog-instrumentations/src/mongoose.js +3 -3
  73. package/packages/datadog-instrumentations/src/mquery.js +2 -2
  74. package/packages/datadog-instrumentations/src/mysql.js +1 -1
  75. package/packages/datadog-instrumentations/src/next.js +24 -0
  76. package/packages/datadog-instrumentations/src/nyc.js +0 -2
  77. package/packages/datadog-instrumentations/src/oracledb.js +2 -2
  78. package/packages/datadog-instrumentations/src/pg.js +2 -2
  79. package/packages/datadog-instrumentations/src/playwright.js +159 -22
  80. package/packages/datadog-instrumentations/src/process.js +3 -0
  81. package/packages/datadog-instrumentations/src/router.js +18 -8
  82. package/packages/datadog-instrumentations/src/selenium.js +52 -26
  83. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +2 -2
  84. package/packages/datadog-instrumentations/src/vitest-main.js +0 -2
  85. package/packages/datadog-instrumentations/src/ws.js +2 -1
  86. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +2 -2
  87. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +2 -1
  88. package/packages/datadog-plugin-aws-sdk/src/util.js +2 -2
  89. package/packages/datadog-plugin-cucumber/src/index.js +1 -1
  90. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +19 -57
  91. package/packages/datadog-plugin-cypress/src/index.js +6 -1
  92. package/packages/datadog-plugin-cypress/src/support.js +92 -26
  93. package/packages/datadog-plugin-electron/src/ipc.js +1 -1
  94. package/packages/datadog-plugin-graphql/src/execute.js +87 -45
  95. package/packages/datadog-plugin-graphql/src/validate.js +2 -2
  96. package/packages/datadog-plugin-http2/src/client.js +1 -1
  97. package/packages/datadog-plugin-langchain/src/handlers/embedding.js +2 -1
  98. package/packages/datadog-plugin-langchain/src/handlers/language_models.js +2 -1
  99. package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
  100. package/packages/datadog-plugin-playwright/src/index.js +177 -64
  101. package/packages/datadog-plugin-router/src/index.js +11 -2
  102. package/packages/datadog-plugin-selenium/src/index.js +5 -36
  103. package/packages/datadog-plugin-ws/src/close.js +2 -1
  104. package/packages/datadog-plugin-ws/src/producer.js +2 -1
  105. package/packages/datadog-plugin-ws/src/receiver.js +2 -1
  106. package/packages/datadog-plugin-ws/src/server.js +2 -1
  107. package/packages/dd-trace/src/aiguard/sdk.js +1 -1
  108. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +2 -2
  109. package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +3 -1
  110. package/packages/dd-trace/src/azure_metadata.js +2 -1
  111. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +93 -28
  112. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +1 -1
  113. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/index.js +160 -0
  114. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +288 -0
  115. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/payload-projection.js +84 -0
  116. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +369 -0
  117. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +60 -0
  118. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +41 -3
  119. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +7 -2
  120. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +1 -1
  121. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +6 -2
  122. package/packages/dd-trace/src/ci-visibility/rum.js +7 -0
  123. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +4 -0
  124. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +1 -1
  125. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +137 -9
  126. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +168 -8
  127. package/packages/dd-trace/src/ci-visibility/test-screenshot.js +90 -0
  128. package/packages/dd-trace/src/config/generated-config-types.d.ts +16 -0
  129. package/packages/dd-trace/src/config/helper.js +1 -0
  130. package/packages/dd-trace/src/config/index.js +10 -0
  131. package/packages/dd-trace/src/config/supported-configurations.json +66 -0
  132. package/packages/dd-trace/src/exporter.js +2 -0
  133. package/packages/dd-trace/src/exporters/common/client-library-headers.js +21 -0
  134. package/packages/dd-trace/src/exporters/common/request.js +59 -30
  135. package/packages/dd-trace/src/exporters/common/url.js +15 -1
  136. package/packages/dd-trace/src/feature-registry.js +10 -3
  137. package/packages/dd-trace/src/llmobs/experiments/client.js +113 -0
  138. package/packages/dd-trace/src/llmobs/experiments/dataset.js +154 -0
  139. package/packages/dd-trace/src/llmobs/experiments/experiment.js +283 -0
  140. package/packages/dd-trace/src/llmobs/experiments/index.js +152 -0
  141. package/packages/dd-trace/src/llmobs/experiments/noop.js +30 -0
  142. package/packages/dd-trace/src/llmobs/experiments/result.js +34 -0
  143. package/packages/dd-trace/src/llmobs/noop.js +6 -0
  144. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +20 -3
  145. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +2 -2
  146. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  147. package/packages/dd-trace/src/llmobs/plugins/openai/constants.js +8 -0
  148. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +48 -14
  149. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +43 -0
  150. package/packages/dd-trace/src/llmobs/sdk.js +17 -0
  151. package/packages/dd-trace/src/llmobs/tagger.js +68 -0
  152. package/packages/dd-trace/src/llmobs/telemetry.js +2 -1
  153. package/packages/dd-trace/src/llmobs/util.js +32 -0
  154. package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +322 -0
  155. package/packages/dd-trace/src/openfeature/configuration_source.js +90 -0
  156. package/packages/dd-trace/src/openfeature/flagging_provider.js +14 -23
  157. package/packages/dd-trace/src/openfeature/index.js +0 -2
  158. package/packages/dd-trace/src/openfeature/noop.js +1 -28
  159. package/packages/dd-trace/src/openfeature/register.js +16 -27
  160. package/packages/dd-trace/src/openfeature/remote_config.js +15 -18
  161. package/packages/dd-trace/src/openfeature/require-provider.js +18 -0
  162. package/packages/dd-trace/src/opentelemetry/span-ending-hook.js +10 -0
  163. package/packages/dd-trace/src/opentelemetry/span.js +5 -0
  164. package/packages/dd-trace/src/plugins/ci_plugin.js +20 -6
  165. package/packages/dd-trace/src/plugins/util/git.js +3 -2
  166. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +29 -5
  167. package/packages/dd-trace/src/plugins/util/test.js +56 -4
  168. package/packages/dd-trace/src/proxy.js +84 -7
  169. package/packages/dd-trace/src/span_processor.js +5 -0
  170. package/packages/dd-trace/src/standalone/index.js +0 -14
  171. package/packages/dd-trace/src/telemetry/send-data.js +2 -2
  172. package/packages/dd-trace/src/util.js +26 -0
  173. package/vendor/dist/@apm-js-collab/code-transformer/index.js +1 -1
  174. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  175. package/vendor/dist/protobufjs/index.js +1 -1
  176. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  177. package/vendor/dist/shell-quote/index.js +1 -1
  178. package/packages/datadog-instrumentations/src/helpers/require-optional-peer.js +0 -17
@@ -0,0 +1,471 @@
1
+ 'use strict'
2
+
3
+ const fs = require('node:fs')
4
+ const path = require('node:path')
5
+
6
+ const MAX_CONFIG_BYTES = 512 * 1024
7
+ const MAX_STATIC_CONFIG_ARRAY_BYTES = 4096
8
+ const MAX_STATIC_CONFIG_PATTERNS = 32
9
+ const MAX_STATIC_CONFIG_PATTERN_BYTES = 256
10
+ const JEST_LOCAL_PATH_PATTERN = /(['"])(<rootDir>\/[^'"\r\n]+)\1/g
11
+ const JEST_ROOT_DIR_PATTERN = /\brootDir\s*:\s*(['"])([^'"]+)\1/
12
+ const TYPECHECK_ENABLED_PATTERN = /typecheck\s*:\s*\{[\s\S]{0,2000}?enabled\s*:\s*true/
13
+ const VITEST_CONFIG_FILENAMES = [
14
+ 'vitest.config.js',
15
+ 'vitest.config.mjs',
16
+ 'vitest.config.cjs',
17
+ 'vitest.config.ts',
18
+ 'vitest.config.mts',
19
+ 'vitest.config.cts',
20
+ 'vite.config.js',
21
+ 'vite.config.mjs',
22
+ 'vite.config.cjs',
23
+ 'vite.config.ts',
24
+ 'vite.config.mts',
25
+ 'vite.config.cts',
26
+ ]
27
+
28
+ /**
29
+ * Returns why a planned command is unsuitable for deterministic validation.
30
+ *
31
+ * @param {object} input suitability input
32
+ * @param {object} input.command manifest command
33
+ * @param {object} input.framework manifest framework
34
+ * @param {string} input.label planned command label
35
+ * @param {string} input.repositoryRoot repository root
36
+ * @returns {string|undefined} suitability error
37
+ */
38
+ function getCommandSuitabilityError ({ command, framework, label, repositoryRoot }) {
39
+ const yarnError = getRepositoryYarnError(command, repositoryRoot)
40
+ if (yarnError) return yarnError
41
+
42
+ if (framework.framework === 'jest') {
43
+ const missingInputError = getJestMissingLocalInputError(framework)
44
+ if (missingInputError) return missingInputError
45
+ }
46
+
47
+ if (framework.framework === 'vitest' &&
48
+ (label === 'the selected test command' || label.includes('advanced-feature'))) {
49
+ if (label.includes('advanced-feature')) {
50
+ const generatedPathError = getVitestGeneratedPathError(command, framework, repositoryRoot)
51
+ if (generatedPathError) return generatedPathError
52
+ }
53
+ return getVitestTypecheckError(command, label.includes('advanced-feature'), repositoryRoot)
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Returns an error for an exact local file referenced by Jest config but absent before execution.
59
+ *
60
+ * @param {object} framework manifest framework entry
61
+ * @returns {string|undefined} suitability error
62
+ */
63
+ function getJestMissingLocalInputError (framework) {
64
+ for (const configFile of framework.project?.configFiles || []) {
65
+ const config = readConfig(configFile)
66
+ if (!config) continue
67
+
68
+ const rootDirMatch = config.match(JEST_ROOT_DIR_PATTERN)
69
+ const rootDir = rootDirMatch
70
+ ? path.resolve(path.dirname(configFile), rootDirMatch[2])
71
+ : path.dirname(configFile)
72
+
73
+ for (const match of config.matchAll(JEST_LOCAL_PATH_PATTERN)) {
74
+ const configuredPath = match[2]
75
+ if (/[$*?{}[\]]/.test(configuredPath) || !/\.(?:[cm]?[jt]sx?|json)$/.test(configuredPath)) continue
76
+
77
+ const localPath = path.resolve(rootDir, configuredPath.slice('<rootDir>/'.length))
78
+ if (fs.existsSync(localPath) || isProducedBySetup(framework, localPath)) continue
79
+
80
+ return `uses Jest config ${configFile}, which references missing local input ${localPath}. ` +
81
+ 'Choose a representative whose config inputs already exist, or declare the reviewed setup command and ' +
82
+ 'its output path before marking this framework runnable.'
83
+ }
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Checks whether an approved setup command declares the missing input as an output.
89
+ *
90
+ * @param {object} framework manifest framework entry
91
+ * @param {string} localPath missing local input
92
+ * @returns {boolean} whether setup declares the input
93
+ */
94
+ function isProducedBySetup (framework, localPath) {
95
+ for (const command of framework.setup?.commands || []) {
96
+ for (const outputPath of command.outputPaths || []) {
97
+ const relative = path.relative(path.resolve(outputPath), localPath)
98
+ if (relative === '' || (relative && !relative.startsWith('..') && !path.isAbsolute(relative))) return true
99
+ }
100
+ }
101
+ return false
102
+ }
103
+
104
+ /**
105
+ * @param {{ usesShell?: boolean, argv?: string[] }} command
106
+ * @param {string} repositoryRoot
107
+ */
108
+ function getRepositoryYarnError (command, repositoryRoot) {
109
+ if (command.usesShell || path.basename(command.argv?.[0] || '') !== 'yarn') return
110
+
111
+ const releaseDirectory = path.join(repositoryRoot, '.yarn', 'releases')
112
+ let releases
113
+ try {
114
+ releases = fs.readdirSync(releaseDirectory)
115
+ .filter(filename => /^yarn-[^/]+\.cjs$/.test(filename))
116
+ .sort()
117
+ } catch {}
118
+ if (releases?.length > 0) {
119
+ const release = path.posix.join('.yarn', 'releases', releases.at(-1))
120
+ return `uses bare "yarn", but this repository pins ${release}. Use the structured command ` +
121
+ `argv [process.execPath, "${release}", ...] so validation does not depend on an ambient Yarn shim.`
122
+ }
123
+
124
+ const packageManager = getRepositoryPackageManager(repositoryRoot)
125
+ const match = /^yarn@(\d+)(?:\.|$)/.exec(packageManager || '')
126
+ if (match && Number(match[1]) > 1) {
127
+ return `uses bare "yarn", but package.json requires ${packageManager}. Use an explicit Corepack command ` +
128
+ '`argv ["corepack", "yarn", ...]` or the repository-configured `yarnPath` so validation cannot resolve an ' +
129
+ 'incompatible ambient Yarn version.'
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Reads the package-manager requirement declared by the repository root.
135
+ *
136
+ * @param {string} repositoryRoot repository root
137
+ * @returns {string|undefined} declared package-manager requirement
138
+ */
139
+ function getRepositoryPackageManager (repositoryRoot) {
140
+ const packageJson = readRepositoryConfig(path.join(repositoryRoot, 'package.json'), repositoryRoot)
141
+ if (!packageJson) return
142
+
143
+ try {
144
+ const value = JSON.parse(packageJson).packageManager
145
+ return typeof value === 'string' ? value : undefined
146
+ } catch {}
147
+ }
148
+
149
+ function getVitestTypecheckError (command, generatedTest, repositoryRoot) {
150
+ const commandText = command.usesShell ? command.shellCommand || '' : (command.argv || []).join(' ')
151
+ if (/(^|\s)--typecheck\.enabled=false(?:\s|$)/.test(commandText)) return
152
+ if (/(^|\s)--typecheck(?:\s|$|=)/.test(commandText)) {
153
+ return getTypecheckError('runs Vitest with --typecheck', generatedTest)
154
+ }
155
+
156
+ const configFile = getVitestConfigFile(command, repositoryRoot)
157
+ if (!configFile || !configEnablesTypecheck(configFile, repositoryRoot)) return
158
+
159
+ return getTypecheckError(`uses typecheck-enabled Vitest config ${configFile}`, generatedTest)
160
+ }
161
+
162
+ function getTypecheckError (prefix, generatedTest) {
163
+ if (generatedTest) {
164
+ return `${prefix} for generated runtime tests. Typecheck projects can count each generated test twice; use ` +
165
+ 'an existing typecheck-disabled config, or create a declared temporary config for advanced checks.'
166
+ }
167
+ return `${prefix} for the selected direct test command. Typecheck can duplicate runtime tests and make ` +
168
+ 'unrelated source errors fail Basic Reporting; use an existing runtime-only config or append ' +
169
+ '`--typecheck.enabled=false`.'
170
+ }
171
+
172
+ function getVitestGeneratedPathError (command, framework, repositoryRoot) {
173
+ const configFile = getVitestConfigFile(command, repositoryRoot)
174
+ if (!configFile) return
175
+ const config = readRepositoryConfig(configFile, repositoryRoot)
176
+ if (!config) return
177
+
178
+ const includes = getLiteralTestConfigPatterns(config, 'include')
179
+ const excludes = getLiteralTestConfigPatterns(config, 'exclude')
180
+ if (includes.length === 0 && excludes.length === 0) return
181
+
182
+ for (const file of framework.generatedTestStrategy?.files || []) {
183
+ const relative = path.relative(path.dirname(configFile), file.path).split(path.sep).join('/')
184
+ if (relative === '..' || relative.startsWith('../') || path.isAbsolute(relative)) continue
185
+
186
+ if (includes.length > 0 && !includes.some(pattern => matchesGlob(relative, pattern))) {
187
+ return `uses temporary test path ${file.path}, which does not match the literal test.include patterns in ` +
188
+ `${configFile}: ${includes.join(', ')}. Choose a temporary test path accepted by the selected Vitest ` +
189
+ 'config ' +
190
+ 'before asking for approval.'
191
+ }
192
+ if (excludes.some(pattern => matchesGlob(relative, pattern))) {
193
+ return `uses temporary test path ${file.path}, which matches a literal test.exclude pattern in ` +
194
+ `${configFile}. Choose a temporary test path accepted by the selected Vitest config before asking for ` +
195
+ 'approval.'
196
+ }
197
+ }
198
+ }
199
+
200
+ function getLiteralTestConfigPatterns (config, property) {
201
+ const candidates = []
202
+ for (const objectStart of getLiteralObjectStarts(config, 'test')) {
203
+ const patterns = getDirectLiteralArrayPatterns(config, objectStart, property)
204
+ if (patterns.length > 0) candidates.push(patterns)
205
+ }
206
+ if (candidates.length === 0) return []
207
+
208
+ const first = JSON.stringify(candidates[0])
209
+ return candidates.every(candidate => JSON.stringify(candidate) === first) ? candidates[0] : []
210
+ }
211
+
212
+ function getLiteralObjectStarts (config, property) {
213
+ const starts = []
214
+ visitConfigSource(config, ({ index }) => {
215
+ if (!isPropertyAt(config, index, property)) return
216
+ const match = new RegExp(String.raw`^${property}\s*:\s*\{`).exec(config.slice(index))
217
+ if (match) starts.push(index + match[0].lastIndexOf('{'))
218
+ })
219
+ return starts
220
+ }
221
+
222
+ function getDirectLiteralArrayPatterns (config, objectStart, property) {
223
+ let patterns = []
224
+ visitConfigSource(config, ({ index, depth }) => {
225
+ if (patterns.length > 0 || depth !== 1 || !isPropertyAt(config, index, property)) return
226
+ const match = new RegExp(String.raw`^${property}\s*:\s*\[`).exec(config.slice(index))
227
+ if (match) patterns = readLiteralStringArray(config, index + match[0].length)
228
+ }, objectStart)
229
+ return patterns
230
+ }
231
+
232
+ function readLiteralStringArray (config, offset) {
233
+ const patterns = []
234
+ let quote = ''
235
+ let value = ''
236
+ let closed = false
237
+ const end = Math.min(config.length, offset + MAX_STATIC_CONFIG_ARRAY_BYTES)
238
+ for (let index = offset; index < end; index++) {
239
+ const character = config[index]
240
+ if (!quote) {
241
+ if (character === ']') {
242
+ closed = true
243
+ break
244
+ }
245
+ if (character === '"' || character === '\'') {
246
+ quote = character
247
+ value = ''
248
+ } else if (character !== ',' && !/\s/.test(character)) {
249
+ return []
250
+ }
251
+ continue
252
+ }
253
+
254
+ if (character === quote) {
255
+ if (value.length > MAX_STATIC_CONFIG_PATTERN_BYTES || patterns.length === MAX_STATIC_CONFIG_PATTERNS) {
256
+ return []
257
+ }
258
+ patterns.push(value)
259
+ quote = ''
260
+ } else if (character.charCodeAt(0) === 92) {
261
+ return []
262
+ } else if (character === '\r' || character === '\n') {
263
+ return []
264
+ } else {
265
+ value += character
266
+ }
267
+ }
268
+ return closed && !quote ? patterns : []
269
+ }
270
+
271
+ function visitConfigSource (config, visitor, objectStart = -1) {
272
+ let blockComment = false
273
+ let depth = objectStart === -1 ? 0 : 1
274
+ let lineComment = false
275
+ let quote = ''
276
+ const start = objectStart === -1 ? 0 : objectStart + 1
277
+
278
+ for (let index = start; index < config.length; index++) {
279
+ const character = config[index]
280
+ const next = config[index + 1]
281
+ if (lineComment) {
282
+ if (character === '\n') lineComment = false
283
+ continue
284
+ }
285
+ if (blockComment) {
286
+ if (character === '*' && next === '/') {
287
+ blockComment = false
288
+ index++
289
+ }
290
+ continue
291
+ }
292
+ if (quote) {
293
+ if (character.charCodeAt(0) === 92) {
294
+ index++
295
+ } else if (character === quote) {
296
+ quote = ''
297
+ }
298
+ continue
299
+ }
300
+ if (character === '/' && next === '/') {
301
+ lineComment = true
302
+ index++
303
+ continue
304
+ }
305
+ if (character === '/' && next === '*') {
306
+ blockComment = true
307
+ index++
308
+ continue
309
+ }
310
+ if (character === '"' || character === '\'' || character === '`') {
311
+ quote = character
312
+ continue
313
+ }
314
+ if (character === '{') {
315
+ depth++
316
+ continue
317
+ }
318
+ if (character === '}') {
319
+ depth--
320
+ if (objectStart !== -1 && depth === 0) break
321
+ continue
322
+ }
323
+ visitor({ index, depth })
324
+ }
325
+ }
326
+
327
+ function isPropertyAt (config, index, property) {
328
+ if (!config.startsWith(property, index)) return false
329
+ const before = config[index - 1]
330
+ const after = config[index + property.length]
331
+ return !isIdentifierCharacter(before) && !isIdentifierCharacter(after)
332
+ }
333
+
334
+ function isIdentifierCharacter (character) {
335
+ return Boolean(character && /[A-Za-z0-9_$]/.test(character))
336
+ }
337
+
338
+ function matchesGlob (filename, pattern) {
339
+ const normalized = pattern.replace(/^\.\//, '')
340
+ let source = '^'
341
+ let index = 0
342
+
343
+ while (index < normalized.length) {
344
+ const character = normalized[index]
345
+ if (character === '*' && normalized[index + 1] === '*') {
346
+ if (normalized[index + 2] === '/') {
347
+ source += '(?:[^/]+/)*'
348
+ index += 3
349
+ } else {
350
+ source += '.*'
351
+ index += 2
352
+ }
353
+ continue
354
+ } else if (character === '*') {
355
+ source += '[^/]*'
356
+ } else if (character === '?' && normalized[index + 1] === '(') {
357
+ const end = normalized.indexOf(')', index + 2)
358
+ const value = end === -1 ? '' : normalized.slice(index + 2, end)
359
+ if (/^[A-Za-z0-9._-]+$/.test(value)) {
360
+ source += `(?:${escapeRegex(value)})?`
361
+ index = end + 1
362
+ continue
363
+ } else {
364
+ source += '[^/]'
365
+ }
366
+ } else if (character === '?') {
367
+ source += '[^/]'
368
+ } else if (character === '[') {
369
+ const end = normalized.indexOf(']', index + 1)
370
+ const value = end === -1 ? '' : normalized.slice(index + 1, end)
371
+ if (/^!?[A-Za-z0-9_-]+$/.test(value)) {
372
+ source += `[${value.startsWith('!') ? `^${value.slice(1)}` : value}]`
373
+ index = end + 1
374
+ continue
375
+ } else {
376
+ source += String.raw`\[`
377
+ }
378
+ } else if (character === '{') {
379
+ const end = normalized.indexOf('}', index + 1)
380
+ const values = end === -1 ? [] : normalized.slice(index + 1, end).split(',')
381
+ if (values.length > 1 && values.every(value => /^[A-Za-z0-9._-]+$/.test(value))) {
382
+ source += `(?:${values.map(escapeRegex).join('|')})`
383
+ index = end + 1
384
+ continue
385
+ } else {
386
+ source += String.raw`\{`
387
+ }
388
+ } else {
389
+ source += escapeRegex(character)
390
+ }
391
+ index++
392
+ }
393
+
394
+ return new RegExp(`${source}$`).test(filename)
395
+ }
396
+
397
+ function escapeRegex (value) {
398
+ return value.replaceAll(/[\\^$.*+?()[\]{}|]/g, String.raw`\$&`)
399
+ }
400
+
401
+ function getVitestConfigFile (command, repositoryRoot) {
402
+ if (command.usesShell) {
403
+ const match = String(command.shellCommand || '').match(/(?:^|\s)--config(?:=|\s+)([^\s]+)/)
404
+ return match ? path.resolve(command.cwd, unquote(match[1])) : undefined
405
+ }
406
+
407
+ const argv = command.argv || []
408
+ for (let index = 0; index < argv.length; index++) {
409
+ if (argv[index] === '--config' && argv[index + 1]) return path.resolve(command.cwd, argv[index + 1])
410
+ if (argv[index].startsWith('--config=')) return path.resolve(command.cwd, argv[index].slice('--config='.length))
411
+ }
412
+
413
+ for (const filename of VITEST_CONFIG_FILENAMES) {
414
+ const configFile = path.resolve(command.cwd, filename)
415
+ if (isRepositoryFile(configFile, repositoryRoot)) return configFile
416
+ }
417
+ }
418
+
419
+ function isRepositoryFile (filename, repositoryRoot) {
420
+ return Boolean(getRepositoryFile(filename, repositoryRoot))
421
+ }
422
+
423
+ function getRepositoryFile (filename, repositoryRoot) {
424
+ try {
425
+ const entry = fs.lstatSync(filename)
426
+ if (!entry.isFile() && !entry.isSymbolicLink()) return
427
+
428
+ const physicalRoot = fs.realpathSync(repositoryRoot)
429
+ const physicalFilename = fs.realpathSync(filename)
430
+ const relative = path.relative(physicalRoot, physicalFilename)
431
+ if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) return
432
+
433
+ const stat = fs.statSync(physicalFilename)
434
+ if (!stat.isFile()) return
435
+ return { filename: physicalFilename, stat }
436
+ } catch {}
437
+ }
438
+
439
+ function configEnablesTypecheck (filename, repositoryRoot) {
440
+ const config = readRepositoryConfig(filename, repositoryRoot)
441
+ return config ? TYPECHECK_ENABLED_PATTERN.test(config) : false
442
+ }
443
+
444
+ function readRepositoryConfig (filename, repositoryRoot) {
445
+ const file = getRepositoryFile(filename, repositoryRoot)
446
+ if (!file || file.stat.size > MAX_CONFIG_BYTES) return
447
+
448
+ try {
449
+ return fs.readFileSync(file.filename, 'utf8')
450
+ } catch {}
451
+ }
452
+
453
+ /**
454
+ * Reads a bounded test-runner config file.
455
+ *
456
+ * @param {string} filename config path
457
+ * @returns {string|undefined} config text
458
+ */
459
+ function readConfig (filename) {
460
+ try {
461
+ const stat = fs.statSync(filename)
462
+ if (!stat.isFile() || stat.size > MAX_CONFIG_BYTES) return
463
+ return fs.readFileSync(filename, 'utf8')
464
+ } catch {}
465
+ }
466
+
467
+ function unquote (value) {
468
+ return value.replaceAll(/^['"]|['"]$/g, '')
469
+ }
470
+
471
+ module.exports = { getCommandSuitabilityError }
@@ -0,0 +1,48 @@
1
+ 'use strict'
2
+
3
+ const APPROVED_EXECUTABLE = Symbol('approvedValidationExecutable')
4
+
5
+ /**
6
+ * Binds a command to the executable identity covered by the approved plan.
7
+ *
8
+ * @param {object} command command to bind
9
+ * @param {object} identity approved executable identity
10
+ * @returns {void}
11
+ */
12
+ function bindApprovedExecutable (command, identity) {
13
+ Object.defineProperty(command, APPROVED_EXECUTABLE, {
14
+ configurable: true,
15
+ enumerable: false,
16
+ value: identity,
17
+ writable: false,
18
+ })
19
+ }
20
+
21
+ /**
22
+ * Returns the executable identity bound to a command.
23
+ *
24
+ * @param {object} command command to inspect
25
+ * @returns {object|undefined} approved executable identity
26
+ */
27
+ function getApprovedExecutable (command) {
28
+ return command?.[APPROVED_EXECUTABLE]
29
+ }
30
+
31
+ /**
32
+ * Carries an approved executable identity onto a derived command.
33
+ *
34
+ * @param {object} source source command
35
+ * @param {object} derived derived command
36
+ * @returns {object} derived command
37
+ */
38
+ function inheritApprovedExecutable (source, derived) {
39
+ const identity = getApprovedExecutable(source)
40
+ if (identity && source !== derived) bindApprovedExecutable(derived, identity)
41
+ return derived
42
+ }
43
+
44
+ module.exports = {
45
+ bindApprovedExecutable,
46
+ getApprovedExecutable,
47
+ inheritApprovedExecutable,
48
+ }