dd-trace 6.4.0 → 6.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.electron.md +7 -0
- package/README.md +17 -0
- package/ci/diagnose.js +2100 -0
- package/ci/init.js +23 -2
- package/ci/runbook.md +198 -0
- package/ci/test-optimization-validation/approval-artifacts.js +143 -0
- package/ci/test-optimization-validation/approval.js +299 -0
- package/ci/test-optimization-validation/artifact-id.js +20 -0
- package/ci/test-optimization-validation/bounded-json.js +121 -0
- package/ci/test-optimization-validation/ci-command-candidate.js +83 -0
- package/ci/test-optimization-validation/ci-discovery.js +181 -0
- package/ci/test-optimization-validation/ci-remediation.js +210 -0
- package/ci/test-optimization-validation/cli.js +903 -0
- package/ci/test-optimization-validation/command-blocker.js +81 -0
- package/ci/test-optimization-validation/command-output-policy.js +206 -0
- package/ci/test-optimization-validation/command-runner.js +707 -0
- package/ci/test-optimization-validation/command-suitability.js +471 -0
- package/ci/test-optimization-validation/executable-approval.js +48 -0
- package/ci/test-optimization-validation/executable.js +480 -0
- package/ci/test-optimization-validation/generated-file-policy.js +63 -0
- package/ci/test-optimization-validation/generated-files.js +351 -0
- package/ci/test-optimization-validation/generated-verifier.js +196 -0
- package/ci/test-optimization-validation/init-probe-preload.js +163 -0
- package/ci/test-optimization-validation/init-probe.js +246 -0
- package/ci/test-optimization-validation/late-initialization.js +63 -0
- package/ci/test-optimization-validation/local-command.js +163 -0
- package/ci/test-optimization-validation/manifest-loader.js +133 -0
- package/ci/test-optimization-validation/manifest-scaffold.js +738 -0
- package/ci/test-optimization-validation/manifest-schema.js +862 -0
- package/ci/test-optimization-validation/offline-fixtures.js +327 -0
- package/ci/test-optimization-validation/offline-output.js +406 -0
- package/ci/test-optimization-validation/payload-normalizer.js +72 -0
- package/ci/test-optimization-validation/plan-writer.js +1120 -0
- package/ci/test-optimization-validation/preflight-runner.js +114 -0
- package/ci/test-optimization-validation/redaction.js +331 -0
- package/ci/test-optimization-validation/report-writer.js +1508 -0
- package/ci/test-optimization-validation/safe-files.js +203 -0
- package/ci/test-optimization-validation/scenarios/auto-test-retries.js +159 -0
- package/ci/test-optimization-validation/scenarios/basic-reporting.js +657 -0
- package/ci/test-optimization-validation/scenarios/ci-wiring.js +780 -0
- package/ci/test-optimization-validation/scenarios/early-flake-detection.js +141 -0
- package/ci/test-optimization-validation/scenarios/helpers.js +539 -0
- package/ci/test-optimization-validation/scenarios/test-management.js +218 -0
- package/ci/test-optimization-validation/setup-runner.js +97 -0
- package/ci/test-optimization-validation/static-diagnosis.js +159 -0
- package/ci/test-optimization-validation/test-output.js +129 -0
- package/ci/test-optimization-validation-manifest.schema.json +1 -0
- package/ci/validate-test-optimization.js +3 -0
- package/ext/exporters.js +1 -0
- package/index.d.ts +121 -8
- package/init.js +18 -0
- package/initialize.mjs +1 -1
- package/loader-hook.mjs +28 -18
- package/openfeature.d.ts +1 -0
- package/openfeature.js +4 -0
- package/package.json +10 -7
- package/packages/datadog-instrumentations/src/ai.js +8 -2
- package/packages/datadog-instrumentations/src/child_process.js +1 -1
- package/packages/datadog-instrumentations/src/claude-agent-sdk.js +1 -1
- package/packages/datadog-instrumentations/src/cucumber-worker-threads.js +9 -3
- package/packages/datadog-instrumentations/src/cucumber.js +20 -7
- package/packages/datadog-instrumentations/src/cypress-config.js +398 -52
- package/packages/datadog-instrumentations/src/fastify.js +7 -11
- package/packages/datadog-instrumentations/src/helpers/hook.js +27 -6
- package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +8 -8
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +28 -2
- package/packages/datadog-instrumentations/src/jest.js +26 -0
- package/packages/datadog-instrumentations/src/mariadb.js +1 -1
- package/packages/datadog-instrumentations/src/mocha/main.js +0 -2
- package/packages/datadog-instrumentations/src/mocha/utils.js +0 -4
- package/packages/datadog-instrumentations/src/mongodb.js +3 -3
- package/packages/datadog-instrumentations/src/mongoose.js +3 -3
- package/packages/datadog-instrumentations/src/mquery.js +2 -2
- package/packages/datadog-instrumentations/src/mysql.js +1 -1
- package/packages/datadog-instrumentations/src/next.js +24 -0
- package/packages/datadog-instrumentations/src/nyc.js +0 -2
- package/packages/datadog-instrumentations/src/oracledb.js +2 -2
- package/packages/datadog-instrumentations/src/pg.js +2 -2
- package/packages/datadog-instrumentations/src/playwright.js +159 -22
- package/packages/datadog-instrumentations/src/process.js +3 -0
- package/packages/datadog-instrumentations/src/router.js +18 -8
- package/packages/datadog-instrumentations/src/selenium.js +52 -26
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +2 -2
- package/packages/datadog-instrumentations/src/vitest-main.js +0 -2
- package/packages/datadog-instrumentations/src/ws.js +2 -1
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +2 -2
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +2 -1
- package/packages/datadog-plugin-aws-sdk/src/util.js +2 -2
- package/packages/datadog-plugin-cucumber/src/index.js +1 -1
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +19 -57
- package/packages/datadog-plugin-cypress/src/index.js +6 -1
- package/packages/datadog-plugin-cypress/src/support.js +92 -26
- package/packages/datadog-plugin-electron/src/ipc.js +1 -1
- package/packages/datadog-plugin-graphql/src/execute.js +87 -45
- package/packages/datadog-plugin-graphql/src/validate.js +2 -2
- package/packages/datadog-plugin-http2/src/client.js +1 -1
- package/packages/datadog-plugin-langchain/src/handlers/embedding.js +2 -1
- package/packages/datadog-plugin-langchain/src/handlers/language_models.js +2 -1
- package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
- package/packages/datadog-plugin-playwright/src/index.js +177 -64
- package/packages/datadog-plugin-router/src/index.js +11 -2
- package/packages/datadog-plugin-selenium/src/index.js +5 -36
- package/packages/datadog-plugin-ws/src/close.js +2 -1
- package/packages/datadog-plugin-ws/src/producer.js +2 -1
- package/packages/datadog-plugin-ws/src/receiver.js +2 -1
- package/packages/datadog-plugin-ws/src/server.js +2 -1
- package/packages/dd-trace/src/aiguard/sdk.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +2 -2
- package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +3 -1
- package/packages/dd-trace/src/azure_metadata.js +2 -1
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +93 -28
- package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/index.js +160 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +288 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/payload-projection.js +84 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +369 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +60 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +41 -3
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +7 -2
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +1 -1
- package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +6 -2
- package/packages/dd-trace/src/ci-visibility/rum.js +7 -0
- package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +4 -0
- package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +1 -1
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +137 -9
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +168 -8
- package/packages/dd-trace/src/ci-visibility/test-screenshot.js +90 -0
- package/packages/dd-trace/src/config/generated-config-types.d.ts +16 -0
- package/packages/dd-trace/src/config/helper.js +1 -0
- package/packages/dd-trace/src/config/index.js +10 -0
- package/packages/dd-trace/src/config/supported-configurations.json +66 -0
- package/packages/dd-trace/src/exporter.js +2 -0
- package/packages/dd-trace/src/exporters/common/client-library-headers.js +21 -0
- package/packages/dd-trace/src/exporters/common/request.js +59 -30
- package/packages/dd-trace/src/exporters/common/url.js +15 -1
- package/packages/dd-trace/src/feature-registry.js +10 -3
- package/packages/dd-trace/src/llmobs/experiments/client.js +113 -0
- package/packages/dd-trace/src/llmobs/experiments/dataset.js +154 -0
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +283 -0
- package/packages/dd-trace/src/llmobs/experiments/index.js +152 -0
- package/packages/dd-trace/src/llmobs/experiments/noop.js +30 -0
- package/packages/dd-trace/src/llmobs/experiments/result.js +34 -0
- package/packages/dd-trace/src/llmobs/noop.js +6 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +20 -3
- package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +2 -2
- package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/openai/constants.js +8 -0
- package/packages/dd-trace/src/llmobs/plugins/openai/index.js +48 -14
- package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +43 -0
- package/packages/dd-trace/src/llmobs/sdk.js +17 -0
- package/packages/dd-trace/src/llmobs/tagger.js +68 -0
- package/packages/dd-trace/src/llmobs/telemetry.js +2 -1
- package/packages/dd-trace/src/llmobs/util.js +32 -0
- package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +322 -0
- package/packages/dd-trace/src/openfeature/configuration_source.js +90 -0
- package/packages/dd-trace/src/openfeature/flagging_provider.js +14 -23
- package/packages/dd-trace/src/openfeature/index.js +0 -2
- package/packages/dd-trace/src/openfeature/noop.js +1 -28
- package/packages/dd-trace/src/openfeature/register.js +16 -27
- package/packages/dd-trace/src/openfeature/remote_config.js +15 -18
- package/packages/dd-trace/src/openfeature/require-provider.js +18 -0
- package/packages/dd-trace/src/opentelemetry/span-ending-hook.js +10 -0
- package/packages/dd-trace/src/opentelemetry/span.js +5 -0
- package/packages/dd-trace/src/plugins/ci_plugin.js +20 -6
- package/packages/dd-trace/src/plugins/util/git.js +3 -2
- package/packages/dd-trace/src/plugins/util/inferred_proxy.js +29 -5
- package/packages/dd-trace/src/plugins/util/test.js +56 -4
- package/packages/dd-trace/src/proxy.js +84 -7
- package/packages/dd-trace/src/span_processor.js +5 -0
- package/packages/dd-trace/src/standalone/index.js +0 -14
- package/packages/dd-trace/src/telemetry/send-data.js +2 -2
- package/packages/dd-trace/src/util.js +26 -0
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +1 -1
- package/vendor/dist/@datadog/sketches-js/index.js +1 -1
- package/vendor/dist/protobufjs/index.js +1 -1
- package/vendor/dist/protobufjs/minimal/index.js +1 -1
- package/vendor/dist/shell-quote/index.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/require-optional-peer.js +0 -17
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const FILESYSTEM_PERMISSION_PATTERN = /\b(?:EACCES|EPERM|Operation not permitted|Permission denied)\b/i
|
|
4
|
+
const PACKAGE_MANAGER_PATH_PATTERN = /(?:^|[/\\.])(?:corepack|npm|pnpm|yarn)(?:$|[/\\.])/i
|
|
5
|
+
const WATCHMAN_PATTERN = /\bwatchman\b/i
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Identifies toolchain and execution-environment failures that happen before tests start.
|
|
9
|
+
*
|
|
10
|
+
* @param {object} result command result
|
|
11
|
+
* @param {string} [result.stdout] captured stdout
|
|
12
|
+
* @param {string} [result.stderr] captured stderr
|
|
13
|
+
* @returns {object|undefined} structured blocker diagnosis
|
|
14
|
+
*/
|
|
15
|
+
function getCommandBlocker (result) {
|
|
16
|
+
const output = `${result.stdout || ''}\n${result.stderr || ''}`
|
|
17
|
+
const yarnVersions = output.match(
|
|
18
|
+
/defines "packageManager": "(yarn@[^"]+)"[\s\S]*?current global version of Yarn is ([0-9][0-9.]*)\./i
|
|
19
|
+
)
|
|
20
|
+
if (yarnVersions) {
|
|
21
|
+
return {
|
|
22
|
+
kind: 'package-manager-version-mismatch',
|
|
23
|
+
summary: `The test command did not start because it resolved Yarn ${yarnVersions[2]}, but package.json ` +
|
|
24
|
+
`requires ${yarnVersions[1]}. No Test Optimization conclusion was reached.`,
|
|
25
|
+
recommendation: 'Run the approved command through the project-declared Yarn version, using its configured ' +
|
|
26
|
+
'`yarnPath` or an explicit Corepack Yarn command, then render and approve a fresh plan.',
|
|
27
|
+
signals: getMatchingLines(output, /packageManager|current global version of Yarn/i),
|
|
28
|
+
toolchainBlocked: true,
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (WATCHMAN_PATTERN.test(output) && FILESYSTEM_PERMISSION_PATTERN.test(output)) {
|
|
33
|
+
return {
|
|
34
|
+
kind: 'watchman-filesystem-blocked',
|
|
35
|
+
summary: 'The execution environment blocked Watchman state access before tests started. No CI wiring or ' +
|
|
36
|
+
'Test Optimization conclusion was reached.',
|
|
37
|
+
recommendation: 'Rerun in an environment where Watchman can access its state directory. If the CI job ' +
|
|
38
|
+
'itself disables Watchman, preserve that exact setting in the replay command.',
|
|
39
|
+
signals: getMatchingLines(output, /watchman|EACCES|EPERM|Operation not permitted|Permission denied/i),
|
|
40
|
+
blockedByExecutionEnvironment: true,
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const permissionLines = getMatchingLines(
|
|
45
|
+
output,
|
|
46
|
+
/EACCES|EPERM|Operation not permitted|Permission denied/i
|
|
47
|
+
)
|
|
48
|
+
if (permissionLines.some(line => PACKAGE_MANAGER_PATH_PATTERN.test(line))) {
|
|
49
|
+
return {
|
|
50
|
+
kind: 'package-manager-filesystem-blocked',
|
|
51
|
+
summary: 'The test command did not start because the package manager could not write to its tool or cache ' +
|
|
52
|
+
'directory in this execution environment. No Test Optimization conclusion was reached.',
|
|
53
|
+
recommendation: 'Rerun with the project package manager already installed and a writable package-manager ' +
|
|
54
|
+
'home or cache directory. Do not interpret this launcher failure as a Test Optimization problem.',
|
|
55
|
+
signals: permissionLines,
|
|
56
|
+
blockedByExecutionEnvironment: true,
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Returns a small de-duplicated set of matching output lines.
|
|
63
|
+
*
|
|
64
|
+
* @param {string} output command output
|
|
65
|
+
* @param {RegExp} pattern interesting-line pattern
|
|
66
|
+
* @returns {string[]} matching lines
|
|
67
|
+
*/
|
|
68
|
+
function getMatchingLines (output, pattern) {
|
|
69
|
+
const lines = []
|
|
70
|
+
const seen = new Set()
|
|
71
|
+
for (const line of output.split(/\r?\n/)) {
|
|
72
|
+
const value = line.trim()
|
|
73
|
+
if (!value || seen.has(value) || !pattern.test(value)) continue
|
|
74
|
+
seen.add(value)
|
|
75
|
+
lines.push(value)
|
|
76
|
+
if (lines.length === 6) break
|
|
77
|
+
}
|
|
78
|
+
return lines
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports = { getCommandBlocker }
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs')
|
|
4
|
+
const path = require('node:path')
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Returns command-created paths that must be declared and removed after validation.
|
|
8
|
+
*
|
|
9
|
+
* @param {object} command structured command
|
|
10
|
+
* @returns {string[]} absolute output paths
|
|
11
|
+
*/
|
|
12
|
+
function getCommandOutputPaths (command) {
|
|
13
|
+
const paths = new Set((command.outputPaths || []).map(outputPath => path.resolve(command.cwd, outputPath)))
|
|
14
|
+
const tokens = command.usesShell ? tokenizeShell(command.shellCommand) : command.argv || []
|
|
15
|
+
const coverageDirectory = getCoverageDirectory(tokens)
|
|
16
|
+
if (coverageDirectory) paths.add(path.resolve(command.cwd, coverageDirectory))
|
|
17
|
+
return [...paths]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Refuses pre-existing outputs and records parent identities for fail-closed cleanup.
|
|
22
|
+
*
|
|
23
|
+
* @param {object} input isolation inputs
|
|
24
|
+
* @param {object} input.command structured command
|
|
25
|
+
* @param {string} input.artifactRoot validation results root
|
|
26
|
+
* @param {string} [input.repositoryRoot] repository root
|
|
27
|
+
* @returns {{outputPath: string, repositoryRoot: string, parentIdentities: object[]}[]} cleanup state
|
|
28
|
+
*/
|
|
29
|
+
function prepareCommandOutputs ({ command, artifactRoot, repositoryRoot }) {
|
|
30
|
+
repositoryRoot = path.resolve(repositoryRoot || path.dirname(path.resolve(artifactRoot)))
|
|
31
|
+
const states = []
|
|
32
|
+
const outputPaths = getCommandOutputPaths(command)
|
|
33
|
+
|
|
34
|
+
for (const outputPath of outputPaths) {
|
|
35
|
+
assertSafeOutputPath({ outputPath, repositoryRoot, artifactRoot, command })
|
|
36
|
+
}
|
|
37
|
+
for (const outputPath of outputPaths) {
|
|
38
|
+
if (pathExists(outputPath)) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`Command output path already exists and will not be moved or overwritten: ${outputPath}. ` +
|
|
41
|
+
'Remove it or choose a command that writes to a fresh output path, then render a new approval plan.'
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
states.push({
|
|
45
|
+
outputPath,
|
|
46
|
+
repositoryRoot,
|
|
47
|
+
parentIdentities: captureExistingParentIdentities(outputPath, repositoryRoot),
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return states
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Removes outputs created by a command after revalidating every parent component.
|
|
56
|
+
*
|
|
57
|
+
* @param {{outputPath: string, repositoryRoot: string, parentIdentities: object[]}[]} states cleanup state
|
|
58
|
+
* @returns {object[]} customer-safe cleanup summary
|
|
59
|
+
*/
|
|
60
|
+
function cleanupCommandOutputs (states) {
|
|
61
|
+
const actions = []
|
|
62
|
+
for (let index = states.length - 1; index >= 0; index--) {
|
|
63
|
+
const state = states[index]
|
|
64
|
+
assertOutputParentsUnchanged(state)
|
|
65
|
+
const existed = fs.existsSync(state.outputPath)
|
|
66
|
+
if (existed) fs.rmSync(state.outputPath, { force: true, recursive: true })
|
|
67
|
+
actions.push({ outputPath: state.outputPath, action: existed ? 'removed' : 'absent' })
|
|
68
|
+
}
|
|
69
|
+
return actions.reverse()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Records every existing directory from repository.root through an output parent.
|
|
74
|
+
*
|
|
75
|
+
* @param {string} outputPath output path
|
|
76
|
+
* @param {string} repositoryRoot repository root
|
|
77
|
+
* @returns {{path: string, dev: number, ino: number}[]} parent identities
|
|
78
|
+
*/
|
|
79
|
+
function captureExistingParentIdentities (outputPath, repositoryRoot) {
|
|
80
|
+
const identities = []
|
|
81
|
+
const relative = path.relative(repositoryRoot, path.dirname(outputPath))
|
|
82
|
+
let current = repositoryRoot
|
|
83
|
+
for (const segment of relative ? relative.split(path.sep) : []) {
|
|
84
|
+
const stat = fs.lstatSync(current)
|
|
85
|
+
assertRegularDirectory(stat, current)
|
|
86
|
+
identities.push({ path: current, dev: stat.dev, ino: stat.ino })
|
|
87
|
+
current = path.join(current, segment)
|
|
88
|
+
if (!pathExists(current)) return identities
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const stat = fs.lstatSync(current)
|
|
92
|
+
assertRegularDirectory(stat, current)
|
|
93
|
+
identities.push({ path: current, dev: stat.dev, ino: stat.ino })
|
|
94
|
+
return identities
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Refuses cleanup if an existing parent changed or a new parent is a symbolic link.
|
|
99
|
+
*
|
|
100
|
+
* @param {object} state cleanup state
|
|
101
|
+
*/
|
|
102
|
+
function assertOutputParentsUnchanged (state) {
|
|
103
|
+
for (const identity of state.parentIdentities) {
|
|
104
|
+
const stat = fs.lstatSync(identity.path)
|
|
105
|
+
assertRegularDirectory(stat, identity.path)
|
|
106
|
+
if (stat.dev !== identity.dev || stat.ino !== identity.ino) {
|
|
107
|
+
throw new Error(`Refusing command output cleanup because a parent directory changed: ${identity.path}`)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const lastExisting = state.parentIdentities.at(-1).path
|
|
112
|
+
const relative = path.relative(lastExisting, path.dirname(state.outputPath))
|
|
113
|
+
let current = lastExisting
|
|
114
|
+
for (const segment of relative ? relative.split(path.sep) : []) {
|
|
115
|
+
current = path.join(current, segment)
|
|
116
|
+
if (!pathExists(current)) break
|
|
117
|
+
assertRegularDirectory(fs.lstatSync(current), current)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Refuses symbolic links and non-directory parent components.
|
|
123
|
+
*
|
|
124
|
+
* @param {fs.Stats} stat path status
|
|
125
|
+
* @param {string} directory directory path
|
|
126
|
+
*/
|
|
127
|
+
function assertRegularDirectory (stat, directory) {
|
|
128
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
129
|
+
throw new Error(`Refusing command output cleanup through a non-regular directory: ${directory}`)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function pathExists (filename) {
|
|
134
|
+
try {
|
|
135
|
+
fs.lstatSync(filename)
|
|
136
|
+
return true
|
|
137
|
+
} catch (error) {
|
|
138
|
+
if (error.code === 'ENOENT') return false
|
|
139
|
+
throw error
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function getCoverageDirectory (tokens) {
|
|
144
|
+
let coverageEnabled = false
|
|
145
|
+
for (let index = 0; index < tokens.length; index++) {
|
|
146
|
+
const token = String(tokens[index])
|
|
147
|
+
if (token === '--coverage' || token === '--coverage=true') coverageEnabled = true
|
|
148
|
+
const inline = /^(?:--coverageDirectory|--coverage-directory|--coverage\.reportsDirectory)=(.+)$/.exec(token)
|
|
149
|
+
if (inline) return inline[1]
|
|
150
|
+
if (['--coverageDirectory', '--coverage-directory', '--coverage.reportsDirectory'].includes(token)) {
|
|
151
|
+
return tokens[index + 1]
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return coverageEnabled ? 'coverage' : undefined
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function tokenizeShell (source) {
|
|
158
|
+
return String(source || '').match(/"[^"]*"|'[^']*'|[^\s]+/g)?.map(token => {
|
|
159
|
+
return token.replace(/^(?:"([\s\S]*)"|'([\s\S]*)')$/, '$1$2')
|
|
160
|
+
}) || []
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function assertSafeOutputPath ({ outputPath, repositoryRoot, artifactRoot, command }) {
|
|
164
|
+
const relative = path.relative(repositoryRoot, outputPath)
|
|
165
|
+
if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
166
|
+
throw new Error(`Command output path must be a child of repository.root: ${outputPath}`)
|
|
167
|
+
}
|
|
168
|
+
assertPhysicalOutputPathInsideRepository(outputPath, repositoryRoot)
|
|
169
|
+
if (isPathInside(artifactRoot, outputPath) || isPathInside(outputPath, artifactRoot)) {
|
|
170
|
+
throw new Error(`Command output path must not contain or overlap validation artifacts: ${outputPath}`)
|
|
171
|
+
}
|
|
172
|
+
if (path.resolve(command.cwd) === outputPath) {
|
|
173
|
+
throw new Error(`Command output path must not replace the command working directory: ${outputPath}`)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Rejects an output whose nearest existing ancestor resolves outside the repository.
|
|
179
|
+
*
|
|
180
|
+
* @param {string} outputPath absolute output path
|
|
181
|
+
* @param {string} repositoryRoot absolute repository root
|
|
182
|
+
* @returns {void}
|
|
183
|
+
*/
|
|
184
|
+
function assertPhysicalOutputPathInsideRepository (outputPath, repositoryRoot) {
|
|
185
|
+
const physicalRoot = fs.realpathSync(repositoryRoot)
|
|
186
|
+
let existingPath = outputPath
|
|
187
|
+
while (!fs.existsSync(existingPath) && path.dirname(existingPath) !== existingPath) {
|
|
188
|
+
existingPath = path.dirname(existingPath)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const physicalExistingPath = fs.realpathSync(existingPath)
|
|
192
|
+
if (!isPathInside(physicalRoot, physicalExistingPath)) {
|
|
193
|
+
throw new Error(`Command output path must not resolve outside physical repository.root: ${outputPath}`)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function isPathInside (directory, filename) {
|
|
198
|
+
const relative = path.relative(path.resolve(directory), path.resolve(filename))
|
|
199
|
+
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative))
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
module.exports = {
|
|
203
|
+
cleanupCommandOutputs,
|
|
204
|
+
getCommandOutputPaths,
|
|
205
|
+
prepareCommandOutputs,
|
|
206
|
+
}
|