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,5 +1,10 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const fs = require('node:fs')
|
|
4
|
+
const path = require('node:path')
|
|
5
|
+
|
|
6
|
+
const { BLOCKER_CATEGORIES } = require('./blocker-category')
|
|
7
|
+
const { isProjectBuildArtifactPath } = require('./project-build-artifact')
|
|
3
8
|
const { stripAnsi } = require('./test-output')
|
|
4
9
|
|
|
5
10
|
const FILESYSTEM_PERMISSION_PATTERN = /\b(?:EACCES|EPERM|Operation not permitted|Permission denied)\b/i
|
|
@@ -8,22 +13,39 @@ const CONNECTION_REFUSED_PATTERN = /\bECONNREFUSED\b|\bconnection refused\b/i
|
|
|
8
13
|
const NO_TESTS_FOUND_PATTERN =
|
|
9
14
|
/\b(?:No test files? found|No tests? found|No test files? were found|0 tests? collected)\b/i
|
|
10
15
|
const MODULE_OR_TRANSFORM_PATTERN =
|
|
11
|
-
/\b(?:Cannot find (?:module|package)|ERR_MODULE_NOT_FOUND|MODULE_NOT_FOUND|ERR_PACKAGE_PATH_NOT_EXPORTED|Package subpath\b[\s\S]
|
|
16
|
+
/\b(?:Cannot find (?:module|package)|ERR_MODULE_NOT_FOUND|MODULE_NOT_FOUND|ERR_PACKAGE_PATH_NOT_EXPORTED|Package subpath\b[\s\S]+\bnot defined by "exports"|Could not resolve|transform failed)\b/i
|
|
17
|
+
const BUILD_ARTIFACT_PATH_PATTERN = String.raw`(?:^|[\\/])(?:build|dist|generated)(?:[\\/]|['"])`
|
|
18
|
+
const MISSING_ARTIFACT_MESSAGE_PATTERN =
|
|
19
|
+
'(?:Cannot find (?:module|package)|ERR_MODULE_NOT_FOUND|MODULE_NOT_FOUND|ENOENT|no such file or directory|' +
|
|
20
|
+
'is not found|does not exist|could not be found)'
|
|
21
|
+
const BUILD_ARTIFACT_MISSING_PATTERN = new RegExp(
|
|
22
|
+
String.raw`(?:${MISSING_ARTIFACT_MESSAGE_PATTERN}[^\r\n]{0,500}${BUILD_ARTIFACT_PATH_PATTERN}|` +
|
|
23
|
+
String.raw`${BUILD_ARTIFACT_PATH_PATTERN}[^\r\n]{0,500}${MISSING_ARTIFACT_MESSAGE_PATTERN})`,
|
|
24
|
+
'im'
|
|
25
|
+
)
|
|
26
|
+
const FILE_PATH_REFERENCE_PATTERN = /(?:^|[\s('"`])((?:\.{1,2}[\\/]|[\\/]|[A-Za-z]:[\\/])[^'"`\s),;]*)/g
|
|
27
|
+
const CUCUMBER_CONFIG_OPTION_UNSUPPORTED_PATTERN = /unknown option ['"]--config['"]/i
|
|
12
28
|
const CYPRESS_BINARY_PATTERN =
|
|
13
|
-
/
|
|
29
|
+
/Cypress executable not found|Cypress binary is missing|Cypress failed to start|Please reinstall Cypress/i
|
|
14
30
|
const PLAYWRIGHT_BROWSER_PATTERN = new RegExp(
|
|
15
|
-
String.raw`
|
|
16
|
-
'Please run the following command to download new browsers|playwright install
|
|
31
|
+
String.raw`browserType\.launch: Executable doesn't exist|` +
|
|
32
|
+
'Please run the following command to download new browsers|playwright install',
|
|
17
33
|
'i'
|
|
18
34
|
)
|
|
19
35
|
const PLAYWRIGHT_BROWSER_LAUNCH_PATTERN =
|
|
20
|
-
/
|
|
36
|
+
/Failed to launch the browser process|bootstrap_check_in|MachPortRendezvous/i
|
|
21
37
|
const PLAYWRIGHT_BROWSER_ABORT_PATTERN =
|
|
22
38
|
/(?:browserType\.launch: Target page, context or browser has been closed|Browser logs:)[\s\S]*?(?:signal=SIGABRT|Received signal 6|Abort trap: 6)/i
|
|
39
|
+
const PUPPETEER_BROWSER_PATTERN =
|
|
40
|
+
/(?:Could not find (?:Chrome|Chromium)|Failed to launch the browser process|Troubleshooting:\s*https:\/\/pptr\.dev)/i
|
|
41
|
+
const PUPPETEER_BROWSER_ABORT_PATTERN =
|
|
42
|
+
/Failed to launch the browser process[\s\S]*?(?:signal=SIGABRT|Received signal 6|Abort trap: 6)/i
|
|
43
|
+
const CUCUMBER_BROWSER_FAILURE_PATTERN =
|
|
44
|
+
/(?:(?:browser|puppeteer|playwright|webdriver)[^\r\n]{0,160}\b(?:aborted|closed|crashed|failed|terminated|unavailable)\b|(?:Failed|Unable) to (?:connect to|launch|start) (?:the )?browser)/i
|
|
23
45
|
const VITEST_BROWSER_PROVIDER_PATTERN =
|
|
24
|
-
/
|
|
46
|
+
/Cannot find (?:module|package).*@vitest\/browser|@vitest\/browser-[^\s'"].*(?:missing|not (?:found|installed))/i
|
|
25
47
|
const RUNNER_COMMAND_NOT_FOUND_PATTERN =
|
|
26
|
-
/
|
|
48
|
+
/command not found|is not recognized as an internal or external command|spawn [^\r\n]+ ENOENT/i
|
|
27
49
|
|
|
28
50
|
/**
|
|
29
51
|
* Identifies toolchain and execution-environment failures that happen before tests start.
|
|
@@ -34,6 +56,7 @@ const RUNNER_COMMAND_NOT_FOUND_PATTERN =
|
|
|
34
56
|
* @param {object} [options] classification options
|
|
35
57
|
* @param {boolean} [options.browserRequired] whether the selected runner uses browser mode
|
|
36
58
|
* @param {string} [options.framework] test framework identifier
|
|
59
|
+
* @param {string} [options.packageJson] approval-bound project package.json
|
|
37
60
|
* @param {boolean} [options.testsRan] whether reliable test output was observed
|
|
38
61
|
* @returns {object|undefined} structured blocker diagnosis
|
|
39
62
|
*/
|
|
@@ -41,6 +64,7 @@ function getCommandBlocker (result, options = {}) {
|
|
|
41
64
|
const output = `${result.stdout || ''}\n${result.stderr || ''}`
|
|
42
65
|
if (Array.isArray(result.missingRequiredEnvVars) && result.missingRequiredEnvVars.length > 0) {
|
|
43
66
|
return {
|
|
67
|
+
blockerCategory: BLOCKER_CATEGORIES.PROJECT_SETUP_REQUIRED,
|
|
44
68
|
kind: 'project-command-environment-missing',
|
|
45
69
|
summary: 'The selected project test command requires approved non-secret environment variables that are not ' +
|
|
46
70
|
'available in the validator process. No Test Optimization conclusion was reached.',
|
|
@@ -50,14 +74,29 @@ function getCommandBlocker (result, options = {}) {
|
|
|
50
74
|
toolchainBlocked: true,
|
|
51
75
|
}
|
|
52
76
|
}
|
|
77
|
+
if (options.framework === 'cucumber' && result.exitCode !== 0 &&
|
|
78
|
+
CUCUMBER_CONFIG_OPTION_UNSUPPORTED_PATTERN.test(output)) {
|
|
79
|
+
return {
|
|
80
|
+
blockerCategory: BLOCKER_CATEGORIES.VALIDATOR_LIMITATION,
|
|
81
|
+
kind: 'cucumber-config-isolation-unsupported',
|
|
82
|
+
summary: 'The installed Cucumber CLI rejected the validator-owned configuration-isolation option. This is a ' +
|
|
83
|
+
'validator compatibility limitation, not a project test or Test Optimization failure.',
|
|
84
|
+
recommendation: 'Report this Cucumber version to validator engineering. Use the static CI audit and do not ' +
|
|
85
|
+
'change the customer profile, command, or working directory to bypass this limitation.',
|
|
86
|
+
signals: getMatchingLines(output, CUCUMBER_CONFIG_OPTION_UNSUPPORTED_PATTERN),
|
|
87
|
+
validatorBlocked: true,
|
|
88
|
+
}
|
|
89
|
+
}
|
|
53
90
|
if (LOCAL_SOCKET_PATTERN.test(output) && FILESYSTEM_PERMISSION_PATTERN.test(output)) {
|
|
54
91
|
return {
|
|
92
|
+
blockerCategory: BLOCKER_CATEGORIES.EXECUTION_ENVIRONMENT_BLOCKED,
|
|
55
93
|
kind: 'local-test-socket-blocked',
|
|
56
94
|
summary: 'The selected project test could not start its localhost listener in this execution environment. ' +
|
|
57
95
|
'The offline Datadog validator did not open this socket. No Test Optimization conclusion was reached.',
|
|
58
96
|
recommendation: 'Run the same approved plan in an environment that permits the project test to use its ' +
|
|
59
|
-
'required localhost socket.
|
|
60
|
-
'permissions automatically or interpret this as a
|
|
97
|
+
'required localhost socket. Use the unchanged command and SHA from execution-plan.md. This may require ' +
|
|
98
|
+
'normal project-test permissions. Do not request broader permissions automatically or interpret this as a ' +
|
|
99
|
+
'Test Optimization failure.',
|
|
61
100
|
signals: getMatchingLines(
|
|
62
101
|
output,
|
|
63
102
|
/127\.0\.0\.1|localhost|listen|EACCES|EPERM|Operation not permitted|Permission denied/i
|
|
@@ -68,6 +107,7 @@ function getCommandBlocker (result, options = {}) {
|
|
|
68
107
|
if (options.framework === 'cypress' && result.exitCode !== 0 &&
|
|
69
108
|
CONNECTION_REFUSED_PATTERN.test(output) && LOCAL_SOCKET_PATTERN.test(output)) {
|
|
70
109
|
return {
|
|
110
|
+
blockerCategory: BLOCKER_CATEGORIES.PROJECT_SETUP_REQUIRED,
|
|
71
111
|
kind: 'cypress-application-unavailable',
|
|
72
112
|
summary: 'The selected Cypress spec could not connect to its localhost application. Discovery does not start ' +
|
|
73
113
|
'customer services, so no Test Optimization conclusion was reached.',
|
|
@@ -79,6 +119,7 @@ function getCommandBlocker (result, options = {}) {
|
|
|
79
119
|
}
|
|
80
120
|
if (options.testsRan !== true && NO_TESTS_FOUND_PATTERN.test(output)) {
|
|
81
121
|
return {
|
|
122
|
+
blockerCategory: BLOCKER_CATEGORIES.VALIDATOR_LIMITATION,
|
|
82
123
|
kind: 'no-tests-collected',
|
|
83
124
|
summary: 'The selected representative was not collected by the project test runner. No Test Optimization ' +
|
|
84
125
|
'conclusion was reached.',
|
|
@@ -91,21 +132,24 @@ function getCommandBlocker (result, options = {}) {
|
|
|
91
132
|
}
|
|
92
133
|
|
|
93
134
|
const browserMode = options.framework === 'playwright' ||
|
|
135
|
+
(options.framework === 'cucumber' && options.browserRequired === true) ||
|
|
94
136
|
(options.framework === 'vitest' && options.browserRequired === true)
|
|
95
137
|
if (browserMode &&
|
|
96
138
|
result.exitCode !== 0 &&
|
|
97
139
|
PLAYWRIGHT_BROWSER_LAUNCH_PATTERN.test(output) && FILESYSTEM_PERMISSION_PATTERN.test(output)) {
|
|
98
|
-
const runner = options.framework
|
|
140
|
+
const runner = getBrowserRunnerName(options.framework)
|
|
99
141
|
return {
|
|
142
|
+
blockerCategory: BLOCKER_CATEGORIES.EXECUTION_ENVIRONMENT_BLOCKED,
|
|
100
143
|
kind: `${options.framework}-browser-launch-blocked`,
|
|
101
|
-
summary: `${runner} needs to launch the project browser, but the current
|
|
144
|
+
summary: `${runner} needs to launch the project browser, but the current execution environment denied that ` +
|
|
145
|
+
'launch. ' +
|
|
102
146
|
'No Test Optimization conclusion was reached.',
|
|
103
147
|
recommendation: 'Retry the same approved plan from a host shell or another environment where the installed ' +
|
|
104
|
-
'project browser can launch.
|
|
105
|
-
'approval file, or approval SHA.',
|
|
148
|
+
'project browser can launch. Use the unchanged command and SHA from execution-plan.md. Do not request ' +
|
|
149
|
+
'broader permissions automatically or change the command, approval file, or approval SHA.',
|
|
106
150
|
signals: getMatchingLines(
|
|
107
151
|
output,
|
|
108
|
-
/
|
|
152
|
+
/Failed to launch the browser process|bootstrap_check_in|MachPortRendezvous|EACCES|EPERM|Operation not permitted|Permission denied/i
|
|
109
153
|
),
|
|
110
154
|
blockedByExecutionEnvironment: true,
|
|
111
155
|
}
|
|
@@ -114,6 +158,7 @@ function getCommandBlocker (result, options = {}) {
|
|
|
114
158
|
if (options.framework === 'playwright' && result.exitCode !== 0 &&
|
|
115
159
|
PLAYWRIGHT_BROWSER_ABORT_PATTERN.test(stripAnsi(output))) {
|
|
116
160
|
return {
|
|
161
|
+
blockerCategory: BLOCKER_CATEGORIES.CLEAN_TEST_FAILED,
|
|
117
162
|
kind: 'playwright-browser-process-aborted',
|
|
118
163
|
summary: 'A browser launched by Playwright aborted before the selected tests could produce reliable results. ' +
|
|
119
164
|
'The available evidence does not identify whether the browser/runtime setup, the project, the execution ' +
|
|
@@ -129,9 +174,28 @@ function getCommandBlocker (result, options = {}) {
|
|
|
129
174
|
}
|
|
130
175
|
}
|
|
131
176
|
|
|
177
|
+
if (options.framework === 'cucumber' && options.browserRequired === true && result.exitCode !== 0 &&
|
|
178
|
+
PUPPETEER_BROWSER_ABORT_PATTERN.test(stripAnsi(output))) {
|
|
179
|
+
return {
|
|
180
|
+
blockerCategory: BLOCKER_CATEGORIES.CLEAN_TEST_FAILED,
|
|
181
|
+
kind: 'cucumber-browser-process-aborted',
|
|
182
|
+
summary: 'A browser launched by the selected Cucumber support code aborted before a reliable scenario result ' +
|
|
183
|
+
'was observed. No Test Optimization conclusion was reached.',
|
|
184
|
+
recommendation: 'Run the same approved plan where the project\'s normal Cucumber browser tests can launch. ' +
|
|
185
|
+
'Collect the browser and operating-system crash diagnostics if it still fails; do not treat this result as ' +
|
|
186
|
+
'a Test Optimization failure.',
|
|
187
|
+
signals: getMatchingLines(
|
|
188
|
+
output,
|
|
189
|
+
/Failed to launch the browser process|signal=SIGABRT|Received signal 6|Abort trap: 6/i
|
|
190
|
+
),
|
|
191
|
+
localRuntimeBlocked: true,
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
132
195
|
if (options.framework === 'cypress' && result.exitCode !== 0 &&
|
|
133
196
|
options.testsRan !== true && CYPRESS_BINARY_PATTERN.test(output)) {
|
|
134
197
|
return {
|
|
198
|
+
blockerCategory: BLOCKER_CATEGORIES.PROJECT_SETUP_REQUIRED,
|
|
135
199
|
kind: 'cypress-runtime-missing',
|
|
136
200
|
summary: 'The Cypress npm package is installed, but its application binary is missing or could not start. ' +
|
|
137
201
|
'No Test Optimization conclusion was reached.',
|
|
@@ -143,17 +207,24 @@ function getCommandBlocker (result, options = {}) {
|
|
|
143
207
|
}
|
|
144
208
|
|
|
145
209
|
if (browserMode && result.exitCode !== 0 && options.testsRan !== true &&
|
|
146
|
-
PLAYWRIGHT_BROWSER_PATTERN.test(output)) {
|
|
210
|
+
(PLAYWRIGHT_BROWSER_PATTERN.test(output) || PUPPETEER_BROWSER_PATTERN.test(output))) {
|
|
147
211
|
const vitestBrowser = options.framework === 'vitest' && options.browserRequired === true
|
|
212
|
+
const cucumberBrowser = options.framework === 'cucumber'
|
|
213
|
+
const browserPattern = cucumberBrowser ? PUPPETEER_BROWSER_PATTERN : PLAYWRIGHT_BROWSER_PATTERN
|
|
148
214
|
return {
|
|
149
|
-
|
|
150
|
-
|
|
215
|
+
blockerCategory: BLOCKER_CATEGORIES.PROJECT_SETUP_REQUIRED,
|
|
216
|
+
kind: vitestBrowser
|
|
217
|
+
? 'vitest-browser-provider-missing'
|
|
218
|
+
: cucumberBrowser
|
|
219
|
+
? 'cucumber-browser-missing'
|
|
220
|
+
: 'playwright-browser-missing',
|
|
221
|
+
summary: `${getBrowserRunnerName(options.framework)} is installed, but the selected project ` +
|
|
151
222
|
'test requires a browser binary or provider that is not installed. ' +
|
|
152
223
|
'No Test Optimization conclusion was reached.',
|
|
153
|
-
recommendation: `Complete the project's normal ${
|
|
154
|
-
'
|
|
224
|
+
recommendation: `Complete the project's normal ${getBrowserRunnerName(options.framework)} setup, then render ` +
|
|
225
|
+
'and approve a fresh ' +
|
|
155
226
|
'validation plan. The validator does not download browsers automatically.',
|
|
156
|
-
signals: getMatchingLines(output,
|
|
227
|
+
signals: getMatchingLines(output, browserPattern),
|
|
157
228
|
toolchainBlocked: true,
|
|
158
229
|
}
|
|
159
230
|
}
|
|
@@ -161,6 +232,7 @@ function getCommandBlocker (result, options = {}) {
|
|
|
161
232
|
if (result.exitCode !== 0 && options.testsRan !== true && options.framework === 'vitest' &&
|
|
162
233
|
options.browserRequired === true && VITEST_BROWSER_PROVIDER_PATTERN.test(output)) {
|
|
163
234
|
return {
|
|
235
|
+
blockerCategory: BLOCKER_CATEGORIES.PROJECT_SETUP_REQUIRED,
|
|
164
236
|
kind: 'vitest-browser-provider-missing',
|
|
165
237
|
summary: 'Vitest browser mode could not load its configured browser provider. Local Test Optimization ' +
|
|
166
238
|
'compatibility was not tested. No Test Optimization conclusion was reached.',
|
|
@@ -173,6 +245,7 @@ function getCommandBlocker (result, options = {}) {
|
|
|
173
245
|
|
|
174
246
|
if (result.exitCode !== 0 && options.testsRan !== true && RUNNER_COMMAND_NOT_FOUND_PATTERN.test(output)) {
|
|
175
247
|
return {
|
|
248
|
+
blockerCategory: BLOCKER_CATEGORIES.PROJECT_SETUP_REQUIRED,
|
|
176
249
|
kind: 'test-runner-command-missing',
|
|
177
250
|
summary: 'The selected project test command could not find its test-runner executable, so local Test ' +
|
|
178
251
|
'Optimization compatibility was not tested. No Test Optimization conclusion was reached.',
|
|
@@ -183,8 +256,25 @@ function getCommandBlocker (result, options = {}) {
|
|
|
183
256
|
}
|
|
184
257
|
}
|
|
185
258
|
|
|
259
|
+
const projectRoot = typeof options.packageJson === 'string' ? path.dirname(options.packageJson) : undefined
|
|
260
|
+
if (result.exitCode !== 0 && hasMissingProjectBuildArtifact(output, projectRoot)) {
|
|
261
|
+
const buildScript = getBuildScript(options.packageJson)
|
|
262
|
+
return {
|
|
263
|
+
blockerCategory: BLOCKER_CATEGORIES.PROJECT_SETUP_REQUIRED,
|
|
264
|
+
kind: 'project-build-artifact-missing',
|
|
265
|
+
summary: 'The selected test requires a conventional build or generated output that is missing. The validator ' +
|
|
266
|
+
'does not run project build commands, so no Test Optimization conclusion was reached.',
|
|
267
|
+
recommendation: `Complete the project's normal build workflow${
|
|
268
|
+
buildScript ? `; package.json declares "build": ${JSON.stringify(buildScript)}` : ''
|
|
269
|
+
}. Confirm the selected test passes normally, then render and approve a fresh validation plan.`,
|
|
270
|
+
signals: getMatchingLines(output, BUILD_ARTIFACT_MISSING_PATTERN),
|
|
271
|
+
blockedByProjectSetup: true,
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
186
275
|
if (result.exitCode !== 0 && options.testsRan !== true && MODULE_OR_TRANSFORM_PATTERN.test(output)) {
|
|
187
276
|
return {
|
|
277
|
+
blockerCategory: BLOCKER_CATEGORIES.PROJECT_SETUP_REQUIRED,
|
|
188
278
|
kind: 'project-command-initialization-failed',
|
|
189
279
|
summary: 'The selected project test command failed during module resolution, transformation, or runner ' +
|
|
190
280
|
'initialization before a reliable test result was observed. No Test Optimization conclusion was reached.',
|
|
@@ -197,6 +287,7 @@ function getCommandBlocker (result, options = {}) {
|
|
|
197
287
|
|
|
198
288
|
if (options.framework === 'cypress' && result.exitCode === 134 && options.testsRan !== true) {
|
|
199
289
|
return {
|
|
290
|
+
blockerCategory: BLOCKER_CATEGORIES.CLEAN_TEST_FAILED,
|
|
200
291
|
kind: 'cypress-process-aborted',
|
|
201
292
|
summary: 'Cypress exited with code 134 before any test result was observed. The available evidence does not ' +
|
|
202
293
|
'identify whether Cypress, its browser/runtime setup, the project, or the execution environment caused the ' +
|
|
@@ -211,6 +302,54 @@ function getCommandBlocker (result, options = {}) {
|
|
|
211
302
|
localRuntimeBlocked: true,
|
|
212
303
|
}
|
|
213
304
|
}
|
|
305
|
+
|
|
306
|
+
if (options.framework === 'cucumber' && options.browserRequired === true &&
|
|
307
|
+
result.exitCode !== 0 && options.testsRan !== true &&
|
|
308
|
+
CUCUMBER_BROWSER_FAILURE_PATTERN.test(output)) {
|
|
309
|
+
return {
|
|
310
|
+
blockerCategory: BLOCKER_CATEGORIES.CLEAN_TEST_FAILED,
|
|
311
|
+
kind: 'cucumber-browser-execution-incomplete',
|
|
312
|
+
summary: 'The selected Cucumber scenario uses browser-backed support code and exited before a reliable ' +
|
|
313
|
+
'scenario result was observed. A secondary Cucumber formatter exception is not enough to identify the root ' +
|
|
314
|
+
'cause. No Test Optimization conclusion was reached.',
|
|
315
|
+
recommendation: 'Run the same approved plan where the project\'s normal Cucumber browser tests can launch, ' +
|
|
316
|
+
'then create a fresh plan. If it still fails, preserve the earliest browser or project error rather than ' +
|
|
317
|
+
'treating a later formatter exception as the cause.',
|
|
318
|
+
signals: getMatchingLines(
|
|
319
|
+
output,
|
|
320
|
+
/Failed to launch|Received signal|TypeError|Cannot read|browser|puppeteer|playwright/i
|
|
321
|
+
),
|
|
322
|
+
localRuntimeBlocked: true,
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function hasMissingProjectBuildArtifact (output, projectRoot) {
|
|
328
|
+
for (const line of output.split(/\r?\n/)) {
|
|
329
|
+
if (!BUILD_ARTIFACT_MISSING_PATTERN.test(line)) continue
|
|
330
|
+
for (const match of line.matchAll(FILE_PATH_REFERENCE_PATTERN)) {
|
|
331
|
+
if (isProjectBuildArtifactPath(match[1], projectRoot)) return true
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return false
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function getBuildScript (packageJson) {
|
|
338
|
+
if (typeof packageJson !== 'string') return
|
|
339
|
+
try {
|
|
340
|
+
const stat = fs.statSync(packageJson)
|
|
341
|
+
if (!stat.isFile() || stat.size > 512 * 1024) return
|
|
342
|
+
const script = JSON.parse(fs.readFileSync(packageJson, 'utf8'))?.scripts?.build
|
|
343
|
+
if (typeof script !== 'string' || !script.trim() || Buffer.byteLength(script) > 500 ||
|
|
344
|
+
/[\0\r\n]/.test(script)) return
|
|
345
|
+
return script.trim()
|
|
346
|
+
} catch {}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function getBrowserRunnerName (framework) {
|
|
350
|
+
if (framework === 'cucumber') return 'Cucumber browser support'
|
|
351
|
+
if (framework === 'vitest') return 'Vitest browser mode'
|
|
352
|
+
return 'Playwright Test'
|
|
214
353
|
}
|
|
215
354
|
|
|
216
355
|
/**
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
const fs = require('node:fs')
|
|
4
4
|
const path = require('node:path')
|
|
5
5
|
|
|
6
|
+
const { createValidationBlocker } = require('./validation-blocker')
|
|
7
|
+
|
|
6
8
|
/**
|
|
7
9
|
* Returns command-created paths that must be declared and removed after validation.
|
|
8
10
|
*
|
|
@@ -32,10 +34,14 @@ function prepareCommandOutputs ({ command, artifactRoot, repositoryRoot }) {
|
|
|
32
34
|
}
|
|
33
35
|
for (const outputPath of outputPaths) {
|
|
34
36
|
if (pathExists(outputPath)) {
|
|
35
|
-
throw
|
|
37
|
+
throw createValidationBlocker(
|
|
36
38
|
`Command output path already exists and will not be moved or overwritten: ${outputPath}. ` +
|
|
37
|
-
'The validator will not delete pre-existing output.
|
|
38
|
-
|
|
39
|
+
'The validator will not delete pre-existing output.',
|
|
40
|
+
{
|
|
41
|
+
kind: 'command-output-exists',
|
|
42
|
+
recommendation: `Inspect ${outputPath}. Remove it manually only if it is disposable validator or test ` +
|
|
43
|
+
'output, or choose a fresh output path, then render and approve a fresh plan.',
|
|
44
|
+
}
|
|
39
45
|
)
|
|
40
46
|
}
|
|
41
47
|
states.push({
|
|
@@ -71,21 +77,21 @@ function cleanupCommandOutputs (states) {
|
|
|
71
77
|
*
|
|
72
78
|
* @param {string} outputPath output path
|
|
73
79
|
* @param {string} repositoryRoot repository root
|
|
74
|
-
* @returns {{path: string, dev:
|
|
80
|
+
* @returns {{path: string, dev: bigint, ino: bigint}[]} parent identities
|
|
75
81
|
*/
|
|
76
82
|
function captureExistingParentIdentities (outputPath, repositoryRoot) {
|
|
77
83
|
const identities = []
|
|
78
84
|
const relative = path.relative(repositoryRoot, path.dirname(outputPath))
|
|
79
85
|
let current = repositoryRoot
|
|
80
86
|
for (const segment of relative ? relative.split(path.sep) : []) {
|
|
81
|
-
const stat = fs.lstatSync(current)
|
|
87
|
+
const stat = fs.lstatSync(current, { bigint: true })
|
|
82
88
|
assertRegularDirectory(stat, current)
|
|
83
89
|
identities.push({ path: current, dev: stat.dev, ino: stat.ino })
|
|
84
90
|
current = path.join(current, segment)
|
|
85
91
|
if (!pathExists(current)) return identities
|
|
86
92
|
}
|
|
87
93
|
|
|
88
|
-
const stat = fs.lstatSync(current)
|
|
94
|
+
const stat = fs.lstatSync(current, { bigint: true })
|
|
89
95
|
assertRegularDirectory(stat, current)
|
|
90
96
|
identities.push({ path: current, dev: stat.dev, ino: stat.ino })
|
|
91
97
|
return identities
|
|
@@ -98,7 +104,7 @@ function captureExistingParentIdentities (outputPath, repositoryRoot) {
|
|
|
98
104
|
*/
|
|
99
105
|
function assertOutputParentsUnchanged (state) {
|
|
100
106
|
for (const identity of state.parentIdentities) {
|
|
101
|
-
const stat = fs.lstatSync(identity.path)
|
|
107
|
+
const stat = fs.lstatSync(identity.path, { bigint: true })
|
|
102
108
|
assertRegularDirectory(stat, identity.path)
|
|
103
109
|
if (stat.dev !== identity.dev || stat.ino !== identity.ino) {
|
|
104
110
|
throw new Error(`Refusing command output cleanup because a parent directory changed: ${identity.path}`)
|
|
@@ -118,7 +124,7 @@ function assertOutputParentsUnchanged (state) {
|
|
|
118
124
|
/**
|
|
119
125
|
* Refuses symbolic links and non-directory parent components.
|
|
120
126
|
*
|
|
121
|
-
* @param {fs.Stats} stat path status
|
|
127
|
+
* @param {fs.Stats | fs.BigIntStats} stat path status
|
|
122
128
|
* @param {string} directory directory path
|
|
123
129
|
*/
|
|
124
130
|
function assertRegularDirectory (stat, directory) {
|
|
@@ -522,7 +522,7 @@ function buildOfflineCaptureEnv ({ fixture, outputRoot }) {
|
|
|
522
522
|
* @param {object} input offline validation inputs
|
|
523
523
|
* @param {{manifestPath: string}} input.fixture authoritative cache fixture
|
|
524
524
|
* @param {string} input.outputRoot pre-created payload output root
|
|
525
|
-
* @returns {
|
|
525
|
+
* @returns {typeof process.env} validation transport environment
|
|
526
526
|
*/
|
|
527
527
|
function buildOfflineValidationEnv ({ fixture, outputRoot }) {
|
|
528
528
|
return {
|
|
@@ -551,15 +551,17 @@ function buildOfflineValidationEnv ({ fixture, outputRoot }) {
|
|
|
551
551
|
* Rejects command-local assignments that can bypass validator-controlled offline routing.
|
|
552
552
|
*
|
|
553
553
|
* @param {object} command command to execute
|
|
554
|
-
* @param {
|
|
554
|
+
* @param {typeof process.env} env validator environment overrides
|
|
555
555
|
*/
|
|
556
556
|
function assertNoInlineValidationEnvOverrides (command, env) {
|
|
557
557
|
if (!env[VALIDATION_MODE_ENV]) return
|
|
558
|
-
|
|
559
|
-
const
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
558
|
+
if (command.env) {
|
|
559
|
+
for (const name of Object.keys(command.env)) {
|
|
560
|
+
const normalized = process.platform === 'win32' ? name.toUpperCase() : name
|
|
561
|
+
if (VALIDATION_RESERVED_ENV_NAMES.some(reserved => environmentNamesEqual(reserved, name)) ||
|
|
562
|
+
isDatadogEnvironmentName(name) || normalized.startsWith('OTEL_')) {
|
|
563
|
+
throw new Error(`Direct-runner adapter must not override validator-controlled environment variable ${name}.`)
|
|
564
|
+
}
|
|
563
565
|
}
|
|
564
566
|
}
|
|
565
567
|
}
|
|
@@ -63,8 +63,10 @@ function setEnvironmentValue (environment, name, value, platform = process.platf
|
|
|
63
63
|
* @returns {void}
|
|
64
64
|
*/
|
|
65
65
|
function mergeEnvironment (target, source, platform = process.platform) {
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
if (source) {
|
|
67
|
+
for (const [name, value] of Object.entries(source)) {
|
|
68
|
+
setEnvironmentValue(target, name, value, platform)
|
|
69
|
+
}
|
|
68
70
|
}
|
|
69
71
|
}
|
|
70
72
|
|
|
@@ -40,19 +40,21 @@ function getResolvedExecutable (command) {
|
|
|
40
40
|
*/
|
|
41
41
|
function bindManifestExecutables (manifest) {
|
|
42
42
|
const identities = []
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
43
|
+
if (manifest.frameworks) {
|
|
44
|
+
for (const framework of manifest.frameworks) {
|
|
45
|
+
if (framework.status !== 'runnable') continue
|
|
46
|
+
const command = getManifestCommands({ frameworks: [framework] })[0]?.[1]
|
|
47
|
+
try {
|
|
48
|
+
const identity = getCommandExecutableIdentity(command, manifest.repository.root)
|
|
49
|
+
bindApprovedExecutable(framework.validation, identity)
|
|
50
|
+
identities.push({ id: `framework:${framework.id}`, ...identity })
|
|
51
|
+
} catch (error) {
|
|
52
|
+
identities.push({
|
|
53
|
+
id: `framework:${framework.id}`,
|
|
54
|
+
unavailable: true,
|
|
55
|
+
reason: error?.message || String(error),
|
|
56
|
+
})
|
|
57
|
+
}
|
|
56
58
|
}
|
|
57
59
|
}
|
|
58
60
|
return identities
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs')
|
|
4
|
+
const path = require('node:path')
|
|
5
|
+
|
|
6
|
+
const { createFileSafely } = require('./safe-files')
|
|
7
|
+
const { createValidationBlocker } = require('./validation-blocker')
|
|
8
|
+
|
|
9
|
+
const EXECUTION_LOCK_FILENAME = '.dd-test-optimization-validation.lock'
|
|
10
|
+
|
|
11
|
+
function getExecutionLockPath (out) {
|
|
12
|
+
return path.join(path.resolve(out), EXECUTION_LOCK_FILENAME)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Locks are never reclaimed automatically; only the creator may release the exact file identity.
|
|
16
|
+
function acquireExecutionLock ({ out, approvedPlanSha256 }) {
|
|
17
|
+
const lockPath = getExecutionLockPath(out)
|
|
18
|
+
const content = `${JSON.stringify({
|
|
19
|
+
approvedPlanSha256,
|
|
20
|
+
pid: process.pid,
|
|
21
|
+
startedAt: new Date().toISOString(),
|
|
22
|
+
}, null, 2)}\n`
|
|
23
|
+
try {
|
|
24
|
+
createFileSafely(out, lockPath, content, 'validation execution lock')
|
|
25
|
+
} catch (error) {
|
|
26
|
+
if (error.code === 'EEXIST') throw getExistingLockError(lockPath)
|
|
27
|
+
throw error
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const stat = fs.lstatSync(lockPath, { bigint: true })
|
|
31
|
+
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
32
|
+
throw new Error(`Validation execution lock is not a regular file: ${lockPath}`)
|
|
33
|
+
}
|
|
34
|
+
return { dev: stat.dev, ino: stat.ino, path: lockPath }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function releaseExecutionLock (lock) {
|
|
38
|
+
let stat
|
|
39
|
+
try {
|
|
40
|
+
stat = fs.lstatSync(lock.path, { bigint: true })
|
|
41
|
+
} catch (error) {
|
|
42
|
+
if (error.code === 'ENOENT') {
|
|
43
|
+
throw new Error(`Validation execution lock disappeared before safe cleanup: ${lock.path}`)
|
|
44
|
+
}
|
|
45
|
+
throw error
|
|
46
|
+
}
|
|
47
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.dev !== lock.dev || stat.ino !== lock.ino) {
|
|
48
|
+
throw new Error(`Refusing to remove a changed validation execution lock: ${lock.path}`)
|
|
49
|
+
}
|
|
50
|
+
fs.unlinkSync(lock.path)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function getExistingLockError (lockPath) {
|
|
54
|
+
return createValidationBlocker(
|
|
55
|
+
`Another validation may be active, or an interrupted run left its execution lock: ${lockPath}.`,
|
|
56
|
+
{
|
|
57
|
+
kind: 'execution-lock-exists',
|
|
58
|
+
recommendation: `Inspect ${lockPath}. Remove only that lock after confirming no validation process is active, ` +
|
|
59
|
+
'then render and approve a fresh plan.',
|
|
60
|
+
suppressReport: true,
|
|
61
|
+
}
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = {
|
|
66
|
+
EXECUTION_LOCK_FILENAME,
|
|
67
|
+
acquireExecutionLock,
|
|
68
|
+
getExecutionLockPath,
|
|
69
|
+
releaseExecutionLock,
|
|
70
|
+
}
|