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,903 @@
1
+ 'use strict'
2
+
3
+ /* eslint-disable no-console */
4
+
5
+ const fs = require('fs')
6
+ const path = require('path')
7
+
8
+ const { getFrameworkDefinitions } = require('../diagnose')
9
+ const { DD_MAJOR } = require('../../version')
10
+
11
+ const { assertApprovalDigest, getApprovalDigest } = require('./approval')
12
+ const { loadApprovedPlan } = require('./approval-artifacts')
13
+
14
+ const { runBasicReporting } = require('./scenarios/basic-reporting')
15
+ const { runEarlyFlakeDetection } = require('./scenarios/early-flake-detection')
16
+ const { runAutoTestRetries } = require('./scenarios/auto-test-retries')
17
+ const { runTestManagement } = require('./scenarios/test-management')
18
+ const { runCiWiring } = require('./scenarios/ci-wiring')
19
+ const { cleanupGeneratedFiles } = require('./generated-files')
20
+ const { verifyGeneratedTestStrategy } = require('./generated-verifier')
21
+ const { annotateCiDiscovery } = require('./ci-discovery')
22
+ const { loadManifest } = require('./manifest-loader')
23
+ const { createManifestScaffold } = require('./manifest-scaffold')
24
+ const { formatExecutionPlan, getApprovalSummaryPath, getExecutionPlanPath } = require('./plan-writer')
25
+ const { runFrameworkPreflight } = require('./preflight-runner')
26
+ const { sanitizeConsoleText } = require('./redaction')
27
+ const { writePendingReport, writeReport } = require('./report-writer')
28
+ const { ensureSafeDirectory } = require('./safe-files')
29
+ const { runSetupCommands } = require('./setup-runner')
30
+ const {
31
+ getStaticBlocker,
32
+ runStaticDiagnosis,
33
+ } = require('./static-diagnosis')
34
+
35
+ const DEFAULT_MANIFEST = './dd-test-optimization-validation-manifest.json'
36
+ const DEFAULT_OUT = './dd-test-optimization-validation-results'
37
+
38
+ const SCENARIOS = {
39
+ 'basic-reporting': runBasicReporting,
40
+ efd: runEarlyFlakeDetection,
41
+ atr: runAutoTestRetries,
42
+ 'test-management': runTestManagement,
43
+ }
44
+ const BASIC_REPORTING_SCENARIO = 'basic-reporting'
45
+ const CI_WIRING_SCENARIO = 'ci-wiring'
46
+
47
+ function parseArgs (argv) {
48
+ const options = {
49
+ manifest: DEFAULT_MANIFEST,
50
+ out: DEFAULT_OUT,
51
+ frameworks: new Set(),
52
+ scenarios: new Set(getSelectableScenarios()),
53
+ requestedScenario: null,
54
+ keepTempFiles: false,
55
+ verbose: false,
56
+ approvalOverrides: [],
57
+ }
58
+
59
+ for (let i = 0; i < argv.length; i++) {
60
+ const arg = argv[i]
61
+ switch (arg) {
62
+ case '--manifest':
63
+ options.manifest = requireValue(argv, ++i, arg)
64
+ options.approvalOverrides.push(arg)
65
+ break
66
+ case '--out':
67
+ options.out = requireValue(argv, ++i, arg)
68
+ options.approvalOverrides.push(arg)
69
+ break
70
+ case '--framework':
71
+ options.frameworks.add(normalizeFrameworkTarget(requireValue(argv, ++i, arg)))
72
+ options.approvalOverrides.push(arg)
73
+ break
74
+ case '--scenario':
75
+ options.requestedScenario = requireValue(argv, ++i, arg)
76
+ options.scenarios = normalizeScenarioSelection(options.requestedScenario)
77
+ options.approvalOverrides.push(arg)
78
+ break
79
+ case '--keep-temp-files':
80
+ options.keepTempFiles = true
81
+ options.approvalOverrides.push(arg)
82
+ break
83
+ case '--verbose':
84
+ options.verbose = true
85
+ options.approvalOverrides.push(arg)
86
+ break
87
+ case '--validate-manifest':
88
+ options.validateManifest = true
89
+ break
90
+ case '--init-manifest':
91
+ options.initManifest = true
92
+ break
93
+ case '--print-plan':
94
+ options.printPlan = true
95
+ break
96
+ case '--print-approval-sha256':
97
+ options.printApprovalSha256 = true
98
+ break
99
+ case '--approved-plan-sha256':
100
+ options.approvedPlanSha256 = requireValue(argv, ++i, arg)
101
+ break
102
+ case '--offline-fixture-nonce':
103
+ options.offlineFixtureNonce = requireValue(argv, ++i, arg)
104
+ break
105
+ case '--run-approved-plan':
106
+ options.runApprovedPlan = requireValue(argv, ++i, arg)
107
+ break
108
+ case '--sha256':
109
+ options.approvedArtifactSha256 = requireValue(argv, ++i, arg)
110
+ break
111
+ case '--help':
112
+ case '-h':
113
+ options.help = true
114
+ break
115
+ default:
116
+ throw new Error(`Unknown argument: ${arg}`)
117
+ }
118
+ }
119
+
120
+ for (const scenario of options.scenarios) {
121
+ if (!getSelectableScenarios().includes(scenario)) {
122
+ throw new Error(`Unknown scenario "${scenario}". Expected one of: ${getSelectableScenarios().join(', ')}`)
123
+ }
124
+ }
125
+
126
+ return options
127
+ }
128
+
129
+ function requireValue (argv, index, flag) {
130
+ if (!argv[index]) {
131
+ throw new Error(`${flag} requires a value`)
132
+ }
133
+ return argv[index]
134
+ }
135
+
136
+ function printHelp () {
137
+ console.log(`Usage:
138
+ node ci/validate-test-optimization.js [options]
139
+
140
+ Options:
141
+ --manifest <path> Manifest path. Defaults to ${DEFAULT_MANIFEST}
142
+ --out <path> Output directory. Defaults to ${DEFAULT_OUT}
143
+ --framework <id> Run one framework entry. Can be repeated. A trailing ":" is ignored.
144
+ A framework kind such as "vitest" runs all matching Vitest entries.
145
+ --scenario <name> Run one scenario: ${getSelectableScenarios().join(', ')}
146
+ --keep-temp-files Leave generated validation files in place.
147
+ --verbose Print command progress.
148
+ --validate-manifest Validate the manifest and exit without running project code.
149
+ --init-manifest Create a schema-valid manifest scaffold without running project code.
150
+ --print-plan Write the plan and approval artifacts without running project code.
151
+ --run-approved-plan Run the exact approval.json produced by --print-plan.
152
+ --sha256 <digest> Require approval.json and reconstructed current inputs to match this SHA-256.
153
+ --help Show this help.
154
+ `)
155
+ }
156
+
157
+ async function main (argv) {
158
+ try {
159
+ const options = parseArgs(argv)
160
+ if (options.help) {
161
+ printHelp()
162
+ return
163
+ }
164
+
165
+ if (options.initManifest) {
166
+ const manifestPath = path.resolve(options.manifest)
167
+ if (path.dirname(manifestPath) !== process.cwd()) {
168
+ throw new Error('The generated manifest must be stored directly in the current repository root.')
169
+ }
170
+ const manifest = createManifestScaffold({ root: process.cwd(), frameworks: options.frameworks })
171
+ fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, { flag: 'wx' })
172
+ console.log(sanitizeConsoleText(
173
+ `Created validation manifest scaffold without running project code: ${manifestPath}\n` +
174
+ 'This scaffold is already schema-valid; preserve its command boilerplate. Review the selected test commands ' +
175
+ 'and CI files listed in ciDiscovery, record one replayable CI test step when available, then run ' +
176
+ '--validate-manifest and follow its field-specific errors.'
177
+ ))
178
+ return
179
+ }
180
+
181
+ if (options.runApprovedPlan) applyApprovedPlanOptions(options)
182
+
183
+ const manifest = loadManifest(options.manifest)
184
+ if (options.printPlan) {
185
+ const out = validateOutputPath(manifest, options.out)
186
+ const approvalManifest = getApprovalManifest(manifest, options.frameworks)
187
+ formatExecutionPlan({
188
+ manifest: approvalManifest,
189
+ out,
190
+ selectedFrameworkIds: options.frameworks.size > 0
191
+ ? approvalManifest.frameworks.map(framework => framework.id)
192
+ : [],
193
+ requestedScenario: options.requestedScenario,
194
+ keepTempFiles: options.keepTempFiles,
195
+ verbose: options.verbose,
196
+ })
197
+ console.log(sanitizeConsoleText(
198
+ '[test-optimization-validator-agent] Customer approval summary written to ' +
199
+ `${getApprovalSummaryPath(out)}. Read that file and send its complete contents in a user-facing message ` +
200
+ `before requesting approval. Detailed audit information is in ${getExecutionPlanPath(out)}. ` +
201
+ 'The approval command is available only in the summary.'
202
+ ))
203
+ return
204
+ }
205
+ if (options.printApprovalSha256) {
206
+ if (!options.offlineFixtureNonce) {
207
+ throw new Error('--print-approval-sha256 requires the --offline-fixture-nonce value shown in the plan.')
208
+ }
209
+ const out = validateOutputPath(manifest, options.out)
210
+ const approvalManifest = getApprovalManifest(manifest, options.frameworks)
211
+ console.log(getApprovalDigest({
212
+ manifest: approvalManifest,
213
+ out,
214
+ selectedFrameworkIds: options.frameworks.size > 0
215
+ ? approvalManifest.frameworks.map(framework => framework.id)
216
+ : [],
217
+ requestedScenario: options.requestedScenario,
218
+ offlineFixtureNonce: options.offlineFixtureNonce,
219
+ keepTempFiles: options.keepTempFiles,
220
+ verbose: options.verbose,
221
+ }))
222
+ return
223
+ }
224
+ if (options.validateManifest) {
225
+ console.log(sanitizeConsoleText(`Validation manifest is valid: ${manifest.__path}`))
226
+ return
227
+ }
228
+ if (!options.approvedPlanSha256 || !options.offlineFixtureNonce) {
229
+ throw new Error(
230
+ 'Live validation requires --run-approved-plan and --sha256 from a reviewed --print-plan result. Render and ' +
231
+ 'approve a fresh execution plan first.'
232
+ )
233
+ }
234
+ const out = validateOutputPath(manifest, options.out)
235
+ options.repositoryRoot = manifest.repository.root
236
+ const selectedFrameworks = filterFrameworks(manifest.frameworks, options.frameworks)
237
+ const approvalManifest = getApprovalManifest(manifest, options.frameworks)
238
+ assertApprovalDigest(options.approvedPlanSha256, {
239
+ manifest: approvalManifest,
240
+ out,
241
+ selectedFrameworkIds: options.frameworks.size > 0
242
+ ? selectedFrameworks.map(framework => framework.id)
243
+ : [],
244
+ requestedScenario: options.requestedScenario,
245
+ offlineFixtureNonce: options.offlineFixtureNonce,
246
+ keepTempFiles: options.keepTempFiles,
247
+ verbose: options.verbose,
248
+ })
249
+ options.requireExecutableApproval = true
250
+ ensureSafeDirectory(manifest.repository.root, out, 'validation output directory', { allowRootSymlink: true })
251
+ if (writePendingReport) writePendingReport({ manifest, out })
252
+ const staticDiagnosis = runStaticDiagnosis({ manifest, out })
253
+ annotateCiDiscovery({ manifest, diagnosis: staticDiagnosis.report })
254
+
255
+ const results = []
256
+ const runnableFrameworks = []
257
+
258
+ try {
259
+ const frameworks = filterFrameworks(manifest.frameworks, options.frameworks)
260
+ const liveReadyFrameworks = []
261
+
262
+ for (const framework of frameworks) {
263
+ if (framework.status !== 'runnable') {
264
+ results.push(getFrameworkStatusResult(framework))
265
+ continue
266
+ }
267
+
268
+ const staticBlocker = getStaticBlocker(framework, staticDiagnosis.report)
269
+ if (staticBlocker) {
270
+ results.push(getStaticFailure(framework, staticBlocker, staticDiagnosis.reportPath))
271
+ continue
272
+ }
273
+
274
+ liveReadyFrameworks.push(framework)
275
+ }
276
+
277
+ for (const framework of liveReadyFrameworks) {
278
+ // Setup commands are project preparation, not Test Optimization signal collection.
279
+ if (framework.setup?.commands?.length > 0) logPhaseStart(framework, 'Project setup')
280
+ // eslint-disable-next-line no-await-in-loop
281
+ const setup = await runSetupCommands({ framework, out, options })
282
+ if (framework.setup?.commands?.length > 0) {
283
+ logPhaseComplete(framework, 'Project setup', setup.ok ? 'pass' : setup.failure?.status)
284
+ }
285
+ if (!setup.ok) {
286
+ results.push(setup.failure)
287
+ continue
288
+ }
289
+
290
+ runnableFrameworks.push(framework)
291
+ }
292
+ for (const framework of runnableFrameworks) {
293
+ let basicResult
294
+ if (options.scenarios.has(BASIC_REPORTING_SCENARIO)) {
295
+ // The validator owns the dd-trace-less control so ambient agent initialization cannot contaminate it.
296
+ logPhaseStart(framework, 'Test execution without Datadog')
297
+ // eslint-disable-next-line no-await-in-loop
298
+ const preflight = await runFrameworkPreflight({ framework, out, options })
299
+ logPhaseComplete(
300
+ framework,
301
+ 'Test execution without Datadog',
302
+ preflight.ok ? 'pass' : preflight.failure?.status
303
+ )
304
+ // Scenarios intentionally run in order so each one can use an isolated offline fixture.
305
+ if (preflight.ok) {
306
+ logPhaseStart(framework, 'Basic Reporting')
307
+ // eslint-disable-next-line no-await-in-loop
308
+ basicResult = await SCENARIOS[BASIC_REPORTING_SCENARIO]({ manifest, framework, out, options })
309
+ logPhaseComplete(framework, 'Basic Reporting', basicResult.status)
310
+ } else {
311
+ basicResult = preflight.failure
312
+ }
313
+ results.push(basicResult)
314
+ }
315
+
316
+ if (options.scenarios.has(CI_WIRING_SCENARIO)) {
317
+ if (basicResult && basicResult.status !== 'pass') {
318
+ results.push(getSkippedCiWiringAfterBasicFailure(framework, basicResult))
319
+ } else {
320
+ // CI wiring runs after Basic Reporting proves this framework can report when initialized directly.
321
+ logPhaseStart(framework, 'CI wiring')
322
+ // eslint-disable-next-line no-await-in-loop
323
+ const ciWiringResult = await runCiWiring({ manifest, framework, out, options, basicResult })
324
+ results.push(ciWiringResult)
325
+ logPhaseComplete(framework, 'CI wiring', ciWiringResult.status)
326
+ }
327
+ }
328
+
329
+ const advancedScenarios = getAdvancedScenarios(options.scenarios)
330
+ if (basicResult && basicResult.status !== 'pass') {
331
+ for (const scenario of advancedScenarios) {
332
+ results.push(getSkippedAfterBasicFailure(framework, scenario, basicResult))
333
+ }
334
+ continue
335
+ }
336
+
337
+ if (advancedScenarios.length > 0) {
338
+ logPhaseStart(framework, 'Temporary test verification')
339
+ // eslint-disable-next-line no-await-in-loop
340
+ const generatedVerification = await verifyGeneratedTestStrategy({ framework, out, options })
341
+ logPhaseComplete(
342
+ framework,
343
+ 'Temporary test verification',
344
+ generatedVerification.ok ? 'pass' : generatedVerification.failure?.status
345
+ )
346
+ if (!generatedVerification.ok) {
347
+ results.push(generatedVerification.failure)
348
+ for (const scenario of advancedScenarios) {
349
+ results.push(getSkippedAfterGeneratedVerificationFailure(
350
+ framework,
351
+ scenario,
352
+ generatedVerification.failure
353
+ ))
354
+ }
355
+ continue
356
+ }
357
+ }
358
+
359
+ for (const scenario of advancedScenarios) {
360
+ const runScenario = SCENARIOS[scenario]
361
+ // Scenarios intentionally run in order so each one can use an isolated offline fixture.
362
+ logPhaseStart(framework, getScenarioDisplayName(scenario))
363
+ // eslint-disable-next-line no-await-in-loop
364
+ const result = await runScenario({ manifest, framework, out, options })
365
+ results.push(result)
366
+ logPhaseComplete(framework, getScenarioDisplayName(scenario), result.status)
367
+ }
368
+ }
369
+ } finally {
370
+ await cleanupGeneratedFiles(manifest, { keep: options.keepTempFiles })
371
+ }
372
+
373
+ addMissingRequiredResults(results, runnableFrameworks, options.scenarios)
374
+ const validatorExitCode = results.some(isUnsuccessfulResult) || !didRunLiveValidation(results) ? 1 : 0
375
+ await writeReport({
376
+ manifest,
377
+ results,
378
+ out,
379
+ staticDiagnosis,
380
+ runSummary: {
381
+ runCompleted: true,
382
+ validatorExitCode,
383
+ validationCoverage: getValidationCoverage({
384
+ results,
385
+ requestedScenario: options.requestedScenario,
386
+ frameworks: selectedFrameworks,
387
+ scenarios: options.scenarios,
388
+ }),
389
+ checkedScenarios: [...options.scenarios],
390
+ omittedScenarios: getSelectableScenarios().filter(scenario => !options.scenarios.has(scenario)),
391
+ requestedScenario: options.requestedScenario,
392
+ },
393
+ })
394
+ process.exitCode = validatorExitCode
395
+ } catch (err) {
396
+ process.exitCode = 1
397
+ console.error(sanitizeConsoleText(err && err.stack ? err.stack : err))
398
+ }
399
+ }
400
+
401
+ /**
402
+ * Reconstructs live options from a hash-verified approval artifact.
403
+ *
404
+ * @param {object} options parsed CLI options
405
+ * @returns {void}
406
+ */
407
+ function applyApprovedPlanOptions (options) {
408
+ if (!options.approvedArtifactSha256) {
409
+ throw new Error('--run-approved-plan requires --sha256 from the reviewed execution plan.')
410
+ }
411
+ if (options.approvalOverrides.length > 0 || options.offlineFixtureNonce || options.approvedPlanSha256) {
412
+ throw new Error(
413
+ '--run-approved-plan cannot be combined with manifest, output, selection, or legacy approval flags.'
414
+ )
415
+ }
416
+
417
+ const { material } = loadApprovedPlan(options.runApprovedPlan, options.approvedArtifactSha256)
418
+ options.manifest = material.manifest.path
419
+ options.out = material.validation.outputDirectory
420
+ options.frameworks = new Set(material.selection.frameworks.map(normalizeFrameworkTarget))
421
+ options.requestedScenario = material.selection.scenario
422
+ options.scenarios = options.requestedScenario
423
+ ? normalizeScenarioSelection(options.requestedScenario)
424
+ : new Set(getSelectableScenarios())
425
+ options.offlineFixtureNonce = material.validation.offlineFixtureNonce
426
+ options.keepTempFiles = material.validation.keepTemporaryFiles === true
427
+ options.verbose = material.validation.verbose === true
428
+ options.approvedPlanSha256 = options.approvedArtifactSha256
429
+ }
430
+
431
+ function validateOutputPath (manifest, outputPath) {
432
+ const root = path.resolve(manifest.repository.root)
433
+ const out = path.resolve(outputPath)
434
+ const relative = path.relative(root, out)
435
+ if (relative === '' || !relative || relative.startsWith('..') || path.isAbsolute(relative)) {
436
+ throw new Error('Validation output directory must be a dedicated child directory inside repository.root.')
437
+ }
438
+ return out
439
+ }
440
+
441
+ function filterFrameworks (frameworks, targets) {
442
+ if (targets.size === 0) return frameworks
443
+
444
+ const selected = frameworks.filter(framework => {
445
+ return targets.has(framework.id) || targets.has(framework.framework)
446
+ })
447
+
448
+ if (selected.length === 0) {
449
+ throw new Error(`No framework entries matched ${formatFrameworkTargets(targets)}. Available entries: ${
450
+ frameworks.map(framework => framework.id).join(', ') || 'none'
451
+ }`)
452
+ }
453
+
454
+ return selected
455
+ }
456
+
457
+ /**
458
+ * Creates the manifest view covered by a framework-scoped approval.
459
+ *
460
+ * @param {object} manifest loaded manifest
461
+ * @param {Set<string>} targets selected framework targets
462
+ * @returns {object} approval manifest
463
+ */
464
+ function getApprovalManifest (manifest, targets) {
465
+ const frameworks = filterFrameworks(manifest.frameworks, targets)
466
+ if (frameworks === manifest.frameworks) return manifest
467
+
468
+ const approvalManifest = { ...manifest, frameworks }
469
+ Object.defineProperty(approvalManifest, '__sourceSha256', {
470
+ configurable: false,
471
+ enumerable: false,
472
+ value: manifest.__sourceSha256,
473
+ writable: false,
474
+ })
475
+ return approvalManifest
476
+ }
477
+
478
+ function normalizeFrameworkTarget (target) {
479
+ const normalized = String(target).trim().replaceAll(/:+$/g, '')
480
+ if (!normalized) {
481
+ throw new Error('Framework target cannot be empty')
482
+ }
483
+ return normalized
484
+ }
485
+
486
+ function formatFrameworkTargets (targets) {
487
+ return [...targets].map(target => `"${target}"`).join(', ')
488
+ }
489
+
490
+ function normalizeScenarioSelection (scenario) {
491
+ if (scenario === BASIC_REPORTING_SCENARIO) return new Set([scenario])
492
+ return new Set([BASIC_REPORTING_SCENARIO, scenario])
493
+ }
494
+
495
+ function getAdvancedScenarios (scenarios) {
496
+ return Object.keys(SCENARIOS).filter(scenario => {
497
+ return scenario !== BASIC_REPORTING_SCENARIO && scenarios.has(scenario)
498
+ })
499
+ }
500
+
501
+ /**
502
+ * Fails closed when orchestration omits a selected check for a runnable framework.
503
+ *
504
+ * @param {object[]} results collected validation results
505
+ * @param {object[]} frameworks runnable frameworks whose live phases started
506
+ * @param {Set<string>} scenarios selected scenarios
507
+ * @returns {void}
508
+ */
509
+ function addMissingRequiredResults (results, frameworks, scenarios) {
510
+ for (const framework of frameworks) {
511
+ for (const scenario of scenarios) {
512
+ if (results.some(result => result.frameworkId === framework.id && result.scenario === scenario)) continue
513
+ results.push({
514
+ frameworkId: framework.id,
515
+ scenario,
516
+ status: 'error',
517
+ diagnosis: `${getScenarioDisplayName(scenario)} was selected but produced no validation result.`,
518
+ evidence: {
519
+ validationIncomplete: true,
520
+ recommendation: 'Rerun the validator. If the check remains absent, report this validator orchestration ' +
521
+ 'error instead of treating the validation as successful.',
522
+ },
523
+ artifacts: [],
524
+ })
525
+ }
526
+ }
527
+ }
528
+
529
+ /**
530
+ * Reports whether all default checks produced results in an unscoped run.
531
+ *
532
+ * @param {object} input coverage inputs
533
+ * @param {object[]} input.results validation results
534
+ * @param {string|null} input.requestedScenario explicitly selected scenario
535
+ * @param {object[]} input.frameworks selected manifest frameworks
536
+ * @param {Set<string>} input.scenarios selected scenarios
537
+ * @returns {'complete'|'partial'} validation coverage
538
+ */
539
+ function getValidationCoverage ({ results, requestedScenario, frameworks, scenarios }) {
540
+ if (requestedScenario) return 'partial'
541
+ if (results.some(result => result.evidence?.manifestIncomplete || result.evidence?.validationIncomplete)) {
542
+ return 'partial'
543
+ }
544
+
545
+ const runnableFrameworks = frameworks.filter(framework => framework.status === 'runnable')
546
+ if (runnableFrameworks.length === 0) return 'partial'
547
+ for (const framework of runnableFrameworks) {
548
+ for (const scenario of scenarios) {
549
+ if (!results.some(result => result.frameworkId === framework.id && result.scenario === scenario)) {
550
+ return 'partial'
551
+ }
552
+ }
553
+ }
554
+ return 'complete'
555
+ }
556
+
557
+ function getSelectableScenarios () {
558
+ return [
559
+ BASIC_REPORTING_SCENARIO,
560
+ CI_WIRING_SCENARIO,
561
+ ...Object.keys(SCENARIOS).filter(scenario => scenario !== BASIC_REPORTING_SCENARIO),
562
+ ]
563
+ }
564
+
565
+ function getSkippedCiWiringAfterBasicFailure (framework, basicResult) {
566
+ return {
567
+ frameworkId: framework.id,
568
+ scenario: 'ci-wiring',
569
+ status: 'skip',
570
+ diagnosis: 'Skipped CI wiring validation because Basic Reporting did not pass with direct Datadog ' +
571
+ 'initialization. Fix the selected test command or local Test Optimization capability before diagnosing CI ' +
572
+ 'wiring.',
573
+ evidence: {
574
+ blockedBy: BASIC_REPORTING_SCENARIO,
575
+ basicReportingStatus: basicResult.status,
576
+ basicReportingDiagnosis: basicResult.diagnosis,
577
+ featureEligibility: {
578
+ eligible: false,
579
+ blockedBy: BASIC_REPORTING_SCENARIO,
580
+ reasonCode: 'basic-reporting-failed',
581
+ scenario: 'ci-wiring',
582
+ },
583
+ ciWiring: framework.ciWiring,
584
+ },
585
+ artifacts: [],
586
+ }
587
+ }
588
+
589
+ function getSkippedAfterBasicFailure (framework, scenario, basicResult) {
590
+ return {
591
+ frameworkId: framework.id,
592
+ scenario,
593
+ status: 'skip',
594
+ diagnosis: `Skipped because basic reporting did not pass: ${basicResult.diagnosis}`,
595
+ evidence: {
596
+ blockedBy: BASIC_REPORTING_SCENARIO,
597
+ basicReportingStatus: basicResult.status,
598
+ basicReportingDiagnosis: basicResult.diagnosis,
599
+ featureEligibility: {
600
+ eligible: false,
601
+ blockedBy: BASIC_REPORTING_SCENARIO,
602
+ reasonCode: 'basic-reporting-failed',
603
+ scenario,
604
+ },
605
+ },
606
+ artifacts: [],
607
+ }
608
+ }
609
+
610
+ function getSkippedAfterGeneratedVerificationFailure (framework, scenario, failure) {
611
+ return {
612
+ frameworkId: framework.id,
613
+ scenario,
614
+ status: 'skip',
615
+ diagnosis: `Skipped because the temporary validation test could not run as expected: ${failure.diagnosis}`,
616
+ evidence: {
617
+ blockedBy: 'generated-test-verification',
618
+ verificationStatus: failure.status,
619
+ verificationDiagnosis: failure.diagnosis,
620
+ featureEligibility: {
621
+ eligible: false,
622
+ blockedBy: 'generated-test-verification',
623
+ reasonCode: 'generated-test-verification-failed',
624
+ scenario,
625
+ },
626
+ },
627
+ artifacts: [],
628
+ }
629
+ }
630
+
631
+ function isUnsuccessfulResult (result) {
632
+ return result.status === 'fail' || result.status === 'error' || result.status === 'blocked'
633
+ }
634
+
635
+ /**
636
+ * Reports whether at least one live validation check produced a result.
637
+ *
638
+ * Framework discovery-only entries use the synthetic `all` scenario and do not prove Test Optimization behavior.
639
+ *
640
+ * @param {object[]} results validation results
641
+ * @returns {boolean} whether live validation ran
642
+ */
643
+ function didRunLiveValidation (results) {
644
+ return results.some(result => result.scenario !== 'all')
645
+ }
646
+
647
+ /**
648
+ * Prints the start of one framework validation phase.
649
+ *
650
+ * @param {object} framework manifest framework entry
651
+ * @param {string} phase customer-facing phase name
652
+ * @returns {void}
653
+ */
654
+ function logPhaseStart (framework, phase) {
655
+ logValidationProgress(`${framework.id}: ${phase} started.`)
656
+ }
657
+
658
+ /**
659
+ * Prints the outcome of one framework validation phase.
660
+ *
661
+ * @param {object} framework manifest framework entry
662
+ * @param {string} phase customer-facing phase name
663
+ * @param {string|undefined} status phase outcome
664
+ * @returns {void}
665
+ */
666
+ function logPhaseComplete (framework, phase, status) {
667
+ logValidationProgress(`${framework.id}: ${phase} ${status || 'complete'}.`)
668
+ }
669
+
670
+ /**
671
+ * Prints a sanitized validator progress line.
672
+ *
673
+ * @param {string} message progress message
674
+ * @returns {void}
675
+ */
676
+ function logValidationProgress (message) {
677
+ console.log(sanitizeConsoleText(`[test-optimization-validator] ${message}`))
678
+ }
679
+
680
+ /**
681
+ * Converts an advanced scenario id to customer-facing text.
682
+ *
683
+ * @param {string} scenario scenario id
684
+ * @returns {string} display name
685
+ */
686
+ function getScenarioDisplayName (scenario) {
687
+ return {
688
+ 'basic-reporting': 'Basic Reporting',
689
+ 'ci-wiring': 'CI Wiring',
690
+ efd: 'Early Flake Detection',
691
+ atr: 'Auto Test Retries',
692
+ 'test-management': 'Test Management',
693
+ }[scenario] || scenario
694
+ }
695
+
696
+ function getStaticFailure (framework, blocker, staticDiagnosisPath) {
697
+ return {
698
+ frameworkId: framework.id,
699
+ scenario: 'all',
700
+ status: 'fail',
701
+ diagnosis: blocker.reason,
702
+ evidence: {
703
+ staticDiagnosis: true,
704
+ recommendation: blocker.recommendation,
705
+ },
706
+ artifacts: [staticDiagnosisPath],
707
+ }
708
+ }
709
+
710
+ function getFrameworkStatusResult (framework) {
711
+ const evidence = getFrameworkStatusEvidence(framework)
712
+
713
+ if (framework.status === 'unsupported_by_validator') {
714
+ const frameworkName = getDisplayFrameworkName(framework.framework)
715
+
716
+ return {
717
+ frameworkId: framework.id,
718
+ scenario: 'all',
719
+ status: 'skip',
720
+ diagnosis: `${frameworkName} is not supported as a Test Optimization test framework.`,
721
+ evidence: {
722
+ ...evidence,
723
+ recommendation: 'Choose a supported framework before running live validation.',
724
+ },
725
+ artifacts: [],
726
+ }
727
+ }
728
+
729
+ return {
730
+ frameworkId: framework.id,
731
+ scenario: 'all',
732
+ status: 'skip',
733
+ diagnosis: getFrameworkStatusDiagnosis(framework, evidence),
734
+ evidence: {
735
+ ...evidence,
736
+ recommendation: 'Provide a small runnable command for this framework, or mark the setup blocker explicitly.',
737
+ },
738
+ artifacts: [],
739
+ }
740
+ }
741
+
742
+ function getFrameworkStatusDiagnosis (framework, evidence) {
743
+ const frameworkName = framework.framework
744
+ const notes = evidence.manifestNotes || []
745
+
746
+ if (isDependencyOnlyDetection(evidence)) {
747
+ return getDependencyOnlyDiagnosis(framework, evidence)
748
+ }
749
+
750
+ if (notes.length > 0) {
751
+ return `${frameworkName} was detected, but no runnable validation command was available. ` +
752
+ `Basic reporting was not run. Manifest reason: ${notes[0]}`
753
+ }
754
+
755
+ return `${frameworkName} was detected, but the manifest did not prove a runnable validation command. ` +
756
+ 'Basic reporting was not run. See discovery evidence for scripts/config files to turn into a small command.'
757
+ }
758
+
759
+ function isDependencyOnlyDetection (evidence) {
760
+ return evidence.directDependency && evidence.configFiles.length === 0 && evidence.frameworkScripts.length === 0
761
+ }
762
+
763
+ function getDependencyOnlyDiagnosis (framework, evidence) {
764
+ const frameworkName = getDisplayFrameworkName(framework.framework)
765
+ const dependency = formatDependency(evidence.directDependency)
766
+ const note = evidence.manifestNotes?.[0] ? ` Manifest note: ${evidence.manifestNotes[0]}` : ''
767
+ const common = `${frameworkName} is installed${dependency}, but this repository does not appear to use ` +
768
+ `${frameworkName} to run tests: no ${framework.framework} config, package script, or runnable ` +
769
+ `${framework.framework} test command was found. Basic reporting was not run for ${frameworkName}.`
770
+
771
+ if (framework.framework === 'playwright') {
772
+ return `${frameworkName} is installed${dependency}, but no Playwright Test setup was found. ` +
773
+ 'The playwright package can be used only for browser automation; Test Optimization validation needs a ' +
774
+ '`playwright test` setup with a config, script, or runnable test command. Basic reporting was not run ' +
775
+ `for ${frameworkName}.${note}`
776
+ }
777
+
778
+ return `${common} If this repo does use ${frameworkName}, provide a small ${frameworkName} test command; ` +
779
+ `otherwise this dependency-only detection can be ignored.${note}`
780
+ }
781
+
782
+ function getDisplayFrameworkName (frameworkName) {
783
+ return {
784
+ cucumber: 'Cucumber',
785
+ cypress: 'Cypress',
786
+ jest: 'Jest',
787
+ mocha: 'Mocha',
788
+ playwright: 'Playwright',
789
+ vitest: 'Vitest',
790
+ }[frameworkName] || frameworkName
791
+ }
792
+
793
+ function formatDependency (dependency) {
794
+ if (!dependency) return ''
795
+ return ` in ${dependency.field}${dependency.version ? ` (${dependency.version})` : ''}`
796
+ }
797
+
798
+ function getFrameworkStatusEvidence (framework) {
799
+ const root = framework.project?.root
800
+ return {
801
+ frameworkStatus: framework.status,
802
+ frameworkVersion: framework.frameworkVersion,
803
+ manifestNotes: Array.isArray(framework.notes) ? framework.notes : [],
804
+ directDependency: root ? getDirectDependency(root, framework.framework) : undefined,
805
+ frameworkScripts: root ? findFrameworkScripts(root, framework.framework) : [],
806
+ testLikeScripts: root ? findTestLikeScripts(root) : [],
807
+ configFiles: root ? findFrameworkConfigFiles(root, framework.framework) : [],
808
+ }
809
+ }
810
+
811
+ function getDirectDependency (root, frameworkName) {
812
+ const packageJson = readPackageJson(root)
813
+ if (!packageJson) return
814
+
815
+ for (const field of ['dependencies', 'devDependencies', 'optionalDependencies']) {
816
+ const value = packageJson[field]?.[frameworkName]
817
+ if (value) return { field, version: value }
818
+ }
819
+ }
820
+
821
+ function findFrameworkScripts (root, frameworkName) {
822
+ return findScripts(root, (name, command) => {
823
+ return includesWord(name, frameworkName) || includesWord(command, frameworkName)
824
+ })
825
+ }
826
+
827
+ function findTestLikeScripts (root) {
828
+ return findScripts(root, name => /(^|:)(test|unit|e2e|integration|ci)(:|$)/.test(name)).slice(0, 8)
829
+ }
830
+
831
+ function findScripts (root, predicate) {
832
+ const packageJson = readPackageJson(root)
833
+ const scripts = packageJson?.scripts || {}
834
+ const matches = []
835
+ for (const [name, command] of Object.entries(scripts)) {
836
+ if (predicate(name, command)) matches.push({ name, command })
837
+ }
838
+ return matches.slice(0, 8)
839
+ }
840
+
841
+ function findFrameworkConfigFiles (root, frameworkName) {
842
+ const patterns = getFrameworkConfigPatterns(frameworkName)
843
+ if (patterns.length === 0) return []
844
+
845
+ const files = []
846
+ findFiles(root, 4, file => {
847
+ if (patterns.some(pattern => pattern.test(path.basename(file)))) {
848
+ files.push(path.relative(root, file))
849
+ }
850
+ return files.length < 8
851
+ })
852
+ return files
853
+ }
854
+
855
+ function getFrameworkConfigPatterns (frameworkName) {
856
+ const definition = getFrameworkDefinitions(DD_MAJOR).find(definition => definition.id === frameworkName)
857
+ return definition?.configPatterns || []
858
+ }
859
+
860
+ function readPackageJson (root) {
861
+ try {
862
+ return JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'))
863
+ } catch {
864
+ return null
865
+ }
866
+ }
867
+
868
+ function findFiles (dir, depth, visit) {
869
+ if (depth < 0) return true
870
+
871
+ let entries
872
+ try {
873
+ entries = fs.readdirSync(dir, { withFileTypes: true })
874
+ } catch {
875
+ return true
876
+ }
877
+
878
+ for (const entry of entries) {
879
+ if (shouldSkipDirectory(entry.name)) continue
880
+ const filename = path.join(dir, entry.name)
881
+ if (entry.isDirectory()) {
882
+ if (!findFiles(filename, depth - 1, visit)) return false
883
+ } else if (!visit(filename)) {
884
+ return false
885
+ }
886
+ }
887
+
888
+ return true
889
+ }
890
+
891
+ function shouldSkipDirectory (name) {
892
+ return name === '.git' || name === 'node_modules' || name === 'dist' || name === 'coverage'
893
+ }
894
+
895
+ function includesWord (value, word) {
896
+ return new RegExp(`(^|[^a-zA-Z0-9_-])${escapeRegExp(word)}([^a-zA-Z0-9_-]|$)`).test(value)
897
+ }
898
+
899
+ function escapeRegExp (value) {
900
+ return value.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`)
901
+ }
902
+
903
+ module.exports = { filterFrameworks, main, normalizeFrameworkTarget, parseArgs }