dd-trace 6.4.0 → 6.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.electron.md +7 -0
- package/README.md +17 -0
- package/ci/diagnose.js +2100 -0
- package/ci/init.js +23 -2
- package/ci/runbook.md +198 -0
- package/ci/test-optimization-validation/approval-artifacts.js +143 -0
- package/ci/test-optimization-validation/approval.js +299 -0
- package/ci/test-optimization-validation/artifact-id.js +20 -0
- package/ci/test-optimization-validation/bounded-json.js +121 -0
- package/ci/test-optimization-validation/ci-command-candidate.js +83 -0
- package/ci/test-optimization-validation/ci-discovery.js +181 -0
- package/ci/test-optimization-validation/ci-remediation.js +210 -0
- package/ci/test-optimization-validation/cli.js +903 -0
- package/ci/test-optimization-validation/command-blocker.js +81 -0
- package/ci/test-optimization-validation/command-output-policy.js +206 -0
- package/ci/test-optimization-validation/command-runner.js +707 -0
- package/ci/test-optimization-validation/command-suitability.js +471 -0
- package/ci/test-optimization-validation/executable-approval.js +48 -0
- package/ci/test-optimization-validation/executable.js +480 -0
- package/ci/test-optimization-validation/generated-file-policy.js +63 -0
- package/ci/test-optimization-validation/generated-files.js +351 -0
- package/ci/test-optimization-validation/generated-verifier.js +196 -0
- package/ci/test-optimization-validation/init-probe-preload.js +163 -0
- package/ci/test-optimization-validation/init-probe.js +246 -0
- package/ci/test-optimization-validation/late-initialization.js +63 -0
- package/ci/test-optimization-validation/local-command.js +163 -0
- package/ci/test-optimization-validation/manifest-loader.js +133 -0
- package/ci/test-optimization-validation/manifest-scaffold.js +738 -0
- package/ci/test-optimization-validation/manifest-schema.js +862 -0
- package/ci/test-optimization-validation/offline-fixtures.js +327 -0
- package/ci/test-optimization-validation/offline-output.js +406 -0
- package/ci/test-optimization-validation/payload-normalizer.js +72 -0
- package/ci/test-optimization-validation/plan-writer.js +1120 -0
- package/ci/test-optimization-validation/preflight-runner.js +114 -0
- package/ci/test-optimization-validation/redaction.js +331 -0
- package/ci/test-optimization-validation/report-writer.js +1508 -0
- package/ci/test-optimization-validation/safe-files.js +203 -0
- package/ci/test-optimization-validation/scenarios/auto-test-retries.js +159 -0
- package/ci/test-optimization-validation/scenarios/basic-reporting.js +657 -0
- package/ci/test-optimization-validation/scenarios/ci-wiring.js +780 -0
- package/ci/test-optimization-validation/scenarios/early-flake-detection.js +141 -0
- package/ci/test-optimization-validation/scenarios/helpers.js +539 -0
- package/ci/test-optimization-validation/scenarios/test-management.js +218 -0
- package/ci/test-optimization-validation/setup-runner.js +97 -0
- package/ci/test-optimization-validation/static-diagnosis.js +159 -0
- package/ci/test-optimization-validation/test-output.js +129 -0
- package/ci/test-optimization-validation-manifest.schema.json +1 -0
- package/ci/validate-test-optimization.js +3 -0
- package/ext/exporters.js +1 -0
- package/index.d.ts +121 -8
- package/init.js +18 -0
- package/initialize.mjs +1 -1
- package/loader-hook.mjs +28 -18
- package/openfeature.d.ts +1 -0
- package/openfeature.js +4 -0
- package/package.json +10 -7
- package/packages/datadog-instrumentations/src/ai.js +8 -2
- package/packages/datadog-instrumentations/src/child_process.js +1 -1
- package/packages/datadog-instrumentations/src/claude-agent-sdk.js +1 -1
- package/packages/datadog-instrumentations/src/cucumber-worker-threads.js +9 -3
- package/packages/datadog-instrumentations/src/cucumber.js +20 -7
- package/packages/datadog-instrumentations/src/cypress-config.js +398 -52
- package/packages/datadog-instrumentations/src/fastify.js +7 -11
- package/packages/datadog-instrumentations/src/helpers/hook.js +27 -6
- package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +8 -8
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +28 -2
- package/packages/datadog-instrumentations/src/jest.js +26 -0
- package/packages/datadog-instrumentations/src/mariadb.js +1 -1
- package/packages/datadog-instrumentations/src/mocha/main.js +0 -2
- package/packages/datadog-instrumentations/src/mocha/utils.js +0 -4
- package/packages/datadog-instrumentations/src/mongodb.js +3 -3
- package/packages/datadog-instrumentations/src/mongoose.js +3 -3
- package/packages/datadog-instrumentations/src/mquery.js +2 -2
- package/packages/datadog-instrumentations/src/mysql.js +1 -1
- package/packages/datadog-instrumentations/src/next.js +24 -0
- package/packages/datadog-instrumentations/src/nyc.js +0 -2
- package/packages/datadog-instrumentations/src/oracledb.js +2 -2
- package/packages/datadog-instrumentations/src/pg.js +2 -2
- package/packages/datadog-instrumentations/src/playwright.js +159 -22
- package/packages/datadog-instrumentations/src/process.js +3 -0
- package/packages/datadog-instrumentations/src/router.js +18 -8
- package/packages/datadog-instrumentations/src/selenium.js +52 -26
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +2 -2
- package/packages/datadog-instrumentations/src/vitest-main.js +0 -2
- package/packages/datadog-instrumentations/src/ws.js +2 -1
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +2 -2
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +2 -1
- package/packages/datadog-plugin-aws-sdk/src/util.js +2 -2
- package/packages/datadog-plugin-cucumber/src/index.js +1 -1
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +19 -57
- package/packages/datadog-plugin-cypress/src/index.js +6 -1
- package/packages/datadog-plugin-cypress/src/support.js +92 -26
- package/packages/datadog-plugin-electron/src/ipc.js +1 -1
- package/packages/datadog-plugin-graphql/src/execute.js +87 -45
- package/packages/datadog-plugin-graphql/src/validate.js +2 -2
- package/packages/datadog-plugin-http2/src/client.js +1 -1
- package/packages/datadog-plugin-langchain/src/handlers/embedding.js +2 -1
- package/packages/datadog-plugin-langchain/src/handlers/language_models.js +2 -1
- package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
- package/packages/datadog-plugin-playwright/src/index.js +177 -64
- package/packages/datadog-plugin-router/src/index.js +11 -2
- package/packages/datadog-plugin-selenium/src/index.js +5 -36
- package/packages/datadog-plugin-ws/src/close.js +2 -1
- package/packages/datadog-plugin-ws/src/producer.js +2 -1
- package/packages/datadog-plugin-ws/src/receiver.js +2 -1
- package/packages/datadog-plugin-ws/src/server.js +2 -1
- package/packages/dd-trace/src/aiguard/sdk.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +2 -2
- package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +3 -1
- package/packages/dd-trace/src/azure_metadata.js +2 -1
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +93 -28
- package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/index.js +160 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +288 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/payload-projection.js +84 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +369 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +60 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +41 -3
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +7 -2
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +1 -1
- package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +6 -2
- package/packages/dd-trace/src/ci-visibility/rum.js +7 -0
- package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +4 -0
- package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +1 -1
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +137 -9
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +168 -8
- package/packages/dd-trace/src/ci-visibility/test-screenshot.js +90 -0
- package/packages/dd-trace/src/config/generated-config-types.d.ts +16 -0
- package/packages/dd-trace/src/config/helper.js +1 -0
- package/packages/dd-trace/src/config/index.js +10 -0
- package/packages/dd-trace/src/config/supported-configurations.json +66 -0
- package/packages/dd-trace/src/exporter.js +2 -0
- package/packages/dd-trace/src/exporters/common/client-library-headers.js +21 -0
- package/packages/dd-trace/src/exporters/common/request.js +59 -30
- package/packages/dd-trace/src/exporters/common/url.js +15 -1
- package/packages/dd-trace/src/feature-registry.js +10 -3
- package/packages/dd-trace/src/llmobs/experiments/client.js +113 -0
- package/packages/dd-trace/src/llmobs/experiments/dataset.js +154 -0
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +283 -0
- package/packages/dd-trace/src/llmobs/experiments/index.js +152 -0
- package/packages/dd-trace/src/llmobs/experiments/noop.js +30 -0
- package/packages/dd-trace/src/llmobs/experiments/result.js +34 -0
- package/packages/dd-trace/src/llmobs/noop.js +6 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +20 -3
- package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +2 -2
- package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/openai/constants.js +8 -0
- package/packages/dd-trace/src/llmobs/plugins/openai/index.js +48 -14
- package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +43 -0
- package/packages/dd-trace/src/llmobs/sdk.js +17 -0
- package/packages/dd-trace/src/llmobs/tagger.js +68 -0
- package/packages/dd-trace/src/llmobs/telemetry.js +2 -1
- package/packages/dd-trace/src/llmobs/util.js +32 -0
- package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +322 -0
- package/packages/dd-trace/src/openfeature/configuration_source.js +90 -0
- package/packages/dd-trace/src/openfeature/flagging_provider.js +14 -23
- package/packages/dd-trace/src/openfeature/index.js +0 -2
- package/packages/dd-trace/src/openfeature/noop.js +1 -28
- package/packages/dd-trace/src/openfeature/register.js +16 -27
- package/packages/dd-trace/src/openfeature/remote_config.js +15 -18
- package/packages/dd-trace/src/openfeature/require-provider.js +18 -0
- package/packages/dd-trace/src/opentelemetry/span-ending-hook.js +10 -0
- package/packages/dd-trace/src/opentelemetry/span.js +5 -0
- package/packages/dd-trace/src/plugins/ci_plugin.js +20 -6
- package/packages/dd-trace/src/plugins/util/git.js +3 -2
- package/packages/dd-trace/src/plugins/util/inferred_proxy.js +29 -5
- package/packages/dd-trace/src/plugins/util/test.js +56 -4
- package/packages/dd-trace/src/proxy.js +84 -7
- package/packages/dd-trace/src/span_processor.js +5 -0
- package/packages/dd-trace/src/standalone/index.js +0 -14
- package/packages/dd-trace/src/telemetry/send-data.js +2 -2
- package/packages/dd-trace/src/util.js +26 -0
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +1 -1
- package/vendor/dist/@datadog/sketches-js/index.js +1 -1
- package/vendor/dist/protobufjs/index.js +1 -1
- package/vendor/dist/protobufjs/minimal/index.js +1 -1
- package/vendor/dist/shell-quote/index.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/require-optional-peer.js +0 -17
|
@@ -0,0 +1,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
|
+
}
|