dd-trace 6.7.0 → 6.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +14 -8
- 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/playwright.js +5 -5
- package/ci/test-optimization-validation/framework-adapters/vitest.js +517 -0
- package/ci/test-optimization-validation/generated-files.js +47 -37
- package/ci/test-optimization-validation/generated-test-contract.js +9 -7
- 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 +301 -52
- 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/offline-output.js +6 -6
- 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 +41 -21
- package/ci/test-optimization-validation/runner-contract.js +79 -43
- package/ci/test-optimization-validation/safe-files.js +4 -4
- package/ci/test-optimization-validation/scenarios/basic-reporting.js +60 -6
- 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/scenarios/test-management.js +2 -2
- 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 +377 -84
- package/index.d.ts +175 -14
- package/init.js +1 -17
- package/initialize.mjs +11 -0
- package/loader-hook.mjs +6 -4
- package/package.json +12 -4
- package/packages/datadog-core/src/utils/src/kebabcase.js +8 -2
- package/packages/datadog-core/src/utils/src/parse-tags.js +0 -1
- package/packages/datadog-esbuild/index.js +4 -2
- package/packages/datadog-esbuild/src/utils.js +17 -2
- 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 +85 -9
- package/packages/datadog-instrumentations/src/cucumber.js +131 -51
- 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/extract-package-and-module-path.js +10 -0
- package/packages/datadog-instrumentations/src/helpers/hook.js +19 -2
- package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +9 -3
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +34 -9
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +175 -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/rewriter/transforms.js +50 -18
- package/packages/datadog-instrumentations/src/helpers/router-helper.js +38 -7
- package/packages/datadog-instrumentations/src/helpers/shared-utils.js +39 -0
- package/packages/datadog-instrumentations/src/http/client.js +1 -3
- package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +1 -1
- package/packages/datadog-instrumentations/src/jest.js +986 -246
- package/packages/datadog-instrumentations/src/mocha/common.js +1 -3
- package/packages/datadog-instrumentations/src/mocha/main.js +109 -51
- package/packages/datadog-instrumentations/src/mocha/utils.js +120 -32
- package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +44 -0
- package/packages/datadog-instrumentations/src/mocha/worker.js +441 -43
- package/packages/datadog-instrumentations/src/moleculer/client.js +1 -1
- 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 +176 -81
- package/packages/datadog-instrumentations/src/promise-js.js +4 -4
- package/packages/datadog-instrumentations/src/promise.js +3 -3
- package/packages/datadog-instrumentations/src/rhea.js +1 -1
- package/packages/datadog-instrumentations/src/router.js +195 -19
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +567 -65
- package/packages/datadog-instrumentations/src/vitest-main.js +438 -54
- 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 +1090 -0
- package/packages/datadog-instrumentations/src/when.js +3 -3
- package/packages/datadog-instrumentations/src/ws.js +5 -1
- package/packages/datadog-plugin-aerospike/src/index.js +1 -3
- package/packages/datadog-plugin-amqplib/src/client.js +1 -1
- package/packages/datadog-plugin-amqplib/src/producer.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +59 -48
- package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +2 -2
- package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +2 -2
- package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -3
- package/packages/datadog-plugin-azure-service-bus/src/producer.js +5 -3
- package/packages/datadog-plugin-bullmq/src/producer.js +3 -3
- package/packages/datadog-plugin-child_process/src/index.js +3 -3
- package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +6 -11
- 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/source-map-utils.js +1 -1
- package/packages/datadog-plugin-cypress/src/support.js +7 -4
- package/packages/datadog-plugin-electron/src/net.js +10 -4
- package/packages/datadog-plugin-fastify/src/tracing.js +1 -1
- package/packages/datadog-plugin-fs/src/index.js +0 -4
- package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +14 -7
- package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +19 -10
- package/packages/datadog-plugin-graphql/src/execute.js +1 -0
- package/packages/datadog-plugin-graphql/src/tools/signature.js +1 -1
- package/packages/datadog-plugin-http/src/client.js +3 -2
- package/packages/datadog-plugin-http2/src/client.js +1 -1
- package/packages/datadog-plugin-jest/src/index.js +3 -21
- package/packages/datadog-plugin-jest/src/util.js +4 -1
- package/packages/datadog-plugin-kafkajs/src/producer.js +2 -1
- package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
- package/packages/datadog-plugin-mocha/src/index.js +342 -6
- 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-prisma/src/index.js +5 -1
- package/packages/datadog-plugin-rhea/src/consumer.js +16 -13
- package/packages/datadog-plugin-rhea/src/producer.js +1 -1
- package/packages/datadog-plugin-undici/src/index.js +2 -1
- package/packages/datadog-plugin-vitest/src/index.js +179 -18
- package/packages/datadog-shimmer/src/shimmer.js +6 -2
- package/packages/datadog-webpack/index.js +2 -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/downstream_requests.js +1 -1
- 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/hardcoded-base-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/hsts-header-missing-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +1 -3
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +2 -2
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +2 -2
- package/packages/dd-trace/src/appsec/index.js +48 -476
- package/packages/dd-trace/src/appsec/sdk/track_event.js +10 -1
- package/packages/dd-trace/src/appsec/waf/waf_manager.js +1 -1
- 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-validation/sink.js +7 -6
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +29 -15
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +19 -1
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +25 -1
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/webdriverio.js +26 -0
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -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/request.js +3 -3
- 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 +2 -2
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +5 -1
- package/packages/dd-trace/src/config/defaults.js +14 -6
- package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -2
- package/packages/dd-trace/src/config/helper.js +2 -0
- package/packages/dd-trace/src/config/index.js +31 -5
- package/packages/dd-trace/src/config/parsers.js +19 -1
- package/packages/dd-trace/src/config/remote_config.js +1 -1
- package/packages/dd-trace/src/config/supported-configurations.json +13 -6
- package/packages/dd-trace/src/datastreams/encoding.js +3 -4
- package/packages/dd-trace/src/debugger/devtools_client/log.js +2 -1
- package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +1 -4
- package/packages/dd-trace/src/debugger/devtools_client/send.js +1 -2
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +2 -2
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +16 -2
- 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/encode/tags-processors.js +2 -2
- package/packages/dd-trace/src/exporters/common/url.js +3 -5
- package/packages/dd-trace/src/exporters/electron/index.js +4 -1
- package/packages/dd-trace/src/external-logger/src/index.js +1 -2
- package/packages/dd-trace/src/flare/index.js +2 -2
- 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/id.js +1 -1
- package/packages/dd-trace/src/llmobs/constants/tags.js +1 -1
- package/packages/dd-trace/src/llmobs/eval-metric.js +249 -0
- package/packages/dd-trace/src/llmobs/experiments/client.js +4 -2
- package/packages/dd-trace/src/llmobs/experiments/dataset.js +66 -14
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +289 -95
- package/packages/dd-trace/src/llmobs/experiments/index.js +66 -19
- package/packages/dd-trace/src/llmobs/experiments/noop.js +95 -10
- package/packages/dd-trace/src/llmobs/experiments/result.js +13 -2
- package/packages/dd-trace/src/llmobs/experiments/util.js +220 -0
- 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/anthropic/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +5 -5
- package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +1 -1
- package/packages/dd-trace/src/llmobs/sdk.js +152 -78
- package/packages/dd-trace/src/llmobs/span_processor.js +9 -7
- package/packages/dd-trace/src/llmobs/tagger.js +1 -2
- 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/spans.js +12 -6
- 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/noop/proxy.js +2 -2
- 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/otlp_transformer.js +1 -1
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +3 -2
- package/packages/dd-trace/src/opentelemetry/span.js +4 -0
- package/packages/dd-trace/src/opentracing/span.js +1 -1
- package/packages/dd-trace/src/opentracing/span_context.js +1 -1
- package/packages/dd-trace/src/plugins/ci_plugin.js +124 -23
- package/packages/dd-trace/src/plugins/index.js +2 -0
- package/packages/dd-trace/src/plugins/plugin.js +1 -1
- package/packages/dd-trace/src/plugins/tracing.js +1 -0
- package/packages/dd-trace/src/plugins/util/ci.js +13 -6
- package/packages/dd-trace/src/plugins/util/git.js +7 -5
- package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +3 -3
- package/packages/dd-trace/src/plugins/util/ip_extractor.js +1 -1
- package/packages/dd-trace/src/plugins/util/llm.js +2 -2
- package/packages/dd-trace/src/plugins/util/stacktrace.js +3 -0
- package/packages/dd-trace/src/plugins/util/tags.js +2 -0
- package/packages/dd-trace/src/plugins/util/test.js +356 -86
- package/packages/dd-trace/src/plugins/util/url.js +5 -5
- package/packages/dd-trace/src/plugins/util/user-provided-git.js +1 -1
- package/packages/dd-trace/src/plugins/util/web.js +24 -2
- package/packages/dd-trace/src/priority_sampler.js +6 -3
- 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/profiling/profilers/poisson.js +1 -1
- package/packages/dd-trace/src/profiling/profilers/shared.js +2 -2
- package/packages/dd-trace/src/proxy.js +1 -0
- package/packages/dd-trace/src/ritm.js +10 -20
- 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/startup-log.js +3 -0
- package/packages/dd-trace/src/telemetry/logs/index.js +3 -0
- package/packages/dd-trace/src/telemetry/logs/log-collector.js +3 -1
- package/packages/dd-trace/src/telemetry/telemetry.js +1 -1
- package/packages/dd-trace/src/util.js +16 -0
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +12 -12
- package/vendor/dist/pprof-format/index.js +1 -1
- package/vendor/dist/source-map/index.js +1 -1
- package/version.js +10 -8
- /package/packages/dd-trace/src/appsec/{blocked_templates.js → blocking/templates.js} +0 -0
|
@@ -4,9 +4,12 @@ const fs = require('node:fs')
|
|
|
4
4
|
const path = require('node:path')
|
|
5
5
|
|
|
6
6
|
const { runDiagnosis } = require('../diagnose')
|
|
7
|
+
const { BLOCKER_CATEGORIES } = require('./blocker-category')
|
|
7
8
|
const cucumber = require('./framework-adapters/cucumber')
|
|
8
9
|
const cypress = require('./framework-adapters/cypress')
|
|
9
10
|
const playwright = require('./framework-adapters/playwright')
|
|
11
|
+
const vitest = require('./framework-adapters/vitest')
|
|
12
|
+
const { isProjectBuildArtifactPath } = require('./project-build-artifact')
|
|
10
13
|
const {
|
|
11
14
|
GENERATED_SCENARIOS,
|
|
12
15
|
getGeneratedRetryStatePath,
|
|
@@ -15,6 +18,7 @@ const {
|
|
|
15
18
|
const { validateManifest } = require('./manifest-schema')
|
|
16
19
|
const {
|
|
17
20
|
getProjectNodeRunner,
|
|
21
|
+
getRunnerConfigurationContract,
|
|
18
22
|
getRunnerContract,
|
|
19
23
|
getRunnerSearchRoots,
|
|
20
24
|
} = require('./runner-contract')
|
|
@@ -54,7 +58,7 @@ const CONFIG_PATTERNS = {
|
|
|
54
58
|
jest: /^(?:jest|config-jest)\.config\.(?:[cm]?[jt]s|json)$/,
|
|
55
59
|
mocha: /^\.mocharc\.(?:json|ya?ml|[cm]?js)$/,
|
|
56
60
|
playwright: playwright.CONFIG_PATTERN,
|
|
57
|
-
vitest: /^(?:vite|vitest
|
|
61
|
+
vitest: /^(?:vite\.config|vitest\.(?:config|workspace))\.[cm]?[jt]s$/,
|
|
58
62
|
}
|
|
59
63
|
const JS_CONFIG_EXTENSIONS = ['js', 'cjs', 'mjs', 'ts', 'cts', 'mts']
|
|
60
64
|
const IMPLICIT_CONFIG_FILENAMES = {
|
|
@@ -72,14 +76,20 @@ const IMPLICIT_CONFIG_FILENAMES = {
|
|
|
72
76
|
vitest: JS_CONFIG_EXTENSIONS.flatMap(extension => [
|
|
73
77
|
`vite.config.${extension}`,
|
|
74
78
|
`vitest.config.${extension}`,
|
|
79
|
+
`vitest.workspace.${extension}`,
|
|
75
80
|
]),
|
|
76
81
|
}
|
|
77
82
|
const TEST_FILE_PATTERN = /^.+[._-](?:test|spec)\.[cm]?[jt]sx?$/
|
|
78
83
|
const BARE_TEST_FILE_PATTERN = /^test\.[cm]?[jt]sx?$/
|
|
79
84
|
const TYPE_ONLY_TEST_PATTERN = /\.(?:test|spec)-d\.[cm]?tsx?$|\.d\.[cm]?ts$/i
|
|
80
|
-
const TYPE_ONLY_DIRECTORY_PATTERN = /(?:^|\/)(?:type[-_]?tests?|
|
|
85
|
+
const TYPE_ONLY_DIRECTORY_PATTERN = /(?:^|\/)(?:type[-_]?tests?|test-dts?)(?:\/|$)/i
|
|
81
86
|
const LOCAL_SOCKET_PATTERN =
|
|
82
|
-
|
|
87
|
+
/\bcreateServer\s*\(|\.listen\s*\(|\b(?:localhost|127\.0\.0\.1|supertest)\b|\bcy\.(?:visit|request)\s*\(/
|
|
88
|
+
const IMPORT_SPECIFIER_PATTERN = /(?:from\s+|require\s*\(\s*|import\s*\(\s*)(['"])([^'"]+)\1/g
|
|
89
|
+
const CUCUMBER_BROWSER_SUPPORT_DIRECTORY_PATTERN =
|
|
90
|
+
/(?:^|\/)(?:features?|helpers?|step_definitions|steps?|support)(?:\/|$)/i
|
|
91
|
+
const CUCUMBER_BROWSER_DRIVER_PATTERN =
|
|
92
|
+
/(?:from\s+|(?:import|require)\s*\(\s*)['"](?:@playwright\/test|nightwatch|playwright(?:-core)?|puppeteer(?:-core)?|selenium-webdriver|webdriverio)['"]/
|
|
83
93
|
const CYPRESS_LOCAL_ORIGIN_PATTERN =
|
|
84
94
|
/\bcy\.(?:visit|request)\s*\([\s\S]{0,512}\b(?:https?:\/\/)?(?:localhost|127\.0\.0\.1)(?=[:/'"`\s}])/i
|
|
85
95
|
|
|
@@ -121,7 +131,11 @@ function createManifestScaffold ({ root, frameworks = new Set() }) {
|
|
|
121
131
|
ciDiscovery,
|
|
122
132
|
frameworks: [
|
|
123
133
|
...eligible.map(detection => buildFramework(repositoryRoot, detection, ciDiscovery)),
|
|
124
|
-
...detectedOnly.map(detection =>
|
|
134
|
+
...detectedOnly.map(detection => {
|
|
135
|
+
return detection.supportedVersion && SUPPORTED_FRAMEWORKS.has(detection.id)
|
|
136
|
+
? buildFramework(repositoryRoot, detection, ciDiscovery)
|
|
137
|
+
: buildDetectedOnlyFramework(repositoryRoot, detection, ciDiscovery)
|
|
138
|
+
}),
|
|
125
139
|
...unsupported.map(detection => buildUnsupportedFramework(repositoryRoot, detection, ciDiscovery)),
|
|
126
140
|
],
|
|
127
141
|
omitted: [],
|
|
@@ -144,7 +158,15 @@ function createManifestScaffold ({ root, frameworks = new Set() }) {
|
|
|
144
158
|
*/
|
|
145
159
|
function buildFramework (repositoryRoot, detection, ciDiscovery) {
|
|
146
160
|
const framework = detection.id
|
|
147
|
-
const packageJson =
|
|
161
|
+
const packageJson = getFrameworkPackageJson(repositoryRoot, detection)
|
|
162
|
+
if (!packageJson) {
|
|
163
|
+
return buildDetectedOnlyFramework(
|
|
164
|
+
repositoryRoot,
|
|
165
|
+
detection,
|
|
166
|
+
ciDiscovery,
|
|
167
|
+
`The package that owns ${detection.name} could not be identified from the bounded detection evidence.`
|
|
168
|
+
)
|
|
169
|
+
}
|
|
148
170
|
const projectRoot = path.dirname(packageJson.path)
|
|
149
171
|
const base = getFrameworkBase({
|
|
150
172
|
ciDiscovery,
|
|
@@ -158,16 +180,32 @@ function buildFramework (repositoryRoot, detection, ciDiscovery) {
|
|
|
158
180
|
if (!SUPPORTED_FRAMEWORKS.has(framework)) {
|
|
159
181
|
return {
|
|
160
182
|
...base,
|
|
183
|
+
blockerCategory: BLOCKER_CATEGORIES.VALIDATOR_LIMITATION,
|
|
161
184
|
status: 'unsupported_by_validator',
|
|
162
185
|
notes: [`${detection.name} is supported by dd-trace, but this validator has no direct-runner adapter.`],
|
|
163
186
|
}
|
|
164
187
|
}
|
|
165
188
|
|
|
189
|
+
if (framework === 'cucumber' && !cucumber.supportsConfigIsolation(base.frameworkVersion)) {
|
|
190
|
+
return {
|
|
191
|
+
...base,
|
|
192
|
+
blockerCategory: BLOCKER_CATEGORIES.VALIDATOR_LIMITATION,
|
|
193
|
+
status: 'requires_manual_setup',
|
|
194
|
+
notes: [
|
|
195
|
+
`${detection.name} ${base.frameworkVersion || 'with an unknown version'} is supported by dd-trace, but its ` +
|
|
196
|
+
'CLI cannot bypass customer profiles with a validator-owned config. Live validation is unavailable because ' +
|
|
197
|
+
'changing the working directory or loading the profile dynamically would weaken isolation. The static CI ' +
|
|
198
|
+
'audit can still run.',
|
|
199
|
+
],
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
166
203
|
const projectRunner = getProjectNodeRunner(detection.command, projectRoot, repositoryRoot)
|
|
167
204
|
const runner = projectRunner || resolveRunner(framework, projectRoot, repositoryRoot)
|
|
168
205
|
if (!runner) {
|
|
169
206
|
return {
|
|
170
207
|
...base,
|
|
208
|
+
blockerCategory: BLOCKER_CATEGORIES.PROJECT_SETUP_REQUIRED,
|
|
171
209
|
status: 'requires_manual_setup',
|
|
172
210
|
notes: [
|
|
173
211
|
`${detection.name} is detected, but its repository-contained executable is unavailable. Install this ` +
|
|
@@ -176,10 +214,13 @@ function buildFramework (repositoryRoot, detection, ciDiscovery) {
|
|
|
176
214
|
}
|
|
177
215
|
}
|
|
178
216
|
|
|
179
|
-
const
|
|
217
|
+
const projectFiles = collectProjectFiles(projectRoot)
|
|
218
|
+
const commandRoots = getRunnerSearchRoots(framework, detection.command, projectRoot, repositoryRoot)
|
|
219
|
+
let runnerContract = getRunnerContract(framework, detection.command, projectRoot, repositoryRoot)
|
|
180
220
|
if (runnerContract.error) {
|
|
181
221
|
return {
|
|
182
222
|
...base,
|
|
223
|
+
blockerCategory: BLOCKER_CATEGORIES.VALIDATOR_LIMITATION,
|
|
183
224
|
status: 'requires_manual_setup',
|
|
184
225
|
notes: [
|
|
185
226
|
`${detection.name} runner configuration was not retained because ${runnerContract.error}. ` +
|
|
@@ -187,23 +228,91 @@ function buildFramework (repositoryRoot, detection, ciDiscovery) {
|
|
|
187
228
|
],
|
|
188
229
|
}
|
|
189
230
|
}
|
|
190
|
-
const
|
|
191
|
-
|
|
231
|
+
const implicitConfigFiles = [...new Set([
|
|
232
|
+
...getImplicitConfigFiles(framework, projectRoot, repositoryRoot),
|
|
233
|
+
...(framework === 'vitest' && commandRoots[0]
|
|
234
|
+
? getImplicitConfigFiles(framework, commandRoots[0], repositoryRoot)
|
|
235
|
+
: []),
|
|
236
|
+
])]
|
|
237
|
+
if (framework === 'cucumber') {
|
|
238
|
+
const expanded = cucumber.expandProfiles({
|
|
239
|
+
configFiles: [...new Set([...runnerContract.inputFiles, ...implicitConfigFiles])],
|
|
240
|
+
projectFiles,
|
|
241
|
+
projectRoot,
|
|
242
|
+
runnerArgs: runnerContract.runnerArgs,
|
|
243
|
+
})
|
|
244
|
+
if (expanded.error) {
|
|
245
|
+
return {
|
|
246
|
+
...base,
|
|
247
|
+
blockerCategory: BLOCKER_CATEGORIES.VALIDATOR_LIMITATION,
|
|
248
|
+
status: 'requires_manual_setup',
|
|
249
|
+
notes: [
|
|
250
|
+
`${detection.name} runner configuration was not retained because ${expanded.error}. ` +
|
|
251
|
+
'Use a literal Cucumber profile or a direct command with bounded support-code options, then create a ' +
|
|
252
|
+
'fresh plan.',
|
|
253
|
+
],
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
const expandedContract = getRunnerConfigurationContract(
|
|
257
|
+
framework,
|
|
258
|
+
expanded.runnerArgs,
|
|
259
|
+
runnerContract.environment,
|
|
260
|
+
projectRoot,
|
|
261
|
+
repositoryRoot
|
|
262
|
+
)
|
|
263
|
+
if (expandedContract.error) {
|
|
264
|
+
return {
|
|
265
|
+
...base,
|
|
266
|
+
blockerCategory: BLOCKER_CATEGORIES.VALIDATOR_LIMITATION,
|
|
267
|
+
status: 'requires_manual_setup',
|
|
268
|
+
notes: [
|
|
269
|
+
`${detection.name} profile configuration was not retained because ${expandedContract.error}. ` +
|
|
270
|
+
'Use repository-contained support-code inputs, then create a fresh plan.',
|
|
271
|
+
],
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
runnerContract = {
|
|
275
|
+
...expandedContract,
|
|
276
|
+
omittedOptions: runnerContract.omittedOptions,
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
const vitestProject = framework === 'vitest'
|
|
280
|
+
? vitest.bindLiteralProject({
|
|
281
|
+
configFiles: [...new Set([...runnerContract.inputFiles, ...implicitConfigFiles])],
|
|
282
|
+
projectFiles,
|
|
283
|
+
projectRoot,
|
|
284
|
+
runnerArgs: runnerContract.runnerArgs,
|
|
285
|
+
})
|
|
286
|
+
: undefined
|
|
287
|
+
if (vitestProject?.error) {
|
|
288
|
+
return {
|
|
289
|
+
...base,
|
|
290
|
+
blockerCategory: BLOCKER_CATEGORIES.VALIDATOR_LIMITATION,
|
|
291
|
+
status: 'requires_manual_setup',
|
|
292
|
+
notes: [
|
|
293
|
+
`${detection.name} runner configuration was not retained because ${vitestProject.error}. ` +
|
|
294
|
+
'Use one literal, uniquely named Vitest project with static configuration, then create a fresh plan.',
|
|
295
|
+
],
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
const representativeRoot = vitestProject?.root ||
|
|
299
|
+
commandRoots[0] ||
|
|
300
|
+
findPreferredRepresentativeRoot(projectRoot, repositoryRoot)
|
|
192
301
|
const representativePackage = readJson(path.join(representativeRoot, 'package.json')) || packageJson.json
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
: collectProjectFiles(representativeRoot)
|
|
197
|
-
const candidate = selectRepresentativeTest(
|
|
302
|
+
const candidateFiles = vitestProject?.files ||
|
|
303
|
+
(representativeRoot === projectRoot ? projectFiles : collectProjectFiles(representativeRoot))
|
|
304
|
+
const candidates = selectRepresentativeTests(
|
|
198
305
|
candidateFiles,
|
|
199
306
|
framework,
|
|
200
307
|
representativeRoot,
|
|
201
308
|
representativePackage.name,
|
|
202
|
-
commandRoots.length > 0
|
|
309
|
+
commandRoots.length > 0 || vitestProject !== undefined
|
|
203
310
|
)
|
|
311
|
+
const candidate = candidates[0]
|
|
204
312
|
if (!candidate) {
|
|
205
313
|
return {
|
|
206
314
|
...base,
|
|
315
|
+
blockerCategory: BLOCKER_CATEGORIES.VALIDATOR_LIMITATION,
|
|
207
316
|
status: 'requires_manual_setup',
|
|
208
317
|
notes: [
|
|
209
318
|
`No single ${detection.name} test file could be selected confidently. Choose a normal framework-owned ` +
|
|
@@ -214,6 +323,7 @@ function buildFramework (repositoryRoot, detection, ciDiscovery) {
|
|
|
214
323
|
if (candidate.requiresExternalService) {
|
|
215
324
|
return {
|
|
216
325
|
...base,
|
|
326
|
+
blockerCategory: BLOCKER_CATEGORIES.PROJECT_SETUP_REQUIRED,
|
|
217
327
|
status: 'requires_manual_setup',
|
|
218
328
|
notes: [
|
|
219
329
|
`No self-contained ${detection.name} spec could be selected. The available spec accesses a localhost ` +
|
|
@@ -223,14 +333,23 @@ function buildFramework (repositoryRoot, detection, ciDiscovery) {
|
|
|
223
333
|
}
|
|
224
334
|
}
|
|
225
335
|
|
|
226
|
-
const
|
|
336
|
+
const plannedGeneratedTestStrategy = buildGeneratedTestStrategy({
|
|
227
337
|
framework,
|
|
228
|
-
projectRoot,
|
|
338
|
+
projectRoot: vitestProject?.root || projectRoot,
|
|
229
339
|
representative: candidate.path,
|
|
230
340
|
})
|
|
341
|
+
const generatedTestStrategy = vitestProject &&
|
|
342
|
+
!vitest.supportsGeneratedFiles(vitestProject, plannedGeneratedTestStrategy)
|
|
343
|
+
? {
|
|
344
|
+
reason: 'The selected Vitest project include or exclude patterns do not collect ' +
|
|
345
|
+
'validator-generated test files.',
|
|
346
|
+
status: 'not_possible',
|
|
347
|
+
}
|
|
348
|
+
: plannedGeneratedTestStrategy
|
|
231
349
|
const configFiles = [...new Set([
|
|
232
350
|
...runnerContract.inputFiles,
|
|
233
|
-
...
|
|
351
|
+
...(vitestProject?.configFile ? [vitestProject.configFile] : []),
|
|
352
|
+
...implicitConfigFiles,
|
|
234
353
|
...(representativeRoot === projectRoot
|
|
235
354
|
? []
|
|
236
355
|
: getImplicitConfigFiles(framework, representativeRoot, repositoryRoot)),
|
|
@@ -239,6 +358,7 @@ function buildFramework (repositoryRoot, detection, ciDiscovery) {
|
|
|
239
358
|
if (configFiles.length > 20) {
|
|
240
359
|
return {
|
|
241
360
|
...base,
|
|
361
|
+
blockerCategory: BLOCKER_CATEGORIES.VALIDATOR_LIMITATION,
|
|
242
362
|
status: 'requires_manual_setup',
|
|
243
363
|
notes: [
|
|
244
364
|
`${detection.name} loads more configuration files than the validator can approval-bind safely. ` +
|
|
@@ -247,13 +367,38 @@ function buildFramework (repositoryRoot, detection, ciDiscovery) {
|
|
|
247
367
|
}
|
|
248
368
|
}
|
|
249
369
|
const runnerDescription = projectRunner ? 'repository test wrapper' : `installed ${framework} runner`
|
|
370
|
+
const runnerConfigurationNote = detection.command
|
|
371
|
+
? 'The validator retained only allowlisted runner configuration from the detected package script.'
|
|
372
|
+
: 'The validator uses the installed runner with repository-contained configuration files; the unresolved ' +
|
|
373
|
+
'package wrapper is not executed.'
|
|
374
|
+
const cucumberBrowserRequired = framework === 'cucumber' &&
|
|
375
|
+
hasCucumberBrowserSupport(candidateFiles, runnerContract.inputFiles, representativeRoot)
|
|
250
376
|
const browserRequired = framework === 'cypress' ||
|
|
251
377
|
framework === 'playwright' ||
|
|
378
|
+
cucumberBrowserRequired ||
|
|
252
379
|
(framework === 'vitest' && runnerContract.runnerArgs.includes('--browser'))
|
|
380
|
+
const fallbackCandidates = candidates
|
|
381
|
+
.slice(1)
|
|
382
|
+
.filter(candidate => hasGeneratedTestContract(
|
|
383
|
+
candidate.path,
|
|
384
|
+
framework,
|
|
385
|
+
vitestProject?.root || projectRoot,
|
|
386
|
+
plannedGeneratedTestStrategy
|
|
387
|
+
))
|
|
388
|
+
const fallbackTests = fallbackCandidates
|
|
389
|
+
.map(candidate => ({
|
|
390
|
+
buildArtifactRequired: candidate.requiresBuildArtifact,
|
|
391
|
+
localSocketRequired: candidate.requiresLocalSocket,
|
|
392
|
+
testFile: candidate.path,
|
|
393
|
+
}))
|
|
394
|
+
const allCandidatesRequireLocalSocket =
|
|
395
|
+
[candidate, ...fallbackCandidates].every(candidate => candidate.requiresLocalSocket)
|
|
253
396
|
|
|
254
397
|
return {
|
|
255
398
|
...base,
|
|
399
|
+
allCandidatesRequireLocalSocket,
|
|
256
400
|
browserRequired,
|
|
401
|
+
buildArtifactRequired: candidate.requiresBuildArtifact,
|
|
257
402
|
language: /\.[cm]?tsx?$/.test(candidate.path) ? 'typescript' : 'javascript',
|
|
258
403
|
localSocketRequired: candidate.requiresLocalSocket,
|
|
259
404
|
status: 'runnable',
|
|
@@ -272,6 +417,7 @@ function buildFramework (repositoryRoot, detection, ciDiscovery) {
|
|
|
272
417
|
runnerArgs: runnerContract.runnerArgs,
|
|
273
418
|
selectorScope: projectRunner ? 'instrumented_event_identity' : 'bounded_direct_runner',
|
|
274
419
|
testFile: candidate.path,
|
|
420
|
+
...(fallbackTests.length > 0 ? { fallbackTests } : {}),
|
|
275
421
|
timeoutMs: browserRequired ? 300_000 : 180_000,
|
|
276
422
|
},
|
|
277
423
|
preflight: { status: 'pending' },
|
|
@@ -280,7 +426,7 @@ function buildFramework (repositoryRoot, detection, ciDiscovery) {
|
|
|
280
426
|
`Basic Reporting will invoke the ${runnerDescription} ` +
|
|
281
427
|
'directly for ' +
|
|
282
428
|
`${path.relative(repositoryRoot, candidate.path)}.`,
|
|
283
|
-
|
|
429
|
+
runnerConfigurationNote,
|
|
284
430
|
...(projectRunner
|
|
285
431
|
? [
|
|
286
432
|
'Basic Reporting will remain incomplete unless captured test events identify only the approved ' +
|
|
@@ -289,8 +435,20 @@ function buildFramework (repositoryRoot, detection, ciDiscovery) {
|
|
|
289
435
|
: []),
|
|
290
436
|
...(candidate.requiresLocalSocket
|
|
291
437
|
? [
|
|
292
|
-
'
|
|
293
|
-
'incomplete.',
|
|
438
|
+
`${allCandidatesRequireLocalSocket ? 'Every approved candidate appears' : 'The selected test appears'} ` +
|
|
439
|
+
'to require localhost. A restricted execution environment may leave local validation incomplete.',
|
|
440
|
+
]
|
|
441
|
+
: []),
|
|
442
|
+
...(candidate.requiresBuildArtifact
|
|
443
|
+
? [
|
|
444
|
+
'The selected test appears to load a build or dist artifact. Complete the project\'s normal build before ' +
|
|
445
|
+
'validation if that artifact is not already present.',
|
|
446
|
+
]
|
|
447
|
+
: []),
|
|
448
|
+
...(cucumberBrowserRequired
|
|
449
|
+
? [
|
|
450
|
+
'The selected Cucumber support code imports a browser driver. The approved validation command may launch ' +
|
|
451
|
+
'the project browser.',
|
|
294
452
|
]
|
|
295
453
|
: []),
|
|
296
454
|
],
|
|
@@ -299,15 +457,17 @@ function buildFramework (repositoryRoot, detection, ciDiscovery) {
|
|
|
299
457
|
|
|
300
458
|
function getImplicitConfigFiles (framework, projectRoot, repositoryRoot) {
|
|
301
459
|
const files = []
|
|
302
|
-
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
460
|
+
if (IMPLICIT_CONFIG_FILENAMES[framework]) {
|
|
461
|
+
for (const basename of IMPLICIT_CONFIG_FILENAMES[framework]) {
|
|
462
|
+
const filename = path.join(projectRoot, basename)
|
|
463
|
+
try {
|
|
464
|
+
const stat = fs.lstatSync(filename)
|
|
465
|
+
const physical = fs.realpathSync(filename)
|
|
466
|
+
if (stat.isFile() && !stat.isSymbolicLink() &&
|
|
467
|
+
fs.statSync(physical).isFile() &&
|
|
468
|
+
isPathInside(fs.realpathSync(repositoryRoot), physical)) files.push(physical)
|
|
469
|
+
} catch {}
|
|
470
|
+
}
|
|
311
471
|
}
|
|
312
472
|
return files
|
|
313
473
|
}
|
|
@@ -318,14 +478,17 @@ function getImplicitConfigFiles (framework, projectRoot, repositoryRoot) {
|
|
|
318
478
|
* @param {string} repositoryRoot repository root
|
|
319
479
|
* @param {object} detection supported framework detection
|
|
320
480
|
* @param {object} ciDiscovery bounded CI discovery result
|
|
481
|
+
* @param {string} [blockerNote] precise reason live validation is unavailable
|
|
321
482
|
* @returns {object} framework manifest entry
|
|
322
483
|
*/
|
|
323
|
-
function buildDetectedOnlyFramework (repositoryRoot, detection, ciDiscovery) {
|
|
324
|
-
const packageJson = getDetectionPackageJson(repositoryRoot, detection
|
|
484
|
+
function buildDetectedOnlyFramework (repositoryRoot, detection, ciDiscovery, blockerNote) {
|
|
485
|
+
const packageJson = getDetectionPackageJson(repositoryRoot, getDetectionPackageLocation(detection))
|
|
325
486
|
const projectRoot = path.dirname(packageJson.path)
|
|
326
487
|
const version = detection.supportedVersion?.version ||
|
|
327
488
|
detection.versionDetections?.[0]?.version ||
|
|
328
489
|
detection.versionDetections?.[0]?.rawVersion
|
|
490
|
+
const unsupportedVersion = !detection.supportedVersion &&
|
|
491
|
+
(detection.versionDetections?.[0]?.version || detection.versionDetections?.[0]?.rawVersion)
|
|
329
492
|
return {
|
|
330
493
|
...getFrameworkBase({
|
|
331
494
|
ciDiscovery,
|
|
@@ -335,11 +498,18 @@ function buildDetectedOnlyFramework (repositoryRoot, detection, ciDiscovery) {
|
|
|
335
498
|
projectRoot,
|
|
336
499
|
repositoryRoot,
|
|
337
500
|
}),
|
|
501
|
+
blockerCategory: unsupportedVersion
|
|
502
|
+
? BLOCKER_CATEGORIES.UNSUPPORTED_VERSION
|
|
503
|
+
: BLOCKER_CATEGORIES.VALIDATOR_LIMITATION,
|
|
338
504
|
status: 'detected_not_runnable',
|
|
339
505
|
notes: [
|
|
340
|
-
|
|
341
|
-
?
|
|
342
|
-
|
|
506
|
+
blockerNote || (unsupportedVersion
|
|
507
|
+
? `${detection.name} ${unsupportedVersion} was detected, but live validation requires ` +
|
|
508
|
+
`${detection.supportedRange}. Upgrade ${detection.name} through the project's normal dependency workflow ` +
|
|
509
|
+
'before creating a fresh validation plan.'
|
|
510
|
+
: detection.supportedVersion
|
|
511
|
+
? `A supported ${detection.name} installation was detected, but no project-local validation target was found.`
|
|
512
|
+
: `${detection.name} was detected, but no supported installed version was confirmed.`),
|
|
343
513
|
],
|
|
344
514
|
}
|
|
345
515
|
}
|
|
@@ -365,6 +535,7 @@ function buildUnsupportedFramework (repositoryRoot, detection, ciDiscovery) {
|
|
|
365
535
|
projectRoot,
|
|
366
536
|
repositoryRoot,
|
|
367
537
|
}),
|
|
538
|
+
blockerCategory: BLOCKER_CATEGORIES.VALIDATOR_LIMITATION,
|
|
368
539
|
status: 'unsupported_by_validator',
|
|
369
540
|
notes: [`${detection.name} is not supported by this Test Optimization validator.`],
|
|
370
541
|
}
|
|
@@ -477,8 +648,7 @@ function buildGeneratedTestStrategy ({ framework, projectRoot, representative })
|
|
|
477
648
|
const stepsFile = cucumber.getGeneratedStepsPath(testDirectory)
|
|
478
649
|
files.push({ path: stepsFile, contentLines: cucumber.getGeneratedStepsContent().split('\n') })
|
|
479
650
|
cleanupPaths.push(stepsFile)
|
|
480
|
-
}
|
|
481
|
-
if (framework === 'playwright') {
|
|
651
|
+
} else if (framework === 'playwright') {
|
|
482
652
|
const configFile = playwright.getGeneratedConfigPath(testDirectory)
|
|
483
653
|
files.push({ path: configFile, contentLines: playwright.getGeneratedConfigContent().split('\n') })
|
|
484
654
|
cleanupPaths.push(configFile)
|
|
@@ -499,17 +669,29 @@ function buildGeneratedTestStrategy ({ framework, projectRoot, representative })
|
|
|
499
669
|
}
|
|
500
670
|
}
|
|
501
671
|
|
|
672
|
+
function hasGeneratedTestContract (representative, framework, projectRoot, strategy) {
|
|
673
|
+
const convention = getGeneratedTestConvention(framework, representative, projectRoot)
|
|
674
|
+
return convention.fileExtension === strategy.fileExtension &&
|
|
675
|
+
convention.testDirectory === strategy.testDirectory &&
|
|
676
|
+
getModuleSystem(representative, projectRoot) === strategy.moduleSystem
|
|
677
|
+
}
|
|
678
|
+
|
|
502
679
|
/**
|
|
503
|
-
* Selects
|
|
680
|
+
* Selects up to three representative framework-owned test files.
|
|
504
681
|
*
|
|
505
682
|
* @param {string[]} files bounded project files
|
|
506
683
|
* @param {string} framework framework name
|
|
507
684
|
* @param {string} projectRoot project root
|
|
508
685
|
* @param {string} packageName project package name
|
|
509
|
-
* @param {boolean} allowDirectoryConvention whether a literal runner selector
|
|
510
|
-
* @returns {{
|
|
686
|
+
* @param {boolean} allowDirectoryConvention whether a literal runner selector owns this root
|
|
687
|
+
* @returns {Array<{
|
|
688
|
+
* path: string,
|
|
689
|
+
* requiresBuildArtifact: boolean,
|
|
690
|
+
* requiresExternalService: boolean,
|
|
691
|
+
* requiresLocalSocket: boolean
|
|
692
|
+
* }>} selected tests
|
|
511
693
|
*/
|
|
512
|
-
function
|
|
694
|
+
function selectRepresentativeTests (files, framework, projectRoot, packageName, allowDirectoryConvention) {
|
|
513
695
|
const candidates = []
|
|
514
696
|
for (const filename of files) {
|
|
515
697
|
const source = readText(filename)
|
|
@@ -521,18 +703,39 @@ function selectRepresentativeTest (files, framework, projectRoot, packageName, a
|
|
|
521
703
|
: getStaticTestCount(source) === 0) continue
|
|
522
704
|
if (!hasFrameworkOwnership(filename, source, framework, packageName)) continue
|
|
523
705
|
candidates.push({
|
|
706
|
+
explicitFilename: TEST_FILE_PATTERN.test(path.basename(filename)),
|
|
524
707
|
path: filename,
|
|
525
708
|
rank: getTestRank(filename, source, projectRoot),
|
|
709
|
+
requiresBuildArtifact: requiresProjectBuildArtifact(source, projectRoot, path.dirname(filename)),
|
|
526
710
|
requiresExternalService: framework === 'cypress' && CYPRESS_LOCAL_ORIGIN_PATTERN.test(source),
|
|
527
711
|
requiresLocalSocket: LOCAL_SOCKET_PATTERN.test(source),
|
|
528
712
|
})
|
|
529
713
|
}
|
|
530
714
|
candidates.sort((left, right) => {
|
|
531
715
|
return Number(left.requiresExternalService) - Number(right.requiresExternalService) ||
|
|
716
|
+
Number(right.explicitFilename) - Number(left.explicitFilename) ||
|
|
532
717
|
left.rank - right.rank ||
|
|
533
718
|
left.path.localeCompare(right.path)
|
|
534
719
|
})
|
|
535
|
-
return candidates
|
|
720
|
+
return candidates.slice(0, 3)
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
function requiresProjectBuildArtifact (source, projectRoot, sourceDirectory) {
|
|
724
|
+
for (const match of source.matchAll(IMPORT_SPECIFIER_PATTERN)) {
|
|
725
|
+
if (isProjectBuildArtifactPath(match[2], projectRoot, sourceDirectory)) return true
|
|
726
|
+
}
|
|
727
|
+
return false
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
function hasCucumberBrowserSupport (files, retainedInputs, projectRoot) {
|
|
731
|
+
const retained = new Set(retainedInputs)
|
|
732
|
+
for (const filename of new Set([...files, ...retained])) {
|
|
733
|
+
const relative = path.relative(projectRoot, filename).replaceAll('\\', '/')
|
|
734
|
+
if (!retained.has(filename) && !CUCUMBER_BROWSER_SUPPORT_DIRECTORY_PATTERN.test(relative)) continue
|
|
735
|
+
const source = readText(filename)
|
|
736
|
+
if (source !== undefined && CUCUMBER_BROWSER_DRIVER_PATTERN.test(source)) return true
|
|
737
|
+
}
|
|
738
|
+
return false
|
|
536
739
|
}
|
|
537
740
|
|
|
538
741
|
/**
|
|
@@ -542,7 +745,7 @@ function selectRepresentativeTest (files, framework, projectRoot, packageName, a
|
|
|
542
745
|
* @param {string} source candidate source
|
|
543
746
|
* @param {string} framework framework name
|
|
544
747
|
* @param {string} projectRoot project root
|
|
545
|
-
* @param {boolean} allowDirectoryConvention whether a literal runner selector
|
|
748
|
+
* @param {boolean} allowDirectoryConvention whether a literal runner selector owns this root
|
|
546
749
|
* @returns {boolean} whether the file is a candidate
|
|
547
750
|
*/
|
|
548
751
|
function isTestFile (filename, source, framework, projectRoot, allowDirectoryConvention) {
|
|
@@ -562,7 +765,7 @@ function isTestFile (filename, source, framework, projectRoot, allowDirectoryCon
|
|
|
562
765
|
return ['__tests__', 'spec', 'test', 'tests'].includes(directory)
|
|
563
766
|
})
|
|
564
767
|
if (BARE_TEST_FILE_PATTERN.test(basename)) {
|
|
565
|
-
return inTestDirectory || hasExplicitFrameworkImport(source, framework)
|
|
768
|
+
return (allowDirectoryConvention && inTestDirectory) || hasExplicitFrameworkImport(source, framework)
|
|
566
769
|
}
|
|
567
770
|
return allowDirectoryConvention && inTestDirectory && /\.[cm]?[jt]sx?$/.test(basename)
|
|
568
771
|
}
|
|
@@ -598,14 +801,14 @@ function hasExplicitFrameworkImport (source, framework) {
|
|
|
598
801
|
*/
|
|
599
802
|
function hasFrameworkOwnership (filename, source, framework, packageName) {
|
|
600
803
|
const normalized = filename.replaceAll('\\', '/').toLowerCase()
|
|
601
|
-
if (framework === 'cucumber') return
|
|
804
|
+
if (framework === 'cucumber') return /^[ \t]*(?:Feature|Rule):/m.test(source)
|
|
602
805
|
if (framework === 'cypress') return /\.cy\./.test(filename) || normalized.includes('/cypress/')
|
|
603
806
|
if (framework === 'playwright') {
|
|
604
807
|
return /@playwright\/test/.test(source) || normalized.includes('/playwright/') ||
|
|
605
808
|
normalized.includes('/e2e/')
|
|
606
809
|
}
|
|
607
|
-
if (framework === 'vitest') return /\bvitest\b/.test(source) ||
|
|
608
|
-
if (framework === 'jest') return /\bjest\b/.test(source) ||
|
|
810
|
+
if (framework === 'vitest') return /\bvitest\b/.test(source) || getStaticTestCount(source) > 0
|
|
811
|
+
if (framework === 'jest') return /\bjest\b/.test(source) || getStaticTestCount(source) > 0
|
|
609
812
|
if (framework === 'mocha') {
|
|
610
813
|
return /\bmocha\b/.test(source) || /\bdescribe\s*\(/.test(source) ||
|
|
611
814
|
packageName === 'mocha'
|
|
@@ -644,7 +847,7 @@ function getTestRank (filename, source, projectRoot) {
|
|
|
644
847
|
let rank = relative.split('/').length
|
|
645
848
|
if (/(?:^|\/)(?:test|tests|__tests__|spec)\//.test(relative)) rank -= 20
|
|
646
849
|
if (/(?:^|[._/-])unit(?:[._/-]|$)/.test(relative)) rank -= 15
|
|
647
|
-
if (/
|
|
850
|
+
if (/e2e|integration|acceptance|browser|conformance|fixtures?/.test(relative)) rank += 20
|
|
648
851
|
if (LOCAL_SOCKET_PATTERN.test(source)) rank += 30
|
|
649
852
|
rank += Math.min(getStaticTestCount(source), 50)
|
|
650
853
|
return rank
|
|
@@ -657,9 +860,12 @@ function getTestRank (filename, source, projectRoot) {
|
|
|
657
860
|
* @returns {number} approximate test declaration count
|
|
658
861
|
*/
|
|
659
862
|
function getStaticTestCount (source) {
|
|
660
|
-
|
|
863
|
+
const direct = [...source.matchAll(/\b(?:it|test)((?:\.[A-Za-z]+)*)\s*\(\s*(['"`])/g)]
|
|
661
864
|
.filter(match => !/\.(?:skip|todo)\b/.test(match[1]))
|
|
662
|
-
|
|
865
|
+
const parameterized = [...source.matchAll(
|
|
866
|
+
/\b(?:it|test)((?:\.[A-Za-z]+)*)\.each\s*(?:\([^()]*\)|`(?:\\[\s\S]|[^\\`])*`)((?:\.[A-Za-z]+)*)\s*\(\s*(['"`])/g
|
|
867
|
+
)].filter(match => !/\.(?:skip|todo)\b/.test(`${match[1]}${match[2]}`))
|
|
868
|
+
return direct.length + parameterized.length
|
|
663
869
|
}
|
|
664
870
|
|
|
665
871
|
/**
|
|
@@ -824,9 +1030,9 @@ function getTestExtension (framework, representative) {
|
|
|
824
1030
|
if (framework === 'cucumber') return '.feature'
|
|
825
1031
|
if (framework === 'cypress') return cypress.getTestExtension(representative)
|
|
826
1032
|
if (framework === 'playwright') return playwright.getTestExtension(representative)
|
|
827
|
-
const match = /(
|
|
1033
|
+
const match = /([.-](?:test|spec)\.[cm]?[jt]sx?)$/.exec(representative)
|
|
828
1034
|
if (match) return match[1]
|
|
829
|
-
const moduleExtension = /\.(
|
|
1035
|
+
const moduleExtension = /\.([cm]js|[cm]ts)$/.exec(representative)?.[1]
|
|
830
1036
|
return moduleExtension ? `.test.${moduleExtension}` : '.test.js'
|
|
831
1037
|
}
|
|
832
1038
|
|
|
@@ -900,7 +1106,7 @@ function rankCiReviewTargets (files) {
|
|
|
900
1106
|
*/
|
|
901
1107
|
function getCiRank (filename) {
|
|
902
1108
|
const value = filename.toLowerCase()
|
|
903
|
-
if (/
|
|
1109
|
+
if (/test|ci|build|unit|integration/.test(value)) return 0
|
|
904
1110
|
return 10
|
|
905
1111
|
}
|
|
906
1112
|
|
|
@@ -923,6 +1129,49 @@ function getDetectionPackageJson (repositoryRoot, location) {
|
|
|
923
1129
|
}
|
|
924
1130
|
}
|
|
925
1131
|
|
|
1132
|
+
function getFrameworkPackageJson (repositoryRoot, detection) {
|
|
1133
|
+
const preciseLocation = detection.commandLocation ||
|
|
1134
|
+
detection.supportedVersion?.relativePath ||
|
|
1135
|
+
detection.eligibleCommand?.relativePath ||
|
|
1136
|
+
detection.versionLocation
|
|
1137
|
+
if (preciseLocation) return findOwningPackageJson(repositoryRoot, preciseLocation)
|
|
1138
|
+
|
|
1139
|
+
const owners = new Map()
|
|
1140
|
+
if (detection.locations) {
|
|
1141
|
+
for (const location of detection.locations) {
|
|
1142
|
+
const owner = findOwningPackageJson(repositoryRoot, location)
|
|
1143
|
+
if (owner) owners.set(owner.path, owner)
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
if (owners.size === 1) return [...owners.values()][0]
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
function findOwningPackageJson (repositoryRoot, location) {
|
|
1150
|
+
let candidate = path.resolve(repositoryRoot, location)
|
|
1151
|
+
if (!isPathInside(repositoryRoot, candidate)) return
|
|
1152
|
+
try {
|
|
1153
|
+
if (!fs.statSync(candidate).isDirectory()) candidate = path.dirname(candidate)
|
|
1154
|
+
} catch {
|
|
1155
|
+
candidate = path.dirname(candidate)
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
while (isPathInside(repositoryRoot, candidate)) {
|
|
1159
|
+
const packageJson = path.join(candidate, 'package.json')
|
|
1160
|
+
const json = readJson(packageJson)
|
|
1161
|
+
if (json) return { json, path: packageJson }
|
|
1162
|
+
if (candidate === repositoryRoot) return
|
|
1163
|
+
candidate = path.dirname(candidate)
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
function getDetectionPackageLocation (detection) {
|
|
1168
|
+
return detection.commandLocation ||
|
|
1169
|
+
detection.supportedVersion?.relativePath ||
|
|
1170
|
+
detection.eligibleCommand?.relativePath ||
|
|
1171
|
+
detection.versionLocation ||
|
|
1172
|
+
detection.locations?.[0]
|
|
1173
|
+
}
|
|
1174
|
+
|
|
926
1175
|
/**
|
|
927
1176
|
* Safely reads a bounded directory.
|
|
928
1177
|
*
|