dd-trace 6.7.0 → 6.9.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 (272) hide show
  1. package/ci/diagnose.js +27 -25
  2. package/ci/init.js +6 -6
  3. package/ci/runbook.md +114 -20
  4. package/ci/test-optimization-validation/approval-artifacts.js +36 -3
  5. package/ci/test-optimization-validation/approval.js +78 -22
  6. package/ci/test-optimization-validation/blocker-category.js +24 -0
  7. package/ci/test-optimization-validation/ci-discovery.js +23 -17
  8. package/ci/test-optimization-validation/ci-package-scripts.js +34 -6
  9. package/ci/test-optimization-validation/ci-remediation.js +1 -1
  10. package/ci/test-optimization-validation/cli.js +280 -52
  11. package/ci/test-optimization-validation/command-blocker.js +159 -20
  12. package/ci/test-optimization-validation/command-output-policy.js +14 -8
  13. package/ci/test-optimization-validation/command-runner.js +9 -7
  14. package/ci/test-optimization-validation/environment.js +4 -2
  15. package/ci/test-optimization-validation/executable.js +15 -13
  16. package/ci/test-optimization-validation/execution-lock.js +70 -0
  17. package/ci/test-optimization-validation/framework-adapters/cucumber-validation.json +3 -0
  18. package/ci/test-optimization-validation/framework-adapters/cucumber.js +657 -5
  19. package/ci/test-optimization-validation/framework-adapters/playwright.js +5 -5
  20. package/ci/test-optimization-validation/framework-adapters/vitest.js +517 -0
  21. package/ci/test-optimization-validation/generated-files.js +47 -37
  22. package/ci/test-optimization-validation/generated-test-contract.js +9 -7
  23. package/ci/test-optimization-validation/generated-verifier.js +24 -17
  24. package/ci/test-optimization-validation/literal-glob.js +52 -0
  25. package/ci/test-optimization-validation/manifest-loader.js +3 -0
  26. package/ci/test-optimization-validation/manifest-scaffold.js +301 -52
  27. package/ci/test-optimization-validation/manifest-schema.js +80 -17
  28. package/ci/test-optimization-validation/offline-fixtures.js +13 -4
  29. package/ci/test-optimization-validation/offline-output.js +6 -6
  30. package/ci/test-optimization-validation/package-check.js +94 -0
  31. package/ci/test-optimization-validation/plan-writer.js +183 -10
  32. package/ci/test-optimization-validation/preflight-runner.js +110 -31
  33. package/ci/test-optimization-validation/project-build-artifact.js +31 -0
  34. package/ci/test-optimization-validation/redaction.js +18 -27
  35. package/ci/test-optimization-validation/report-writer.js +182 -48
  36. package/ci/test-optimization-validation/result-semantics.js +20 -3
  37. package/ci/test-optimization-validation/runner-command.js +41 -21
  38. package/ci/test-optimization-validation/runner-contract.js +79 -43
  39. package/ci/test-optimization-validation/safe-files.js +4 -4
  40. package/ci/test-optimization-validation/scenarios/basic-reporting.js +60 -6
  41. package/ci/test-optimization-validation/scenarios/ci-wiring.js +141 -35
  42. package/ci/test-optimization-validation/scenarios/helpers.js +30 -6
  43. package/ci/test-optimization-validation/scenarios/test-management.js +2 -2
  44. package/ci/test-optimization-validation/static-diagnosis.js +10 -3
  45. package/ci/test-optimization-validation/test-output.js +2 -1
  46. package/ci/test-optimization-validation/validation-blocker.js +21 -0
  47. package/ci/vitest-no-worker-init-setup.mjs +377 -84
  48. package/index.d.ts +175 -14
  49. package/init.js +1 -17
  50. package/initialize.mjs +11 -0
  51. package/loader-hook.mjs +6 -4
  52. package/package.json +12 -4
  53. package/packages/datadog-core/src/utils/src/kebabcase.js +8 -2
  54. package/packages/datadog-core/src/utils/src/parse-tags.js +0 -1
  55. package/packages/datadog-esbuild/index.js +4 -2
  56. package/packages/datadog-esbuild/src/utils.js +17 -2
  57. package/packages/datadog-instrumentations/src/ai.js +61 -34
  58. package/packages/datadog-instrumentations/src/bluebird.js +6 -6
  59. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +85 -9
  60. package/packages/datadog-instrumentations/src/cucumber.js +131 -51
  61. package/packages/datadog-instrumentations/src/dns.js +21 -2
  62. package/packages/datadog-instrumentations/src/electron.js +1 -0
  63. package/packages/datadog-instrumentations/src/fastify.js +3 -1
  64. package/packages/datadog-instrumentations/src/grpc/client.js +4 -2
  65. package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +3 -1
  66. package/packages/datadog-instrumentations/src/helpers/extract-package-and-module-path.js +10 -0
  67. package/packages/datadog-instrumentations/src/helpers/hook.js +19 -2
  68. package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
  69. package/packages/datadog-instrumentations/src/helpers/register.js +9 -3
  70. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +34 -9
  71. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  72. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +175 -0
  73. package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +20 -2
  74. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.js +32 -0
  75. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +61 -0
  76. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +50 -18
  77. package/packages/datadog-instrumentations/src/helpers/router-helper.js +38 -7
  78. package/packages/datadog-instrumentations/src/helpers/shared-utils.js +39 -0
  79. package/packages/datadog-instrumentations/src/http/client.js +1 -3
  80. package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +1 -1
  81. package/packages/datadog-instrumentations/src/jest.js +986 -246
  82. package/packages/datadog-instrumentations/src/mocha/common.js +1 -3
  83. package/packages/datadog-instrumentations/src/mocha/main.js +109 -51
  84. package/packages/datadog-instrumentations/src/mocha/utils.js +120 -32
  85. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +44 -0
  86. package/packages/datadog-instrumentations/src/mocha/worker.js +441 -43
  87. package/packages/datadog-instrumentations/src/moleculer/client.js +1 -1
  88. package/packages/datadog-instrumentations/src/mysql.js +1 -1
  89. package/packages/datadog-instrumentations/src/next.js +155 -23
  90. package/packages/datadog-instrumentations/src/openai.js +14 -0
  91. package/packages/datadog-instrumentations/src/pg.js +180 -6
  92. package/packages/datadog-instrumentations/src/playwright.js +176 -81
  93. package/packages/datadog-instrumentations/src/promise-js.js +4 -4
  94. package/packages/datadog-instrumentations/src/promise.js +3 -3
  95. package/packages/datadog-instrumentations/src/rhea.js +1 -1
  96. package/packages/datadog-instrumentations/src/router.js +195 -19
  97. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +567 -65
  98. package/packages/datadog-instrumentations/src/vitest-main.js +438 -54
  99. package/packages/datadog-instrumentations/src/vitest-util.js +42 -7
  100. package/packages/datadog-instrumentations/src/vitest-worker.js +234 -17
  101. package/packages/datadog-instrumentations/src/webdriverio.js +1090 -0
  102. package/packages/datadog-instrumentations/src/when.js +3 -3
  103. package/packages/datadog-instrumentations/src/ws.js +5 -1
  104. package/packages/datadog-plugin-aerospike/src/index.js +1 -3
  105. package/packages/datadog-plugin-amqplib/src/client.js +1 -1
  106. package/packages/datadog-plugin-amqplib/src/producer.js +1 -1
  107. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +59 -48
  108. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +2 -2
  109. package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +2 -2
  110. package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -3
  111. package/packages/datadog-plugin-azure-service-bus/src/producer.js +5 -3
  112. package/packages/datadog-plugin-bullmq/src/producer.js +3 -3
  113. package/packages/datadog-plugin-child_process/src/index.js +3 -3
  114. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +6 -11
  115. package/packages/datadog-plugin-cucumber/src/index.js +5 -1
  116. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +50 -46
  117. package/packages/datadog-plugin-cypress/src/source-map-utils.js +1 -1
  118. package/packages/datadog-plugin-cypress/src/support.js +7 -4
  119. package/packages/datadog-plugin-electron/src/net.js +10 -4
  120. package/packages/datadog-plugin-fastify/src/tracing.js +1 -1
  121. package/packages/datadog-plugin-fs/src/index.js +0 -4
  122. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +14 -7
  123. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +19 -10
  124. package/packages/datadog-plugin-graphql/src/execute.js +1 -0
  125. package/packages/datadog-plugin-graphql/src/tools/signature.js +1 -1
  126. package/packages/datadog-plugin-http/src/client.js +3 -2
  127. package/packages/datadog-plugin-http2/src/client.js +1 -1
  128. package/packages/datadog-plugin-jest/src/index.js +3 -21
  129. package/packages/datadog-plugin-jest/src/util.js +4 -1
  130. package/packages/datadog-plugin-kafkajs/src/producer.js +2 -1
  131. package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
  132. package/packages/datadog-plugin-mocha/src/index.js +342 -6
  133. package/packages/datadog-plugin-next/src/index.js +57 -9
  134. package/packages/datadog-plugin-openai/src/tracing.js +4 -0
  135. package/packages/datadog-plugin-pg/src/index.js +65 -1
  136. package/packages/datadog-plugin-playwright/src/index.js +1 -1
  137. package/packages/datadog-plugin-prisma/src/index.js +5 -1
  138. package/packages/datadog-plugin-rhea/src/consumer.js +16 -13
  139. package/packages/datadog-plugin-rhea/src/producer.js +1 -1
  140. package/packages/datadog-plugin-undici/src/index.js +2 -1
  141. package/packages/datadog-plugin-vitest/src/index.js +179 -18
  142. package/packages/datadog-shimmer/src/shimmer.js +6 -2
  143. package/packages/datadog-webpack/index.js +2 -2
  144. package/packages/dd-trace/src/aiguard/sdk.js +3 -0
  145. package/packages/dd-trace/src/appsec/{blocking.js → blocking/index.js} +26 -6
  146. package/packages/dd-trace/src/appsec/downstream_requests.js +1 -1
  147. package/packages/dd-trace/src/appsec/handlers/auth.js +62 -0
  148. package/packages/dd-trace/src/appsec/handlers/http-request.js +215 -0
  149. package/packages/dd-trace/src/appsec/handlers/http-response.js +78 -0
  150. package/packages/dd-trace/src/appsec/handlers/http-shared.js +24 -0
  151. package/packages/dd-trace/src/appsec/handlers/payments.js +104 -0
  152. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-base-analyzer.js +1 -1
  153. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-analyzer.js +1 -1
  154. package/packages/dd-trace/src/appsec/iast/analyzers/hsts-header-missing-analyzer.js +1 -1
  155. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +1 -1
  156. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +1 -3
  157. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +2 -2
  158. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +2 -2
  159. package/packages/dd-trace/src/appsec/index.js +48 -476
  160. package/packages/dd-trace/src/appsec/sdk/track_event.js +10 -1
  161. package/packages/dd-trace/src/appsec/waf/waf_manager.js +1 -1
  162. package/packages/dd-trace/src/ci-visibility/coverage-report-discovery.js +46 -14
  163. package/packages/dd-trace/src/ci-visibility/efd-retry-policy.js +89 -0
  164. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +7 -6
  165. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +29 -15
  166. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +19 -1
  167. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +25 -1
  168. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/webdriverio.js +26 -0
  169. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -1
  170. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +22 -7
  171. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +9 -14
  172. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -3
  173. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +21 -5
  174. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +2 -2
  175. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +5 -1
  176. package/packages/dd-trace/src/config/defaults.js +14 -6
  177. package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -2
  178. package/packages/dd-trace/src/config/helper.js +2 -0
  179. package/packages/dd-trace/src/config/index.js +31 -5
  180. package/packages/dd-trace/src/config/parsers.js +19 -1
  181. package/packages/dd-trace/src/config/remote_config.js +1 -1
  182. package/packages/dd-trace/src/config/supported-configurations.json +13 -6
  183. package/packages/dd-trace/src/datastreams/encoding.js +3 -4
  184. package/packages/dd-trace/src/debugger/devtools_client/log.js +2 -1
  185. package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +1 -4
  186. package/packages/dd-trace/src/debugger/devtools_client/send.js +1 -2
  187. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +2 -2
  188. package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +16 -2
  189. package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -1
  190. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +0 -1
  191. package/packages/dd-trace/src/encode/tags-processors.js +2 -2
  192. package/packages/dd-trace/src/exporters/common/url.js +3 -5
  193. package/packages/dd-trace/src/exporters/electron/index.js +4 -1
  194. package/packages/dd-trace/src/external-logger/src/index.js +1 -2
  195. package/packages/dd-trace/src/flare/index.js +2 -2
  196. package/packages/dd-trace/src/guardrails/apply-pm2-env.js +16 -0
  197. package/packages/dd-trace/src/guardrails/log.js +1 -10
  198. package/packages/dd-trace/src/id.js +1 -1
  199. package/packages/dd-trace/src/llmobs/constants/tags.js +1 -1
  200. package/packages/dd-trace/src/llmobs/eval-metric.js +249 -0
  201. package/packages/dd-trace/src/llmobs/experiments/client.js +4 -2
  202. package/packages/dd-trace/src/llmobs/experiments/dataset.js +66 -14
  203. package/packages/dd-trace/src/llmobs/experiments/experiment.js +289 -95
  204. package/packages/dd-trace/src/llmobs/experiments/index.js +66 -19
  205. package/packages/dd-trace/src/llmobs/experiments/noop.js +95 -10
  206. package/packages/dd-trace/src/llmobs/experiments/result.js +13 -2
  207. package/packages/dd-trace/src/llmobs/experiments/util.js +220 -0
  208. package/packages/dd-trace/src/llmobs/noop.js +2 -0
  209. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +1 -1
  210. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -1
  211. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +1 -1
  212. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +5 -5
  213. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +1 -1
  214. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
  215. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
  216. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +1 -1
  217. package/packages/dd-trace/src/llmobs/sdk.js +152 -78
  218. package/packages/dd-trace/src/llmobs/span_processor.js +9 -7
  219. package/packages/dd-trace/src/llmobs/tagger.js +1 -2
  220. package/packages/dd-trace/src/llmobs/telemetry.js +18 -0
  221. package/packages/dd-trace/src/llmobs/writers/base.js +3 -0
  222. package/packages/dd-trace/src/llmobs/writers/spans.js +12 -6
  223. package/packages/dd-trace/src/llmobs/writers/util.js +2 -0
  224. package/packages/dd-trace/src/log/channels.js +1 -9
  225. package/packages/dd-trace/src/log/levels.js +12 -0
  226. package/packages/dd-trace/src/noop/proxy.js +2 -2
  227. package/packages/dd-trace/src/openfeature/writers/exposures.js +1 -1
  228. package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
  229. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +1 -1
  230. package/packages/dd-trace/src/opentelemetry/metrics/meter.js +8 -4
  231. package/packages/dd-trace/src/opentelemetry/metrics/otlp_transformer.js +1 -1
  232. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +3 -2
  233. package/packages/dd-trace/src/opentelemetry/span.js +4 -0
  234. package/packages/dd-trace/src/opentracing/span.js +1 -1
  235. package/packages/dd-trace/src/opentracing/span_context.js +1 -1
  236. package/packages/dd-trace/src/plugins/ci_plugin.js +124 -23
  237. package/packages/dd-trace/src/plugins/index.js +2 -0
  238. package/packages/dd-trace/src/plugins/plugin.js +1 -1
  239. package/packages/dd-trace/src/plugins/tracing.js +1 -0
  240. package/packages/dd-trace/src/plugins/util/ci.js +13 -6
  241. package/packages/dd-trace/src/plugins/util/git.js +7 -5
  242. package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +3 -3
  243. package/packages/dd-trace/src/plugins/util/ip_extractor.js +1 -1
  244. package/packages/dd-trace/src/plugins/util/llm.js +2 -2
  245. package/packages/dd-trace/src/plugins/util/stacktrace.js +3 -0
  246. package/packages/dd-trace/src/plugins/util/tags.js +2 -0
  247. package/packages/dd-trace/src/plugins/util/test.js +356 -86
  248. package/packages/dd-trace/src/plugins/util/url.js +5 -5
  249. package/packages/dd-trace/src/plugins/util/user-provided-git.js +1 -1
  250. package/packages/dd-trace/src/plugins/util/web.js +24 -2
  251. package/packages/dd-trace/src/priority_sampler.js +6 -3
  252. package/packages/dd-trace/src/profiler.js +1 -1
  253. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +1 -1
  254. package/packages/dd-trace/src/profiling/profilers/events.js +2 -2
  255. package/packages/dd-trace/src/profiling/profilers/poisson.js +1 -1
  256. package/packages/dd-trace/src/profiling/profilers/shared.js +2 -2
  257. package/packages/dd-trace/src/proxy.js +1 -0
  258. package/packages/dd-trace/src/ritm.js +10 -20
  259. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +4 -2
  260. package/packages/dd-trace/src/sampling_rule.js +4 -2
  261. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +1 -1
  262. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +1 -1
  263. package/packages/dd-trace/src/startup-log.js +3 -0
  264. package/packages/dd-trace/src/telemetry/logs/index.js +3 -0
  265. package/packages/dd-trace/src/telemetry/logs/log-collector.js +3 -1
  266. package/packages/dd-trace/src/telemetry/telemetry.js +1 -1
  267. package/packages/dd-trace/src/util.js +16 -0
  268. package/vendor/dist/@apm-js-collab/code-transformer/index.js +12 -12
  269. package/vendor/dist/pprof-format/index.js +1 -1
  270. package/vendor/dist/source-map/index.js +1 -1
  271. package/version.js +10 -8
  272. /package/packages/dd-trace/src/appsec/{blocked_templates.js → blocking/templates.js} +0 -0
@@ -3,7 +3,7 @@
3
3
  const path = require('node:path')
4
4
 
5
5
  const CONFIG_PATTERN = /^playwright\.config\.[cm]?[jt]s$/
6
- const TEST_FILE_PATTERN = /(?:\.(?:spec|test)\.[cm]?[jt]sx?)$/
6
+ const TEST_FILE_PATTERN = /\.(?:spec|test)\.[cm]?[jt]sx?$/
7
7
  const GENERATED_CONFIG_FILENAME = 'dd-test-optimization-validation.playwright.config.cjs'
8
8
  const VALIDATION_OUTPUT_DIRECTORY = '.dd-test-optimization-validation-playwright-output'
9
9
  const PLAYWRIGHT_PACKAGE = '@playwright/test'
@@ -139,12 +139,12 @@ function getOutputPath (filename) {
139
139
  */
140
140
  function getObservedTestCount (output) {
141
141
  const observed = sumLastMatchCounts(output, [
142
- /^\s*(\d+)\s+passed\b/gim,
143
- /^\s*(\d+)\s+failed\b/gim,
144
- /^\s*(\d+)\s+flaky\b/gim,
142
+ /^[ \t]*(\d+)[ \t]+passed\b/gim,
143
+ /^[ \t]*(\d+)[ \t]+failed\b/gim,
144
+ /^[ \t]*(\d+)[ \t]+flaky\b/gim,
145
145
  ])
146
146
  if (observed !== null) return observed
147
- return /^\s*\d+\s+skipped\b/im.test(output) ? 0 : null
147
+ return /^[ \t]*\d+[ \t]+skipped\b/im.test(output) ? 0 : null
148
148
  }
149
149
 
150
150
  /**
@@ -0,0 +1,517 @@
1
+ 'use strict'
2
+
3
+ const fs = require('node:fs')
4
+ const path = require('node:path')
5
+
6
+ const { matchesLiteralGlob } = require('../literal-glob')
7
+ const { maskJavaScriptComments, maskJavaScriptNonCode } = require('../source-text')
8
+
9
+ const CONFIG_PATTERN = /^(?:vite\.config|vitest\.(?:config|workspace))\.[cm]?[jt]s$/
10
+ const WORKSPACE_CONFIG_PATTERN = /^vitest\.workspace\.[cm]?[jt]s$/
11
+ const DIRECT_EXPORT_PATTERN = /(?:export\s+default|module\s*\.\s*exports\s*=)\s*$/
12
+ const CONFIG_CALL_PATTERN =
13
+ /(?:export\s+default|module\s*\.\s*exports\s*=)\s*defineConfig\s*\(\s*$/
14
+ const WORKSPACE_ARRAY_CALL_PATTERN =
15
+ /(?:export\s+default|module\s*\.\s*exports\s*=)\s*defineWorkspace\s*\(\s*$/
16
+ const PROJECT_CALL_PATTERN =
17
+ /(?:export\s+default|module\s*\.\s*exports\s*=)\s*defineProject\s*\(\s*$/
18
+ const LITERAL_PROJECT_PATTERN = /^[A-Za-z0-9_.:@/-]+$/
19
+
20
+ /**
21
+ * Binds one literal `--project` selector to one statically named Vitest project.
22
+ *
23
+ * Customer configuration is read as text only. Dynamic names, roots, includes, and ambiguous matches fail closed.
24
+ *
25
+ * @param {object} input project inputs
26
+ * @param {string[]} input.configFiles approval-bound configuration files
27
+ * @param {string[]} input.projectFiles bounded project files
28
+ * @param {string} input.projectRoot detected project root
29
+ * @param {string[]} input.runnerArgs retained Vitest arguments
30
+ * @returns {{
31
+ * configFile?: string,
32
+ * error?: string,
33
+ * excludePatterns?: string[],
34
+ * files?: string[],
35
+ * includePatterns?: string[],
36
+ * root?: string
37
+ * }|undefined} project binding
38
+ */
39
+ function bindLiteralProject ({ configFiles, projectFiles, projectRoot, runnerArgs }) {
40
+ const projects = getOptionValues(runnerArgs, '--project')
41
+ if (projects.length === 0) return
42
+ if (projects.length !== 1 || !LITERAL_PROJECT_PATTERN.test(projects[0])) {
43
+ return { error: '--project must select exactly one literal project name' }
44
+ }
45
+
46
+ const name = projects[0]
47
+ const roots = getOptionValues(runnerArgs, '--root')
48
+ if (roots.length > 1) {
49
+ return { error: '--project cannot be bound with more than one Vitest root' }
50
+ }
51
+ const effectiveRoot = roots.length === 1
52
+ ? getPhysicalDirectory(path.resolve(projectRoot, roots[0]))
53
+ : getPhysicalDirectory(projectRoot)
54
+ if (!effectiveRoot || !isContainedDirectory(projectRoot, effectiveRoot)) {
55
+ return { error: 'the selected Vitest root is not a project-contained directory' }
56
+ }
57
+
58
+ const selectedConfigs = getSelectedConfigFiles({ configFiles, effectiveRoot, projectRoot, runnerArgs })
59
+ if (selectedConfigs.error) return selectedConfigs
60
+ const bindings = []
61
+ for (const configFile of selectedConfigs.files) {
62
+ const source = readText(configFile)
63
+ if (source === undefined) continue
64
+ for (const property of getLiteralStringProperties(source, 'name')) {
65
+ if (property.value !== name) continue
66
+ const project = getProjectObject(
67
+ source,
68
+ property.index,
69
+ WORKSPACE_CONFIG_PATTERN.test(path.basename(configFile))
70
+ )
71
+ if (!project) continue
72
+ const projectName = getLiteralProperty(project.source, 'name')
73
+ if (projectName.dynamic || projectName.value !== name) continue
74
+ const binding = getBindingFromObject({
75
+ bindingRoot: effectiveRoot,
76
+ configFile,
77
+ projectFiles,
78
+ projectEntry: project.entrySource,
79
+ projectObject: project.source,
80
+ projectRoot,
81
+ standaloneProject: project.standalone,
82
+ })
83
+ if (binding.error) return binding
84
+ bindings.push(binding)
85
+ }
86
+ }
87
+
88
+ if (bindings.length !== 1) {
89
+ return {
90
+ error: bindings.length === 0
91
+ ? `--project ${JSON.stringify(name)} does not map to one statically named Vitest project`
92
+ : `--project ${JSON.stringify(name)} maps to more than one Vitest project`,
93
+ }
94
+ }
95
+ return bindings[0]
96
+ }
97
+
98
+ function getSelectedConfigFiles ({ configFiles, effectiveRoot, projectRoot, runnerArgs }) {
99
+ const explicitConfigs = getOptionValues(runnerArgs, '--config')
100
+ if (explicitConfigs.length > 1) {
101
+ return { error: '--project cannot be bound with more than one explicit Vitest config' }
102
+ }
103
+ if (explicitConfigs.length === 1) {
104
+ const explicitConfig = getPhysicalPath(path.resolve(projectRoot, explicitConfigs[0]))
105
+ const selected = configFiles.find(filename => filename === explicitConfig)
106
+ return selected
107
+ ? { files: [selected] }
108
+ : { error: 'the explicit Vitest config is not an approval-bound regular file' }
109
+ }
110
+
111
+ return {
112
+ files: configFiles.filter(filename => {
113
+ return path.dirname(filename) === effectiveRoot && CONFIG_PATTERN.test(path.basename(filename))
114
+ }),
115
+ }
116
+ }
117
+
118
+ function getBindingFromObject ({
119
+ bindingRoot,
120
+ configFile,
121
+ projectFiles,
122
+ projectEntry,
123
+ projectObject,
124
+ projectRoot,
125
+ standaloneProject,
126
+ }) {
127
+ if (/\.\.\./.test(maskJavaScriptNonCode(projectEntry))) {
128
+ return { error: 'the selected Vitest project uses dynamic spread composition' }
129
+ }
130
+
131
+ const rootProperty = getLiteralProperty(projectObject, 'root')
132
+ if (rootProperty.dynamic) return { error: 'the selected Vitest project has a dynamic root' }
133
+
134
+ const rootBase = standaloneProject ? path.dirname(configFile) : bindingRoot
135
+ const root = rootProperty.value
136
+ ? path.resolve(rootBase, rootProperty.value)
137
+ : standaloneProject
138
+ ? path.dirname(configFile)
139
+ : bindingRoot
140
+ if (!isContainedDirectory(projectRoot, root)) {
141
+ return { error: 'the selected Vitest project root is not a repository-contained directory' }
142
+ }
143
+
144
+ const include = getLiteralStringArray(projectObject, 'include')
145
+ if (include.dynamic) return { error: 'the selected Vitest project has dynamic include patterns' }
146
+ const exclude = getLiteralStringArray(projectObject, 'exclude')
147
+ if (exclude.dynamic) return { error: 'the selected Vitest project has dynamic exclude patterns' }
148
+ const project = {
149
+ excludePatterns: exclude.values,
150
+ includePatterns: include.values,
151
+ root,
152
+ }
153
+ const files = projectFiles.filter(filename => matchesProjectFile(project, filename))
154
+ return { configFile, files, ...project }
155
+ }
156
+
157
+ function supportsGeneratedFiles (project, strategy) {
158
+ return strategy.scenarios.every(scenario => {
159
+ return matchesProjectFile(project, scenario.testIdentities[0].file)
160
+ })
161
+ }
162
+
163
+ function matchesProjectFile (project, filename) {
164
+ const relative = path.relative(project.root, filename)
165
+ if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) return false
166
+ const normalized = relative.replaceAll('\\', '/')
167
+ const included = project.includePatterns.length === 0 ||
168
+ project.includePatterns.some(pattern => matchesLiteralGlob(normalized, pattern))
169
+ return included && !project.excludePatterns.some(pattern => matchesLiteralGlob(normalized, pattern))
170
+ }
171
+
172
+ /**
173
+ * Returns the literal project object that owns a matching name property.
174
+ *
175
+ * @param {string} source Vitest configuration source
176
+ * @param {number} nameIndex matching name property offset
177
+ * @param {boolean} workspaceConfig whether the source is a Vitest workspace config
178
+ * @returns {{entrySource: string, source: string, standalone: boolean}|undefined} bounded project object
179
+ */
180
+ function getProjectObject (source, nameIndex, workspaceConfig) {
181
+ const objectRanges = getObjectRanges(source)
182
+ const ranges = objectRanges
183
+ .filter(range => range.start < nameIndex && range.end > nameIndex)
184
+ .sort((left, right) => (left.end - left.start) - (right.end - right.start))
185
+ if (ranges.length === 0) return
186
+
187
+ // Vitest accepts both `{ name }` and `{ test: { name } }` project entries.
188
+ const inner = ranges[0]
189
+ const parent = ranges[1]
190
+ const parentPrefix = parent && maskJavaScriptComments(source.slice(parent.start + 1, inner.start))
191
+ const nestedTestObject = /(?:^|,)\s*(?:test|(["'])test\1)\s*:\s*$/.test(parentPrefix || '')
192
+ if (nestedTestObject &&
193
+ getDirectPropertyPositions(source.slice(parent.start + 1, parent.end), 'test').length !== 1) return
194
+ const selected = nestedTestObject ? parent : inner
195
+ const standalone = isStandaloneProjectObject(source, selected)
196
+ if (!standalone &&
197
+ !isTestProjectsEntry(source, selected, objectRanges) &&
198
+ !(workspaceConfig && isWorkspaceProjectEntry(source, selected))) return
199
+ return {
200
+ entrySource: source.slice(selected.start + 1, selected.end),
201
+ source: source.slice(inner.start + 1, inner.end),
202
+ standalone,
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Reports whether an object is the direct literal argument to defineProject.
208
+ *
209
+ * @param {string} source Vitest configuration source
210
+ * @param {{end: number, start: number}} range candidate object range
211
+ * @returns {boolean} whether the object is a standalone project
212
+ */
213
+ function isStandaloneProjectObject (source, range) {
214
+ const before = maskJavaScriptNonCode(source.slice(0, range.start))
215
+ const after = maskJavaScriptNonCode(source.slice(range.end + 1))
216
+ return PROJECT_CALL_PATTERN.test(before) && /^\s*,?\s*\)[\s;]*$/.test(after)
217
+ }
218
+
219
+ /**
220
+ * Reports whether an object is a direct entry in a literal test.projects array.
221
+ *
222
+ * @param {string} source Vitest configuration source
223
+ * @param {{end: number, start: number}} range candidate object range
224
+ * @param {{end: number, start: number}[]} objectRanges source object ranges
225
+ * @returns {boolean} whether the object is a bounded project entry
226
+ */
227
+ function isTestProjectsEntry (source, range, objectRanges) {
228
+ const projectsArray = getDirectContainingArray(source, range)
229
+ if (!projectsArray) return false
230
+
231
+ const containers = objectRanges
232
+ .filter(object => object.start < projectsArray.start && object.end > projectsArray.end)
233
+ .sort((left, right) => (left.end - left.start) - (right.end - right.start))
234
+ const testObject = containers[0]
235
+ const configObject = containers[1]
236
+ if (!testObject || !configObject) return false
237
+
238
+ const projectsPrefix = maskJavaScriptComments(source.slice(testObject.start + 1, projectsArray.start))
239
+ if (!/(?:^|,)\s*(?:projects|"projects"|'projects')\s*:\s*$/.test(projectsPrefix) ||
240
+ getDirectPropertyPositions(
241
+ source.slice(testObject.start + 1, testObject.end),
242
+ 'projects'
243
+ ).length !== 1) return false
244
+
245
+ const testPrefix = maskJavaScriptComments(source.slice(configObject.start + 1, testObject.start))
246
+ return /(?:^|,)\s*(?:test|"test"|'test')\s*:\s*$/.test(testPrefix) &&
247
+ getDirectPropertyPositions(source.slice(configObject.start + 1, configObject.end), 'test').length === 1 &&
248
+ isExportedConfigObject(source, configObject)
249
+ }
250
+
251
+ /**
252
+ * Reports whether an object is the direct exported config or defineConfig argument.
253
+ *
254
+ * @param {string} source Vitest configuration source
255
+ * @param {{end: number, start: number}} range candidate config object range
256
+ * @returns {boolean} whether the object belongs to the exported configuration
257
+ */
258
+ function isExportedConfigObject (source, range) {
259
+ const before = maskJavaScriptNonCode(source.slice(0, range.start))
260
+ const after = maskJavaScriptNonCode(source.slice(range.end + 1))
261
+ return (DIRECT_EXPORT_PATTERN.test(before) && /^[\s;]*$/.test(after)) ||
262
+ (CONFIG_CALL_PATTERN.test(before) && /^\s*,?\s*\)[\s;]*$/.test(after))
263
+ }
264
+
265
+ /**
266
+ * Reports whether an object is a direct entry in the exported array of a Vitest workspace config.
267
+ *
268
+ * @param {string} source Vitest workspace configuration source
269
+ * @param {{end: number, start: number}} range candidate object range
270
+ * @returns {boolean} whether the object is a bounded workspace project entry
271
+ */
272
+ function isWorkspaceProjectEntry (source, range) {
273
+ const workspaceArray = getDirectContainingArray(source, range)
274
+ if (!workspaceArray) return false
275
+
276
+ const before = maskJavaScriptNonCode(source.slice(0, workspaceArray.start))
277
+ const after = maskJavaScriptNonCode(source.slice(workspaceArray.end + 1))
278
+ return (DIRECT_EXPORT_PATTERN.test(before) && /^[\s;]*$/.test(after)) ||
279
+ (WORKSPACE_ARRAY_CALL_PATTERN.test(before) && /^\s*,?\s*\)[\s;]*$/.test(after))
280
+ }
281
+
282
+ /**
283
+ * Returns the smallest array containing an object as a direct entry.
284
+ *
285
+ * @param {string} source JavaScript-like source
286
+ * @param {{end: number, start: number}} range candidate object range
287
+ * @returns {{end: number, start: number}|undefined} containing direct array
288
+ */
289
+ function getDirectContainingArray (source, range) {
290
+ const arrays = getDelimitedRanges(source, '[', ']')
291
+ .filter(array => array.start < range.start && array.end > range.end)
292
+ .sort((left, right) => (left.end - left.start) - (right.end - right.start))
293
+ const directArray = arrays[0]
294
+ if (!directArray) return
295
+
296
+ const entryPrefix = maskJavaScriptComments(source.slice(directArray.start, range.start)).trimEnd()
297
+ const entrySuffix = maskJavaScriptComments(source.slice(range.end + 1, directArray.end + 1)).trimStart()
298
+ if (['[', ','].includes(entryPrefix.at(-1)) && [']', ','].includes(entrySuffix[0])) return directArray
299
+ if (/(?:^|\[|,)\s*defineProject\s*\(\s*$/.test(entryPrefix) && /^,?\s*\)\s*[\],]/.test(entrySuffix)) {
300
+ return directArray
301
+ }
302
+ }
303
+
304
+ /**
305
+ * Finds balanced object ranges while ignoring comments and strings.
306
+ *
307
+ * @param {string} source JavaScript-like source
308
+ * @returns {{end: number, start: number}[]} object ranges
309
+ */
310
+ function getObjectRanges (source) {
311
+ return getDelimitedRanges(source, '{', '}')
312
+ }
313
+
314
+ /**
315
+ * Finds balanced delimiter ranges while ignoring comments and strings.
316
+ *
317
+ * @param {string} source JavaScript-like source
318
+ * @param {string} open opening delimiter
319
+ * @param {string} close closing delimiter
320
+ * @returns {{end: number, start: number}[]} delimiter ranges
321
+ */
322
+ function getDelimitedRanges (source, open, close) {
323
+ const ranges = []
324
+ const stack = []
325
+ let quote
326
+ let lineComment = false
327
+ let blockComment = false
328
+ for (let index = 0; index < source.length; index++) {
329
+ const character = source[index]
330
+ const next = source[index + 1]
331
+ if (lineComment) {
332
+ if (character === '\n') lineComment = false
333
+ continue
334
+ }
335
+ if (blockComment) {
336
+ if (character === '*' && next === '/') {
337
+ blockComment = false
338
+ index++
339
+ }
340
+ continue
341
+ }
342
+ if (quote) {
343
+ if (character === '\\') index++
344
+ else if (character === quote) quote = undefined
345
+ continue
346
+ }
347
+ if (character === '/' && next === '/') {
348
+ lineComment = true
349
+ index++
350
+ } else if (character === '/' && next === '*') {
351
+ blockComment = true
352
+ index++
353
+ } else if (character === '"' || character === "'" || character === '`') {
354
+ quote = character
355
+ } else if (character === open) {
356
+ stack.push(index)
357
+ } else if (character === close) {
358
+ const start = stack.pop()
359
+ if (start !== undefined) ranges.push({ end: index, start })
360
+ }
361
+ }
362
+ return ranges
363
+ }
364
+
365
+ function getLiteralStringProperties (source, property) {
366
+ return getPropertyPositions(source, property).flatMap(({ index, valueStart }) => {
367
+ const literal = /^(["'])([^"'\\]+)\1/.exec(source.slice(valueStart))
368
+ return literal ? [{ index, value: literal[2] }] : []
369
+ })
370
+ }
371
+
372
+ function getPropertyPositions (source, property) {
373
+ const properties = []
374
+ let quote
375
+ let lineComment = false
376
+ let blockComment = false
377
+ let previousCodeCharacter = '{'
378
+ for (let index = 0; index < source.length; index++) {
379
+ const character = source[index]
380
+ const next = source[index + 1]
381
+ if (lineComment) {
382
+ if (character === '\n') lineComment = false
383
+ continue
384
+ }
385
+ if (blockComment) {
386
+ if (character === '*' && next === '/') {
387
+ blockComment = false
388
+ index++
389
+ }
390
+ continue
391
+ }
392
+ if (quote) {
393
+ if (character === '\\') index++
394
+ else if (character === quote) quote = undefined
395
+ continue
396
+ }
397
+ if (character === '/' && next === '/') {
398
+ lineComment = true
399
+ index++
400
+ continue
401
+ }
402
+ if (character === '/' && next === '*') {
403
+ blockComment = true
404
+ index++
405
+ continue
406
+ }
407
+ if (character === '"' || character === "'" || character === '`') {
408
+ const match = new RegExp(String.raw`^(["'])${property}\1\s*:\s*`).exec(source.slice(index))
409
+ if (match && ['{', ','].includes(previousCodeCharacter)) {
410
+ properties.push({ index, valueStart: index + match[0].length })
411
+ previousCodeCharacter = ':'
412
+ index += match[0].length - 1
413
+ continue
414
+ }
415
+ quote = character
416
+ continue
417
+ }
418
+ if (!source.startsWith(property, index) ||
419
+ !['{', ','].includes(previousCodeCharacter) ||
420
+ /[A-Za-z0-9_$]/.test(source[index - 1] || '') ||
421
+ /[A-Za-z0-9_$]/.test(source[index + property.length] || '')) {
422
+ if (!/\s/.test(character)) previousCodeCharacter = character
423
+ continue
424
+ }
425
+
426
+ const match = new RegExp(String.raw`^${property}\s*:\s*`).exec(source.slice(index))
427
+ if (!match) {
428
+ previousCodeCharacter = character
429
+ continue
430
+ }
431
+ properties.push({ index, valueStart: index + match[0].length })
432
+ previousCodeCharacter = ':'
433
+ index += match[0].length - 1
434
+ }
435
+ return properties
436
+ }
437
+
438
+ function getLiteralProperty (source, property) {
439
+ const properties = getDirectPropertyPositions(source, property)
440
+ if (properties.length === 0) return {}
441
+ if (properties.length !== 1) return { dynamic: true }
442
+ const literal = /^(["'])([^"'\\]+)\1/.exec(source.slice(properties[0].valueStart))
443
+ if (literal) return { value: literal[2] }
444
+ return { dynamic: true }
445
+ }
446
+
447
+ function getLiteralStringArray (source, property) {
448
+ const properties = getDirectPropertyPositions(source, property)
449
+ if (properties.length === 0) return { values: [] }
450
+ if (properties.length !== 1) return { dynamic: true, values: [] }
451
+ const arrayMatch = /^\[([\s\S]{0,8192}?)\]/.exec(source.slice(properties[0].valueStart))
452
+ if (!arrayMatch) return { dynamic: true, values: [] }
453
+ const syntax = maskJavaScriptComments(arrayMatch[1])
454
+ const values = [...syntax.matchAll(/(["'])([^"'\\]+)\1/g)].map(match => match[2])
455
+ const residue = syntax.replaceAll(/(["'])([^"'\\]+)\1/g, '').replaceAll(',', '').trim()
456
+ return residue ? { dynamic: true, values: [] } : { values }
457
+ }
458
+
459
+ /**
460
+ * Returns matching properties that are not nested inside another object.
461
+ *
462
+ * @param {string} source JavaScript object contents
463
+ * @param {string} property property name
464
+ * @returns {{index: number, valueStart: number}[]} direct property positions
465
+ */
466
+ function getDirectPropertyPositions (source, property) {
467
+ const nestedObjects = getObjectRanges(source)
468
+ return getPropertyPositions(source, property).filter(position => {
469
+ return !nestedObjects.some(object => object.start < position.index && object.end > position.index)
470
+ })
471
+ }
472
+
473
+ function getOptionValues (args, expected) {
474
+ const values = []
475
+ for (let index = 0; index < args.length; index++) {
476
+ if (args[index].split('=', 1)[0] !== expected) continue
477
+ values.push(args[index].includes('=') ? args[index].slice(args[index].indexOf('=') + 1) : args[++index])
478
+ }
479
+ return values.filter(Boolean)
480
+ }
481
+
482
+ function isContainedDirectory (root, candidate) {
483
+ try {
484
+ const physicalRoot = fs.realpathSync(root)
485
+ const physicalCandidate = fs.realpathSync(candidate)
486
+ const relative = path.relative(physicalRoot, physicalCandidate)
487
+ return fs.statSync(physicalCandidate).isDirectory() &&
488
+ (relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative)))
489
+ } catch {
490
+ return false
491
+ }
492
+ }
493
+
494
+ function readText (filename) {
495
+ try {
496
+ const stat = fs.statSync(filename)
497
+ if (!stat.isFile() || stat.size > 512 * 1024) return
498
+ return fs.readFileSync(filename, 'utf8')
499
+ } catch {}
500
+ }
501
+
502
+ function getPhysicalPath (filename) {
503
+ try {
504
+ const stat = fs.lstatSync(filename)
505
+ if (!stat.isFile() || stat.isSymbolicLink()) return
506
+ return fs.realpathSync(filename)
507
+ } catch {}
508
+ }
509
+
510
+ function getPhysicalDirectory (directory) {
511
+ try {
512
+ const physical = fs.realpathSync(directory)
513
+ if (fs.statSync(physical).isDirectory()) return physical
514
+ } catch {}
515
+ }
516
+
517
+ module.exports = { bindLiteralProject, supportsGeneratedFiles }