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
|
@@ -1,10 +1,97 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const fs = require('node:fs')
|
|
3
4
|
const path = require('node:path')
|
|
4
5
|
|
|
6
|
+
const { matchesLiteralGlob } = require('../literal-glob')
|
|
7
|
+
|
|
5
8
|
const CUCUMBER_PACKAGE = '@cucumber/cucumber'
|
|
6
9
|
const CONFIG_PATTERN = /^cucumber\.(?:[cm]?js|json|ya?ml)$/
|
|
10
|
+
const MAX_CONFIG_BYTES = 512 * 1024
|
|
7
11
|
const GENERATED_STEPS_FILENAME = 'dd-test-optimization-validation.steps.cjs'
|
|
12
|
+
const ISOLATION_CONFIG_PATH = path.join(__dirname, 'cucumber-validation.json')
|
|
13
|
+
const PROFILE_VALUE_OPTIONS = new Map([
|
|
14
|
+
['import', '--import'],
|
|
15
|
+
['language', '--language'],
|
|
16
|
+
['loader', '--loader'],
|
|
17
|
+
['parallel', '--parallel'],
|
|
18
|
+
['require', '--require'],
|
|
19
|
+
['requireModule', '--require-module'],
|
|
20
|
+
['worldParameters', '--world-parameters'],
|
|
21
|
+
])
|
|
22
|
+
const PROFILE_FLAG_OPTIONS = new Map([
|
|
23
|
+
['backtrace', '--backtrace'],
|
|
24
|
+
['failFast', '--fail-fast'],
|
|
25
|
+
['strict', '--strict'],
|
|
26
|
+
])
|
|
27
|
+
const OMITTED_PROFILE_KEYS = new Set([
|
|
28
|
+
'dryRun',
|
|
29
|
+
'forceExit',
|
|
30
|
+
'format',
|
|
31
|
+
'formatOptions',
|
|
32
|
+
'name',
|
|
33
|
+
'order',
|
|
34
|
+
'paths',
|
|
35
|
+
'publish',
|
|
36
|
+
'publishQuiet',
|
|
37
|
+
'retry',
|
|
38
|
+
'retryTagFilter',
|
|
39
|
+
'tags',
|
|
40
|
+
])
|
|
41
|
+
const OMITTED_PROFILE_OPTIONS = new Map([
|
|
42
|
+
['-f', true],
|
|
43
|
+
['--dry-run', false],
|
|
44
|
+
['--exit', false],
|
|
45
|
+
['--force-exit', false],
|
|
46
|
+
['--format', true],
|
|
47
|
+
['--format-options', true],
|
|
48
|
+
['--name', true],
|
|
49
|
+
['--order', true],
|
|
50
|
+
['--publish', false],
|
|
51
|
+
['--publish-quiet', false],
|
|
52
|
+
['--retry', true],
|
|
53
|
+
['--retry-tag-filter', true],
|
|
54
|
+
['--tags', true],
|
|
55
|
+
['-t', true],
|
|
56
|
+
])
|
|
57
|
+
const RETAINED_PROFILE_FLAGS = new Set(['--backtrace', '--fail-fast', '--no-strict', '--strict'])
|
|
58
|
+
const RETAINED_PROFILE_VALUES = new Set([
|
|
59
|
+
'-i',
|
|
60
|
+
'-r',
|
|
61
|
+
'--import',
|
|
62
|
+
'--language',
|
|
63
|
+
'--loader',
|
|
64
|
+
'--parallel',
|
|
65
|
+
'--require',
|
|
66
|
+
'--require-module',
|
|
67
|
+
'--world-parameters',
|
|
68
|
+
])
|
|
69
|
+
const JAVASCRIPT_STRING_ESCAPES = Object.freeze({
|
|
70
|
+
'"': '"',
|
|
71
|
+
"'": "'",
|
|
72
|
+
'/': '/',
|
|
73
|
+
'\\': '\\',
|
|
74
|
+
b: '\b',
|
|
75
|
+
f: '\f',
|
|
76
|
+
n: '\n',
|
|
77
|
+
r: '\r',
|
|
78
|
+
t: '\t',
|
|
79
|
+
v: '\v',
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Reports whether the installed Cucumber CLI can bypass customer profiles with a validator-owned config.
|
|
84
|
+
*
|
|
85
|
+
* Cucumber 7 always loads cucumber.js from its working directory and has no --config option. Changing the working
|
|
86
|
+
* directory would alter project test semantics, so those versions remain a validator limitation.
|
|
87
|
+
*
|
|
88
|
+
* @param {string|null|undefined} version installed Cucumber version
|
|
89
|
+
* @returns {boolean} whether validator-owned config isolation is supported
|
|
90
|
+
*/
|
|
91
|
+
function supportsConfigIsolation (version) {
|
|
92
|
+
const match = /^[v=]?(\d+)(?:\.|$)/.exec(String(version || ''))
|
|
93
|
+
return Boolean(match && Number(match[1]) >= 8)
|
|
94
|
+
}
|
|
8
95
|
|
|
9
96
|
/**
|
|
10
97
|
* Reports whether a file follows the Cucumber feature convention.
|
|
@@ -23,7 +110,7 @@ function isTestFile (filename) {
|
|
|
23
110
|
* @returns {number} declared scenario count
|
|
24
111
|
*/
|
|
25
112
|
function getScenarioCount (source) {
|
|
26
|
-
return [...source.matchAll(
|
|
113
|
+
return [...source.matchAll(/^[ \t]*(?:Example|Scenario(?: Outline| Template)?):[ \t]*\S/gm)].length
|
|
27
114
|
}
|
|
28
115
|
|
|
29
116
|
/**
|
|
@@ -77,10 +164,11 @@ function getGeneratedStepsPath (testDirectory) {
|
|
|
77
164
|
* Returns Cucumber arguments that select one existing feature.
|
|
78
165
|
*
|
|
79
166
|
* @param {string} filename selected Cucumber feature
|
|
167
|
+
* @param {string} cwd project working directory
|
|
80
168
|
* @returns {string[]} focused Cucumber arguments
|
|
81
169
|
*/
|
|
82
|
-
function getFocusedTestArgs (filename) {
|
|
83
|
-
return [filename, '--format', '
|
|
170
|
+
function getFocusedTestArgs (filename, cwd) {
|
|
171
|
+
return [...getIsolationArgs(cwd), filename, '--format', 'json']
|
|
84
172
|
}
|
|
85
173
|
|
|
86
174
|
/**
|
|
@@ -88,10 +176,11 @@ function getFocusedTestArgs (filename) {
|
|
|
88
176
|
*
|
|
89
177
|
* @param {string} filename generated Cucumber feature
|
|
90
178
|
* @param {string} stepsFile generated Cucumber step definitions
|
|
179
|
+
* @param {string} cwd project working directory
|
|
91
180
|
* @returns {string[]} generated scenario arguments
|
|
92
181
|
*/
|
|
93
|
-
function getGeneratedTestArgs (filename, stepsFile) {
|
|
94
|
-
return ['--require', stepsFile, '--format', '
|
|
182
|
+
function getGeneratedTestArgs (filename, stepsFile, cwd) {
|
|
183
|
+
return [...getIsolationArgs(cwd), '--require', stepsFile, '--format', 'json', filename]
|
|
95
184
|
}
|
|
96
185
|
|
|
97
186
|
/**
|
|
@@ -101,13 +190,575 @@ function getGeneratedTestArgs (filename, stepsFile) {
|
|
|
101
190
|
* @returns {number|null} final scenario count when present
|
|
102
191
|
*/
|
|
103
192
|
function getObservedTestCount (output) {
|
|
193
|
+
const jsonCount = getJsonScenarioCount(output)
|
|
194
|
+
if (jsonCount !== null) return jsonCount
|
|
195
|
+
|
|
104
196
|
let count = null
|
|
105
197
|
for (const match of output.matchAll(/\b(\d+)\s+scenarios?\b/gi)) count = Number(match[1])
|
|
106
198
|
return count
|
|
107
199
|
}
|
|
108
200
|
|
|
201
|
+
/**
|
|
202
|
+
* Expands selected Cucumber profiles without loading customer JavaScript.
|
|
203
|
+
*
|
|
204
|
+
* Profile feature paths, filters, retries, publishing, and formatters are intentionally omitted. The validator
|
|
205
|
+
* supplies one exact feature and a machine-readable formatter.
|
|
206
|
+
*
|
|
207
|
+
* @param {object} input profile inputs
|
|
208
|
+
* @param {string[]} input.configFiles bounded Cucumber config files
|
|
209
|
+
* @param {string} input.projectRoot project root
|
|
210
|
+
* @param {string[]} input.projectFiles bounded project files
|
|
211
|
+
* @param {string[]} input.runnerArgs retained command-line arguments
|
|
212
|
+
* @returns {{error?: string, runnerArgs: string[]}} expanded profile arguments
|
|
213
|
+
*/
|
|
214
|
+
function expandProfiles ({ configFiles, projectFiles, projectRoot, runnerArgs }) {
|
|
215
|
+
const selectedProfiles = getOptionValues(runnerArgs, new Set(['-p', '--profile']))
|
|
216
|
+
const explicitConfig = getOptionValues(runnerArgs, new Set(['--config']))[0]
|
|
217
|
+
const explicitConfigFile = explicitConfig && getPhysicalPath(path.resolve(projectRoot, explicitConfig))
|
|
218
|
+
const physicalProjectRoot = getPhysicalDirectory(projectRoot)
|
|
219
|
+
const configFile = explicitConfig
|
|
220
|
+
? configFiles.find(filename => path.resolve(filename) === explicitConfigFile)
|
|
221
|
+
: configFiles.find(filename =>
|
|
222
|
+
path.dirname(filename) === physicalProjectRoot && CONFIG_PATTERN.test(path.basename(filename)))
|
|
223
|
+
const directArgs = omitOptions(runnerArgs, new Set(['-p', '--config', '--profile']))
|
|
224
|
+
|
|
225
|
+
if (explicitConfig && !configFile) {
|
|
226
|
+
return {
|
|
227
|
+
error: `explicit Cucumber config ${JSON.stringify(explicitConfig)} is not an approval-bound regular file`,
|
|
228
|
+
runnerArgs: [],
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (!configFile) return expandSupportPaths(directArgs, projectFiles, projectRoot)
|
|
232
|
+
|
|
233
|
+
let definitions
|
|
234
|
+
try {
|
|
235
|
+
definitions = readProfileDefinitions(configFile)
|
|
236
|
+
} catch (error) {
|
|
237
|
+
return { error: `Cucumber configuration could not be expanded statically: ${error.message}`, runnerArgs: [] }
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const profiles = selectedProfiles.length > 0 ? selectedProfiles : ['default']
|
|
241
|
+
const expanded = []
|
|
242
|
+
for (const profile of profiles) {
|
|
243
|
+
const definition = definitions.get(profile)
|
|
244
|
+
if (definition === undefined) {
|
|
245
|
+
return {
|
|
246
|
+
error: `Cucumber profile ${JSON.stringify(profile)} is not a static literal in ${path.basename(configFile)}`,
|
|
247
|
+
runnerArgs: [],
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
const parsed = getProfileArgs(definition)
|
|
251
|
+
if (parsed.error) {
|
|
252
|
+
return {
|
|
253
|
+
error: `Cucumber profile ${JSON.stringify(profile)} ${parsed.error}`,
|
|
254
|
+
runnerArgs: [],
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
expanded.push(...parsed.args)
|
|
258
|
+
}
|
|
259
|
+
return expandSupportPaths([...expanded, ...directArgs], projectFiles, projectRoot)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function getIsolationArgs (cwd) {
|
|
263
|
+
return ['--config', path.relative(cwd, ISOLATION_CONFIG_PATH)]
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function readProfileDefinitions (filename) {
|
|
267
|
+
const stat = fs.lstatSync(filename)
|
|
268
|
+
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
269
|
+
throw new Error('configuration must be a regular non-symbolic-link file')
|
|
270
|
+
}
|
|
271
|
+
if (stat.size > MAX_CONFIG_BYTES) {
|
|
272
|
+
throw new Error(`configuration exceeds the ${MAX_CONFIG_BYTES}-byte limit`)
|
|
273
|
+
}
|
|
274
|
+
const source = fs.readFileSync(filename, 'utf8')
|
|
275
|
+
const extension = path.extname(filename)
|
|
276
|
+
if (extension === '.json') return getDataProfileDefinitions(JSON.parse(source))
|
|
277
|
+
if (extension === '.yaml' || extension === '.yml') {
|
|
278
|
+
return getYamlStringProfileDefinitions(source)
|
|
279
|
+
}
|
|
280
|
+
return getJavascriptProfileDefinitions(source)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function getJavascriptProfileDefinitions (source) {
|
|
284
|
+
const syntax = maskJavascriptCommentsAndStrings(source)
|
|
285
|
+
const exports = [...syntax.matchAll(/^\s*(?:module\s*\.\s*exports\s*=|export\s+default)\s*\{/gm)]
|
|
286
|
+
if (exports.length === 0) return new Map()
|
|
287
|
+
if (exports.length !== 1) throw new Error('configuration must export one literal profile object')
|
|
288
|
+
|
|
289
|
+
const objectStart = exports[0].index + exports[0][0].lastIndexOf('{')
|
|
290
|
+
const objectEnd = findClosingBrace(syntax, objectStart)
|
|
291
|
+
if (objectEnd === -1) throw new Error('configuration contains an unterminated profile object')
|
|
292
|
+
const definitions = new Map()
|
|
293
|
+
let index = objectStart + 1
|
|
294
|
+
while (index < objectEnd) {
|
|
295
|
+
index = skipWhitespace(syntax, index)
|
|
296
|
+
if (syntax[index] === ',') {
|
|
297
|
+
index++
|
|
298
|
+
continue
|
|
299
|
+
}
|
|
300
|
+
if (index >= objectEnd) break
|
|
301
|
+
|
|
302
|
+
let name
|
|
303
|
+
const quotedName = readQuotedString(source, index)
|
|
304
|
+
if (quotedName) {
|
|
305
|
+
name = quotedName.value
|
|
306
|
+
index = quotedName.end
|
|
307
|
+
} else {
|
|
308
|
+
const identifier = /^[A-Za-z_$][\w$]*/.exec(syntax.slice(index))
|
|
309
|
+
if (!identifier) throw new Error('profile names must be static literal properties')
|
|
310
|
+
name = identifier[0]
|
|
311
|
+
index += identifier[0].length
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
index = skipWhitespace(syntax, index)
|
|
315
|
+
if (syntax[index] !== ':') throw new Error(`profile ${JSON.stringify(name)} must have a literal value`)
|
|
316
|
+
index = skipWhitespace(syntax, index + 1)
|
|
317
|
+
const definition = readJavascriptLiteral(source, syntax, index)
|
|
318
|
+
if (!definition || (typeof definition.value !== 'string' &&
|
|
319
|
+
(!definition.value || typeof definition.value !== 'object' || Array.isArray(definition.value)))) {
|
|
320
|
+
throw new Error(`profile ${JSON.stringify(name)} must be a literal string or object`)
|
|
321
|
+
}
|
|
322
|
+
definitions.set(name, definition.value)
|
|
323
|
+
index = skipWhitespace(syntax, definition.end)
|
|
324
|
+
if (index < objectEnd && syntax[index] !== ',') {
|
|
325
|
+
throw new Error(`profile ${JSON.stringify(name)} must be followed by a comma`)
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (!/^[\s;]*$/.test(syntax.slice(objectEnd + 1))) {
|
|
329
|
+
throw new Error('configuration contains code after the exported profile object')
|
|
330
|
+
}
|
|
331
|
+
return definitions
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function maskJavascriptCommentsAndStrings (source) {
|
|
335
|
+
const syntax = [...source]
|
|
336
|
+
let quote
|
|
337
|
+
let lineComment = false
|
|
338
|
+
let blockComment = false
|
|
339
|
+
for (let index = 0; index < source.length; index++) {
|
|
340
|
+
const character = source[index]
|
|
341
|
+
const next = source[index + 1]
|
|
342
|
+
if (lineComment) {
|
|
343
|
+
if (character === '\n') lineComment = false
|
|
344
|
+
else syntax[index] = ' '
|
|
345
|
+
continue
|
|
346
|
+
}
|
|
347
|
+
if (blockComment) {
|
|
348
|
+
syntax[index] = ' '
|
|
349
|
+
if (character === '*' && next === '/') {
|
|
350
|
+
syntax[++index] = ' '
|
|
351
|
+
blockComment = false
|
|
352
|
+
}
|
|
353
|
+
continue
|
|
354
|
+
}
|
|
355
|
+
if (quote) {
|
|
356
|
+
if (character === '\\') {
|
|
357
|
+
syntax[index] = ' '
|
|
358
|
+
if (index + 1 < source.length) syntax[++index] = ' '
|
|
359
|
+
} else if (character === quote) {
|
|
360
|
+
quote = undefined
|
|
361
|
+
} else {
|
|
362
|
+
syntax[index] = ' '
|
|
363
|
+
}
|
|
364
|
+
continue
|
|
365
|
+
}
|
|
366
|
+
if (character === '/' && next === '/') {
|
|
367
|
+
syntax[index] = syntax[++index] = ' '
|
|
368
|
+
lineComment = true
|
|
369
|
+
} else if (character === '/' && next === '*') {
|
|
370
|
+
syntax[index] = syntax[++index] = ' '
|
|
371
|
+
blockComment = true
|
|
372
|
+
} else if (character === '"' || character === "'" || character === '`') {
|
|
373
|
+
quote = character
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return syntax.join('')
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function findClosingBrace (source, start) {
|
|
380
|
+
let depth = 0
|
|
381
|
+
for (let index = start; index < source.length; index++) {
|
|
382
|
+
if (source[index] === '{') depth++
|
|
383
|
+
else if (source[index] === '}' && --depth === 0) return index
|
|
384
|
+
}
|
|
385
|
+
return -1
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function skipWhitespace (source, start) {
|
|
389
|
+
let index = start
|
|
390
|
+
while (/\s/.test(source[index] || '')) index++
|
|
391
|
+
return index
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Reads a bounded JSON-shaped JavaScript literal without evaluating customer code.
|
|
396
|
+
*
|
|
397
|
+
* @param {string} source original JavaScript source
|
|
398
|
+
* @param {string} syntax source with comments and string contents masked
|
|
399
|
+
* @param {number} start literal start offset
|
|
400
|
+
* @param {number} [depth] current literal nesting depth
|
|
401
|
+
* @returns {{end: number, value: unknown}|undefined} parsed literal and ending offset
|
|
402
|
+
*/
|
|
403
|
+
function readJavascriptLiteral (source, syntax, start, depth = 0) {
|
|
404
|
+
if (depth > 16) return
|
|
405
|
+
let index = skipWhitespace(syntax, start)
|
|
406
|
+
const quoted = readQuotedString(source, index)
|
|
407
|
+
if (quoted) return quoted
|
|
408
|
+
|
|
409
|
+
const primitive = /^(true|false|null|-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)(?![\w$.])/.exec(
|
|
410
|
+
syntax.slice(index)
|
|
411
|
+
)
|
|
412
|
+
if (primitive) {
|
|
413
|
+
const values = { false: false, null: null, true: true }
|
|
414
|
+
const value = Object.hasOwn(values, primitive[1]) ? values[primitive[1]] : Number(primitive[1])
|
|
415
|
+
if (typeof value === 'number' && (!Number.isFinite(value) || Object.is(value, -0))) return
|
|
416
|
+
return {
|
|
417
|
+
end: index + primitive[0].length,
|
|
418
|
+
value,
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const open = syntax[index]
|
|
423
|
+
if (open !== '[' && open !== '{') return
|
|
424
|
+
const close = open === '[' ? ']' : '}'
|
|
425
|
+
const value = open === '[' ? [] : Object.create(null)
|
|
426
|
+
index++
|
|
427
|
+
while (index < syntax.length) {
|
|
428
|
+
index = skipWhitespace(syntax, index)
|
|
429
|
+
if (syntax[index] === close) return { end: index + 1, value }
|
|
430
|
+
|
|
431
|
+
let item
|
|
432
|
+
if (open === '[') {
|
|
433
|
+
item = readJavascriptLiteral(source, syntax, index, depth + 1)
|
|
434
|
+
if (!item) return
|
|
435
|
+
value.push(item.value)
|
|
436
|
+
} else {
|
|
437
|
+
const property = readJavascriptPropertyName(source, syntax, index)
|
|
438
|
+
if (!property || property.value === '__proto__' || Object.hasOwn(value, property.value)) return
|
|
439
|
+
index = skipWhitespace(syntax, property.end)
|
|
440
|
+
if (syntax[index] !== ':') return
|
|
441
|
+
item = readJavascriptLiteral(source, syntax, index + 1, depth + 1)
|
|
442
|
+
if (!item) return
|
|
443
|
+
value[property.value] = item.value
|
|
444
|
+
}
|
|
445
|
+
index = item.end
|
|
446
|
+
|
|
447
|
+
index = skipWhitespace(syntax, index)
|
|
448
|
+
if (syntax[index] === close) return { end: index + 1, value }
|
|
449
|
+
if (syntax[index] !== ',') return
|
|
450
|
+
index++
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Reads one static quoted or identifier JavaScript object property name.
|
|
456
|
+
*
|
|
457
|
+
* @param {string} source original JavaScript source
|
|
458
|
+
* @param {string} syntax source with comments and string contents masked
|
|
459
|
+
* @param {number} start property start offset
|
|
460
|
+
* @returns {{end: number, value: string}|undefined} property name and ending offset
|
|
461
|
+
*/
|
|
462
|
+
function readJavascriptPropertyName (source, syntax, start) {
|
|
463
|
+
const index = skipWhitespace(syntax, start)
|
|
464
|
+
const quoted = readQuotedString(source, index)
|
|
465
|
+
if (quoted) return quoted
|
|
466
|
+
const identifier = /^[A-Za-z_$][\w$]*/.exec(syntax.slice(index))
|
|
467
|
+
if (identifier) return { end: index + identifier[0].length, value: identifier[0] }
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function getDataProfileDefinitions (value) {
|
|
471
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
472
|
+
throw new Error('configuration must contain a profile object')
|
|
473
|
+
}
|
|
474
|
+
return new Map(Object.entries(value))
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function getYamlStringProfileDefinitions (source) {
|
|
478
|
+
const definitions = new Map()
|
|
479
|
+
for (const line of source.split(/\r?\n/)) {
|
|
480
|
+
if (!line.trim() || /^\s*#/.test(line)) continue
|
|
481
|
+
const match = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(line)
|
|
482
|
+
const definition = match && !/^\s/.test(line) ? getYamlStringProfileDefinition(match[2]) : undefined
|
|
483
|
+
if (definition === undefined) {
|
|
484
|
+
throw new Error('YAML profiles must be literal one-line command strings')
|
|
485
|
+
}
|
|
486
|
+
definitions.set(match[1], definition)
|
|
487
|
+
}
|
|
488
|
+
return definitions
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function getYamlStringProfileDefinition (source) {
|
|
492
|
+
const value = source.trimStart()
|
|
493
|
+
const quote = value[0]
|
|
494
|
+
if (quote !== '"' && quote !== "'") {
|
|
495
|
+
const comment = /\s+#/.exec(value)
|
|
496
|
+
const definition = value.slice(0, comment?.index ?? value.length).trimEnd()
|
|
497
|
+
return definition || undefined
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
let definition = ''
|
|
501
|
+
for (let index = 1; index < value.length; index++) {
|
|
502
|
+
const character = value[index]
|
|
503
|
+
if (quote === '"' && character === '\\') {
|
|
504
|
+
return
|
|
505
|
+
}
|
|
506
|
+
if (quote === "'" && character === quote && value[index + 1] === quote) {
|
|
507
|
+
definition += quote
|
|
508
|
+
index++
|
|
509
|
+
continue
|
|
510
|
+
}
|
|
511
|
+
if (character !== quote) {
|
|
512
|
+
definition += character
|
|
513
|
+
continue
|
|
514
|
+
}
|
|
515
|
+
const remainder = value.slice(index + 1)
|
|
516
|
+
if (!remainder.trim() || /^\s+#/.test(remainder)) return definition
|
|
517
|
+
return
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function readQuotedString (source, start) {
|
|
522
|
+
const quote = source[start]
|
|
523
|
+
if (quote !== '"' && quote !== "'") return
|
|
524
|
+
let value = ''
|
|
525
|
+
let index = start + 1
|
|
526
|
+
while (index < source.length) {
|
|
527
|
+
const character = source[index]
|
|
528
|
+
if (character === quote) return { end: index + 1, value }
|
|
529
|
+
if (character === '\r' || character === '\n') return
|
|
530
|
+
if (character !== '\\') {
|
|
531
|
+
value += character
|
|
532
|
+
index++
|
|
533
|
+
continue
|
|
534
|
+
}
|
|
535
|
+
const escaped = readJavascriptStringEscape(source, index + 1)
|
|
536
|
+
if (!escaped) return
|
|
537
|
+
value += escaped.value
|
|
538
|
+
index = escaped.end
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Decodes one bounded JavaScript string escape without evaluating source.
|
|
544
|
+
*
|
|
545
|
+
* @param {string} source original JavaScript source
|
|
546
|
+
* @param {number} start first character after the backslash
|
|
547
|
+
* @returns {{end: number, value: string}|undefined} decoded character and ending offset
|
|
548
|
+
*/
|
|
549
|
+
function readJavascriptStringEscape (source, start) {
|
|
550
|
+
const escaped = source[start]
|
|
551
|
+
if (Object.hasOwn(JAVASCRIPT_STRING_ESCAPES, escaped)) {
|
|
552
|
+
return { end: start + 1, value: JAVASCRIPT_STRING_ESCAPES[escaped] }
|
|
553
|
+
}
|
|
554
|
+
if (escaped === '\n') return { end: start + 1, value: '' }
|
|
555
|
+
if (escaped === '\r') {
|
|
556
|
+
return { end: source[start + 1] === '\n' ? start + 2 : start + 1, value: '' }
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
if (escaped === 'x') {
|
|
560
|
+
const hexadecimal = source.slice(start + 1, start + 3)
|
|
561
|
+
if (/^[\da-fA-F]{2}$/.test(hexadecimal)) {
|
|
562
|
+
return { end: start + 3, value: String.fromCharCode(Number.parseInt(hexadecimal, 16)) }
|
|
563
|
+
}
|
|
564
|
+
return
|
|
565
|
+
}
|
|
566
|
+
if (escaped !== 'u') return
|
|
567
|
+
|
|
568
|
+
if (source[start + 1] === '{') {
|
|
569
|
+
const match = /^\{([\da-fA-F]{1,6})\}/.exec(source.slice(start + 1))
|
|
570
|
+
if (!match) return
|
|
571
|
+
const codePoint = Number.parseInt(match[1], 16)
|
|
572
|
+
if (codePoint > 1_114_111) return
|
|
573
|
+
return { end: start + 1 + match[0].length, value: String.fromCodePoint(codePoint) }
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const hexadecimal = source.slice(start + 1, start + 5)
|
|
577
|
+
if (/^[\da-fA-F]{4}$/.test(hexadecimal)) {
|
|
578
|
+
return { end: start + 5, value: String.fromCharCode(Number.parseInt(hexadecimal, 16)) }
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function getProfileArgs (definition) {
|
|
583
|
+
if (typeof definition === 'string') return getStringProfileArgs(definition)
|
|
584
|
+
if (!definition || typeof definition !== 'object' || Array.isArray(definition)) {
|
|
585
|
+
return { error: 'must be a literal string or object', args: [] }
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
const args = []
|
|
589
|
+
for (const [key, value] of Object.entries(definition)) {
|
|
590
|
+
if (OMITTED_PROFILE_KEYS.has(key)) continue
|
|
591
|
+
const flag = PROFILE_FLAG_OPTIONS.get(key)
|
|
592
|
+
if (flag) {
|
|
593
|
+
if (typeof value !== 'boolean') return { error: `contains non-boolean ${key}`, args: [] }
|
|
594
|
+
if (key === 'strict') args.push(value ? '--strict' : '--no-strict')
|
|
595
|
+
else if (value) args.push(flag)
|
|
596
|
+
continue
|
|
597
|
+
}
|
|
598
|
+
const option = PROFILE_VALUE_OPTIONS.get(key)
|
|
599
|
+
if (!option) return { error: `contains unsupported option ${key}`, args: [] }
|
|
600
|
+
|
|
601
|
+
if (key === 'worldParameters') {
|
|
602
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
603
|
+
return { error: 'contains non-object worldParameters', args: [] }
|
|
604
|
+
}
|
|
605
|
+
args.push(option, JSON.stringify(value))
|
|
606
|
+
continue
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
const values = Array.isArray(value) ? value : [value]
|
|
610
|
+
for (const item of values) {
|
|
611
|
+
if (item !== null && typeof item === 'object') {
|
|
612
|
+
return { error: `contains unsupported object value for ${key}`, args: [] }
|
|
613
|
+
}
|
|
614
|
+
args.push(option, String(item))
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
return { args }
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function getStringProfileArgs (definition) {
|
|
621
|
+
const tokens = tokenizeProfile(definition)
|
|
622
|
+
if (!tokens) return { error: 'contains shell control syntax or unbalanced quoting', args: [] }
|
|
623
|
+
|
|
624
|
+
const args = []
|
|
625
|
+
for (let index = 0; index < tokens.length; index++) {
|
|
626
|
+
const token = tokens[index]
|
|
627
|
+
if (!token.startsWith('-')) continue
|
|
628
|
+
const option = token.split('=', 1)[0]
|
|
629
|
+
if (RETAINED_PROFILE_FLAGS.has(option)) {
|
|
630
|
+
args.push(token)
|
|
631
|
+
continue
|
|
632
|
+
}
|
|
633
|
+
if (RETAINED_PROFILE_VALUES.has(option)) {
|
|
634
|
+
const value = token.includes('=') ? token.slice(token.indexOf('=') + 1) : tokens[++index]
|
|
635
|
+
if (!value || value.startsWith('-')) return { error: `has a missing value for ${option}`, args: [] }
|
|
636
|
+
args.push(option, value)
|
|
637
|
+
continue
|
|
638
|
+
}
|
|
639
|
+
if (OMITTED_PROFILE_OPTIONS.has(option)) {
|
|
640
|
+
if (OMITTED_PROFILE_OPTIONS.get(option) && !token.includes('=')) index++
|
|
641
|
+
continue
|
|
642
|
+
}
|
|
643
|
+
return { error: `contains unsupported option ${option}`, args: [] }
|
|
644
|
+
}
|
|
645
|
+
return { args }
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function tokenizeProfile (source) {
|
|
649
|
+
if (/[\0\r\n;&|`]|\$\(|\$\{/.test(source)) return
|
|
650
|
+
const tokens = []
|
|
651
|
+
const pattern = /"([^"\r\n]*)"|'([^'\r\n]*)'|([^\s"']+)/g
|
|
652
|
+
let consumed = 0
|
|
653
|
+
for (const match of source.matchAll(pattern)) {
|
|
654
|
+
if (source.slice(consumed, match.index).trim()) return
|
|
655
|
+
tokens.push(match[1] ?? match[2] ?? match[3])
|
|
656
|
+
consumed = match.index + match[0].length
|
|
657
|
+
}
|
|
658
|
+
if (source.slice(consumed).trim()) return
|
|
659
|
+
return tokens
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function getOptionValues (args, options) {
|
|
663
|
+
const values = []
|
|
664
|
+
for (let index = 0; index < args.length; index++) {
|
|
665
|
+
const option = args[index].split('=', 1)[0]
|
|
666
|
+
if (!options.has(option)) continue
|
|
667
|
+
values.push(args[index].includes('=') ? args[index].slice(args[index].indexOf('=') + 1) : args[++index])
|
|
668
|
+
}
|
|
669
|
+
return values.filter(Boolean)
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
function getPhysicalPath (filename) {
|
|
673
|
+
try {
|
|
674
|
+
return fs.realpathSync(filename)
|
|
675
|
+
} catch {}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function omitOptions (args, options) {
|
|
679
|
+
const retained = []
|
|
680
|
+
for (let index = 0; index < args.length; index++) {
|
|
681
|
+
const option = args[index].split('=', 1)[0]
|
|
682
|
+
if (!options.has(option)) {
|
|
683
|
+
retained.push(args[index])
|
|
684
|
+
continue
|
|
685
|
+
}
|
|
686
|
+
if (!args[index].includes('=')) index++
|
|
687
|
+
}
|
|
688
|
+
return retained
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
function expandSupportPaths (args, projectFiles, projectRoot) {
|
|
692
|
+
const expanded = []
|
|
693
|
+
const pathOptions = new Set(['-i', '-r', '--import', '--require'])
|
|
694
|
+
const aliases = { '-i': '--import', '-r': '--require' }
|
|
695
|
+
for (let index = 0; index < args.length; index++) {
|
|
696
|
+
const option = args[index].split('=', 1)[0]
|
|
697
|
+
if (!pathOptions.has(option)) {
|
|
698
|
+
expanded.push(args[index])
|
|
699
|
+
continue
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
const value = args[index].includes('=') ? args[index].slice(args[index].indexOf('=') + 1) : args[++index]
|
|
703
|
+
const matches = getSupportPathMatches(value, projectFiles, projectRoot)
|
|
704
|
+
if (matches.length === 0) {
|
|
705
|
+
return {
|
|
706
|
+
error: `Cucumber support path ${JSON.stringify(value)} does not match a repository-contained file`,
|
|
707
|
+
runnerArgs: [],
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
for (const filename of matches) expanded.push(aliases[option] || option, filename)
|
|
711
|
+
}
|
|
712
|
+
return { runnerArgs: expanded }
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
function getSupportPathMatches (value, projectFiles, projectRoot) {
|
|
716
|
+
const normalized = String(value || '').replaceAll('\\', '/')
|
|
717
|
+
const candidate = path.resolve(projectRoot, value)
|
|
718
|
+
try {
|
|
719
|
+
if (fs.statSync(candidate).isFile()) return [candidate]
|
|
720
|
+
if (fs.statSync(candidate).isDirectory()) {
|
|
721
|
+
return projectFiles.filter(filename => {
|
|
722
|
+
const relative = path.relative(candidate, filename)
|
|
723
|
+
return relative && !relative.startsWith('..') && !path.isAbsolute(relative) &&
|
|
724
|
+
/\.[cm]?[jt]s$/.test(filename)
|
|
725
|
+
}).sort()
|
|
726
|
+
}
|
|
727
|
+
} catch {}
|
|
728
|
+
if (!/[*?[\]{}]/.test(normalized)) return []
|
|
729
|
+
return projectFiles.filter(filename => {
|
|
730
|
+
return matchesLiteralGlob(path.relative(projectRoot, filename), normalized)
|
|
731
|
+
}).sort()
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
function getJsonScenarioCount (output) {
|
|
735
|
+
const source = output.trim()
|
|
736
|
+
const end = source.lastIndexOf(']')
|
|
737
|
+
if (end === -1) return null
|
|
738
|
+
let attempts = 0
|
|
739
|
+
for (let start = source.indexOf('['); start !== -1 && start < end && attempts < 20;
|
|
740
|
+
start = source.indexOf('[', start + 1), attempts++) {
|
|
741
|
+
try {
|
|
742
|
+
const features = JSON.parse(source.slice(start, end + 1))
|
|
743
|
+
if (!Array.isArray(features)) continue
|
|
744
|
+
return features.reduce((count, feature) => {
|
|
745
|
+
return count + (feature.elements || []).filter(element => element.type === 'scenario').length
|
|
746
|
+
}, 0)
|
|
747
|
+
} catch {}
|
|
748
|
+
}
|
|
749
|
+
return null
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
function getPhysicalDirectory (directory) {
|
|
753
|
+
try {
|
|
754
|
+
const physical = fs.realpathSync(directory)
|
|
755
|
+
if (fs.statSync(physical).isDirectory()) return physical
|
|
756
|
+
} catch {}
|
|
757
|
+
}
|
|
758
|
+
|
|
109
759
|
module.exports = {
|
|
110
760
|
CONFIG_PATTERN,
|
|
761
|
+
expandProfiles,
|
|
111
762
|
getFocusedTestArgs,
|
|
112
763
|
getGeneratedStepsContent,
|
|
113
764
|
getGeneratedStepsPath,
|
|
@@ -116,4 +767,5 @@ module.exports = {
|
|
|
116
767
|
getObservedTestCount,
|
|
117
768
|
getScenarioCount,
|
|
118
769
|
isTestFile,
|
|
770
|
+
supportsConfigIsolation,
|
|
119
771
|
}
|