dd-trace 6.8.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.
- package/ci/diagnose.js +27 -25
- package/ci/init.js +6 -6
- package/ci/runbook.md +114 -20
- package/ci/test-optimization-validation/approval-artifacts.js +36 -3
- package/ci/test-optimization-validation/approval.js +78 -22
- package/ci/test-optimization-validation/blocker-category.js +24 -0
- package/ci/test-optimization-validation/ci-discovery.js +23 -17
- package/ci/test-optimization-validation/ci-package-scripts.js +34 -6
- package/ci/test-optimization-validation/ci-remediation.js +1 -1
- package/ci/test-optimization-validation/cli.js +280 -52
- package/ci/test-optimization-validation/command-blocker.js +159 -20
- package/ci/test-optimization-validation/command-output-policy.js +9 -3
- package/ci/test-optimization-validation/command-runner.js +9 -7
- package/ci/test-optimization-validation/environment.js +4 -2
- package/ci/test-optimization-validation/executable.js +15 -13
- package/ci/test-optimization-validation/execution-lock.js +70 -0
- package/ci/test-optimization-validation/framework-adapters/cucumber-validation.json +3 -0
- package/ci/test-optimization-validation/framework-adapters/cucumber.js +657 -5
- package/ci/test-optimization-validation/framework-adapters/vitest.js +517 -0
- package/ci/test-optimization-validation/generated-files.js +38 -28
- package/ci/test-optimization-validation/generated-test-contract.js +8 -4
- package/ci/test-optimization-validation/generated-verifier.js +24 -17
- package/ci/test-optimization-validation/literal-glob.js +52 -0
- package/ci/test-optimization-validation/manifest-loader.js +3 -0
- package/ci/test-optimization-validation/manifest-scaffold.js +300 -50
- package/ci/test-optimization-validation/manifest-schema.js +80 -17
- package/ci/test-optimization-validation/offline-fixtures.js +13 -4
- package/ci/test-optimization-validation/package-check.js +94 -0
- package/ci/test-optimization-validation/plan-writer.js +183 -10
- package/ci/test-optimization-validation/preflight-runner.js +110 -31
- package/ci/test-optimization-validation/project-build-artifact.js +31 -0
- package/ci/test-optimization-validation/redaction.js +18 -27
- package/ci/test-optimization-validation/report-writer.js +182 -48
- package/ci/test-optimization-validation/result-semantics.js +20 -3
- package/ci/test-optimization-validation/runner-command.js +40 -20
- package/ci/test-optimization-validation/runner-contract.js +79 -43
- package/ci/test-optimization-validation/scenarios/basic-reporting.js +59 -5
- package/ci/test-optimization-validation/scenarios/ci-wiring.js +141 -35
- package/ci/test-optimization-validation/scenarios/helpers.js +30 -6
- package/ci/test-optimization-validation/static-diagnosis.js +10 -3
- package/ci/test-optimization-validation/test-output.js +2 -1
- package/ci/test-optimization-validation/validation-blocker.js +21 -0
- package/ci/vitest-no-worker-init-setup.mjs +30 -34
- package/index.d.ts +98 -1
- package/init.js +1 -17
- package/initialize.mjs +11 -0
- package/loader-hook.mjs +6 -4
- package/package.json +5 -3
- package/packages/datadog-core/src/utils/src/parse-tags.js +0 -1
- package/packages/datadog-esbuild/src/utils.js +16 -1
- package/packages/datadog-instrumentations/src/ai.js +61 -34
- package/packages/datadog-instrumentations/src/bluebird.js +6 -6
- package/packages/datadog-instrumentations/src/claude-agent-sdk.js +81 -5
- package/packages/datadog-instrumentations/src/cucumber.js +124 -47
- package/packages/datadog-instrumentations/src/dns.js +21 -2
- package/packages/datadog-instrumentations/src/electron.js +1 -0
- package/packages/datadog-instrumentations/src/fastify.js +3 -1
- package/packages/datadog-instrumentations/src/grpc/client.js +4 -2
- package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +3 -1
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +33 -9
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +118 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +20 -2
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.js +32 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +61 -0
- package/packages/datadog-instrumentations/src/helpers/router-helper.js +37 -6
- package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +1 -1
- package/packages/datadog-instrumentations/src/jest.js +979 -233
- package/packages/datadog-instrumentations/src/mocha/main.js +107 -9
- package/packages/datadog-instrumentations/src/mocha/utils.js +64 -42
- package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +25 -0
- package/packages/datadog-instrumentations/src/mocha/worker.js +12 -18
- package/packages/datadog-instrumentations/src/mysql.js +1 -1
- package/packages/datadog-instrumentations/src/next.js +155 -23
- package/packages/datadog-instrumentations/src/openai.js +14 -0
- package/packages/datadog-instrumentations/src/pg.js +180 -6
- package/packages/datadog-instrumentations/src/playwright.js +173 -79
- package/packages/datadog-instrumentations/src/promise-js.js +4 -4
- package/packages/datadog-instrumentations/src/promise.js +3 -3
- package/packages/datadog-instrumentations/src/router.js +195 -19
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +43 -10
- package/packages/datadog-instrumentations/src/vitest-main.js +360 -56
- package/packages/datadog-instrumentations/src/vitest-util.js +42 -7
- package/packages/datadog-instrumentations/src/vitest-worker.js +234 -17
- package/packages/datadog-instrumentations/src/webdriverio.js +194 -18
- package/packages/datadog-instrumentations/src/when.js +3 -3
- package/packages/datadog-instrumentations/src/ws.js +5 -1
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +59 -48
- package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +1 -3
- package/packages/datadog-plugin-cucumber/src/index.js +5 -1
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +50 -46
- package/packages/datadog-plugin-cypress/src/support.js +7 -4
- package/packages/datadog-plugin-electron/src/net.js +10 -4
- package/packages/datadog-plugin-http/src/client.js +1 -1
- package/packages/datadog-plugin-http2/src/client.js +1 -1
- package/packages/datadog-plugin-jest/src/index.js +1 -18
- package/packages/datadog-plugin-jest/src/util.js +2 -0
- package/packages/datadog-plugin-kafkajs/src/producer.js +2 -1
- package/packages/datadog-plugin-mocha/src/index.js +318 -1
- package/packages/datadog-plugin-next/src/index.js +57 -9
- package/packages/datadog-plugin-openai/src/tracing.js +4 -0
- package/packages/datadog-plugin-pg/src/index.js +65 -1
- package/packages/datadog-plugin-playwright/src/index.js +1 -1
- package/packages/datadog-plugin-vitest/src/index.js +95 -7
- package/packages/datadog-shimmer/src/shimmer.js +6 -2
- package/packages/dd-trace/src/aiguard/sdk.js +3 -0
- package/packages/dd-trace/src/appsec/{blocking.js → blocking/index.js} +26 -6
- package/packages/dd-trace/src/appsec/handlers/auth.js +62 -0
- package/packages/dd-trace/src/appsec/handlers/http-request.js +215 -0
- package/packages/dd-trace/src/appsec/handlers/http-response.js +78 -0
- package/packages/dd-trace/src/appsec/handlers/http-shared.js +24 -0
- package/packages/dd-trace/src/appsec/handlers/payments.js +104 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/hsts-header-missing-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
- package/packages/dd-trace/src/appsec/index.js +48 -476
- package/packages/dd-trace/src/appsec/sdk/track_event.js +9 -0
- package/packages/dd-trace/src/ci-visibility/coverage-report-discovery.js +46 -14
- package/packages/dd-trace/src/ci-visibility/efd-retry-policy.js +89 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +27 -13
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +17 -0
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +21 -1
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +22 -7
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +9 -14
- package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +21 -5
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +1 -1
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +5 -1
- package/packages/dd-trace/src/config/generated-config-types.d.ts +2 -0
- package/packages/dd-trace/src/config/helper.js +2 -0
- package/packages/dd-trace/src/config/index.js +16 -5
- package/packages/dd-trace/src/config/remote_config.js +1 -1
- package/packages/dd-trace/src/config/supported-configurations.json +8 -0
- package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -1
- package/packages/dd-trace/src/encode/coverage-ci-visibility.js +0 -1
- package/packages/dd-trace/src/guardrails/apply-pm2-env.js +16 -0
- package/packages/dd-trace/src/guardrails/log.js +1 -10
- package/packages/dd-trace/src/llmobs/eval-metric.js +249 -0
- package/packages/dd-trace/src/llmobs/experiments/index.js +33 -25
- package/packages/dd-trace/src/llmobs/experiments/util.js +4 -2
- package/packages/dd-trace/src/llmobs/noop.js +2 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +5 -5
- package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +1 -1
- package/packages/dd-trace/src/llmobs/sdk.js +149 -68
- package/packages/dd-trace/src/llmobs/telemetry.js +18 -0
- package/packages/dd-trace/src/llmobs/writers/base.js +3 -0
- package/packages/dd-trace/src/llmobs/writers/util.js +2 -0
- package/packages/dd-trace/src/log/channels.js +1 -9
- package/packages/dd-trace/src/log/levels.js +12 -0
- package/packages/dd-trace/src/openfeature/writers/exposures.js +1 -1
- package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
- package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +1 -1
- package/packages/dd-trace/src/opentelemetry/metrics/meter.js +8 -4
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -1
- package/packages/dd-trace/src/opentelemetry/span.js +4 -0
- package/packages/dd-trace/src/plugins/ci_plugin.js +21 -5
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/util/ci.js +6 -0
- package/packages/dd-trace/src/plugins/util/git.js +6 -4
- package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +2 -2
- package/packages/dd-trace/src/plugins/util/ip_extractor.js +1 -1
- package/packages/dd-trace/src/plugins/util/stacktrace.js +3 -0
- package/packages/dd-trace/src/plugins/util/test.js +319 -75
- package/packages/dd-trace/src/plugins/util/url.js +1 -1
- package/packages/dd-trace/src/plugins/util/web.js +23 -1
- package/packages/dd-trace/src/profiler.js +1 -1
- package/packages/dd-trace/src/profiling/exporters/event_serializer.js +1 -1
- package/packages/dd-trace/src/profiling/profilers/events.js +2 -2
- package/packages/dd-trace/src/proxy.js +1 -0
- package/packages/dd-trace/src/ritm.js +5 -0
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +4 -2
- package/packages/dd-trace/src/sampling_rule.js +4 -2
- package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +1 -1
- package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +1 -1
- package/packages/dd-trace/src/telemetry/logs/index.js +3 -0
- package/packages/dd-trace/src/telemetry/telemetry.js +1 -1
- package/vendor/dist/pprof-format/index.js +1 -1
- package/version.js +10 -8
- /package/packages/dd-trace/src/appsec/{blocked_templates.js → blocking/templates.js} +0 -0
|
@@ -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 }
|
|
@@ -91,14 +91,16 @@ function cleanupGeneratedFiles (manifest, { keep = false } = {}) {
|
|
|
91
91
|
filesRemoved: 0,
|
|
92
92
|
filesRetained: 0,
|
|
93
93
|
}
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
94
|
+
if (manifest.frameworks) {
|
|
95
|
+
for (const framework of manifest.frameworks) {
|
|
96
|
+
const strategy = framework.generatedTestStrategy
|
|
97
|
+
addCleanupOutcome(
|
|
98
|
+
outcome,
|
|
99
|
+
cleanupPaths(getSafeCleanupPaths(framework, strategy, { includeGeneratedFiles: true })),
|
|
100
|
+
'files'
|
|
101
|
+
)
|
|
102
|
+
addCleanupOutcome(outcome, cleanupCreatedDirectories(framework.project.root), 'directories')
|
|
103
|
+
}
|
|
102
104
|
}
|
|
103
105
|
outcome.status = outcome.filesRetained > 0 || outcome.directoriesRetained > 0
|
|
104
106
|
? 'incomplete'
|
|
@@ -168,13 +170,15 @@ function initializeRuntimeCleanupFiles (framework, strategy) {
|
|
|
168
170
|
if (initializedCleanupStrategies.has(strategy)) return
|
|
169
171
|
|
|
170
172
|
const generatedFiles = new Set((strategy.files || []).map(file => validateGeneratedFilePath(framework, file.path)))
|
|
171
|
-
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
173
|
+
if (strategy.cleanupPaths) {
|
|
174
|
+
for (const cleanupPath of strategy.cleanupPaths) {
|
|
175
|
+
const filename = validateCleanupPath(framework, cleanupPath)
|
|
176
|
+
if (generatedFiles.has(filename) || isDirectory(filename) || !isNamespacedRuntimeFile(filename)) continue
|
|
177
|
+
if (fs.existsSync(filename)) {
|
|
178
|
+
throw new Error(`Refusing to delete pre-existing generated validation runtime file: ${filename}`)
|
|
179
|
+
}
|
|
180
|
+
authorizedRuntimeCleanupFiles.set(filename, authorizePathForCleanup(framework.project.root, filename))
|
|
176
181
|
}
|
|
177
|
-
authorizedRuntimeCleanupFiles.set(filename, authorizePathForCleanup(framework.project.root, filename))
|
|
178
182
|
}
|
|
179
183
|
initializedCleanupStrategies.add(strategy)
|
|
180
184
|
}
|
|
@@ -183,20 +187,24 @@ function getSafeCleanupPaths (framework, strategy, { includeGeneratedFiles }) {
|
|
|
183
187
|
if (!strategy) return []
|
|
184
188
|
|
|
185
189
|
const generatedFiles = new Set()
|
|
186
|
-
|
|
187
|
-
|
|
190
|
+
if (strategy.files) {
|
|
191
|
+
for (const file of strategy.files) {
|
|
192
|
+
generatedFiles.add(validateGeneratedFilePath(framework, file.path))
|
|
193
|
+
}
|
|
188
194
|
}
|
|
189
195
|
|
|
190
196
|
const cleanupPaths = []
|
|
191
|
-
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
if (
|
|
195
|
-
|
|
196
|
-
|
|
197
|
+
if (strategy.cleanupPaths) {
|
|
198
|
+
for (const cleanupPath of strategy.cleanupPaths) {
|
|
199
|
+
const filename = validateCleanupPath(framework, cleanupPath)
|
|
200
|
+
if (generatedFiles.has(filename)) {
|
|
201
|
+
if (includeGeneratedFiles && writtenGeneratedFiles.has(filename)) cleanupPaths.push(filename)
|
|
202
|
+
continue
|
|
203
|
+
}
|
|
197
204
|
|
|
198
|
-
|
|
199
|
-
|
|
205
|
+
if (authorizedRuntimeCleanupFiles.has(filename)) {
|
|
206
|
+
cleanupPaths.push(filename)
|
|
207
|
+
}
|
|
200
208
|
}
|
|
201
209
|
}
|
|
202
210
|
|
|
@@ -264,10 +272,12 @@ function authorizePathForCleanup (root, filename) {
|
|
|
264
272
|
}
|
|
265
273
|
|
|
266
274
|
function pinRuntimeCleanupParents (strategy) {
|
|
267
|
-
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
275
|
+
if (strategy.cleanupPaths) {
|
|
276
|
+
for (const cleanupPath of strategy.cleanupPaths) {
|
|
277
|
+
const authorization = authorizedRuntimeCleanupFiles.get(path.resolve(cleanupPath))
|
|
278
|
+
if (authorization && authorization.physicalParent === undefined) {
|
|
279
|
+
pinCleanupParent(authorization, cleanupPath)
|
|
280
|
+
}
|
|
271
281
|
}
|
|
272
282
|
}
|
|
273
283
|
}
|
|
@@ -79,7 +79,7 @@ function getGeneratedTestContent ({ framework, moduleSystem, scenarioId, stateFi
|
|
|
79
79
|
const requireCall = 'require'
|
|
80
80
|
imports.push(moduleSystem === 'esm'
|
|
81
81
|
? "import { describe, expect, test } from '@jest/globals'"
|
|
82
|
-
: `const
|
|
82
|
+
: `const jestGlobals = ${requireCall}('@jest/globals')`)
|
|
83
83
|
}
|
|
84
84
|
if (framework === 'vitest' && moduleSystem === 'esm') {
|
|
85
85
|
imports.push("import { describe, expect, it } from 'vitest'")
|
|
@@ -97,8 +97,12 @@ function getGeneratedTestContent ({ framework, moduleSystem, scenarioId, stateFi
|
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
const
|
|
101
|
-
const
|
|
100
|
+
const commonJsJest = framework === 'jest' && moduleSystem === 'commonjs'
|
|
101
|
+
const assertion = framework === 'mocha'
|
|
102
|
+
? 'assert.equal(1, 1)'
|
|
103
|
+
: `${commonJsJest ? 'jestGlobals.' : ''}expect(true).toBe(true)`
|
|
104
|
+
const testFunction = framework === 'jest' ? `${commonJsJest ? 'jestGlobals.' : ''}test` : 'it'
|
|
105
|
+
const describeFunction = commonJsJest ? 'jestGlobals.describe' : 'describe'
|
|
102
106
|
const body = scenarioId === 'atr-fail-once'
|
|
103
107
|
? getAtrBody({ moduleSystem, assertion, stateFile })
|
|
104
108
|
: ` ${assertion}`
|
|
@@ -106,7 +110,7 @@ function getGeneratedTestContent ({ framework, moduleSystem, scenarioId, stateFi
|
|
|
106
110
|
return [
|
|
107
111
|
...imports,
|
|
108
112
|
imports.length > 0 ? '' : undefined,
|
|
109
|
-
|
|
113
|
+
`${describeFunction}('dd-test-optimization-validation', () => {`,
|
|
110
114
|
` ${testFunction}('${scenario.testName}', () => {`,
|
|
111
115
|
body,
|
|
112
116
|
' })',
|