diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c3ff9b11f7..3ef9114a05 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -9,7 +9,8 @@ packages/plugin-deployment @DataDog/datadog-ci-admins @DataD packages/base/src/commands/deployment @DataDog/datadog-ci-admins @DataDog/ci-app-libraries @DataDog/ci-app-backend packages/plugin-dora @DataDog/datadog-ci-admins @DataDog/ci-app-libraries @DataDog/ci-app-backend packages/base/src/commands/dora @DataDog/datadog-ci-admins @DataDog/ci-app-libraries @DataDog/ci-app-backend -packages/datadog-ci/src/commands/junit @DataDog/datadog-ci-admins @DataDog/ci-app-libraries @DataDog/ci-app-backend +packages/plugin-junit @DataDog/datadog-ci-admins @DataDog/ci-app-libraries @DataDog/ci-app-backend +packages/base/src/commands/junit @DataDog/datadog-ci-admins @DataDog/ci-app-libraries @DataDog/ci-app-backend packages/datadog-ci/src/commands/measure @DataDog/datadog-ci-admins @DataDog/ci-app-libraries @DataDog/ci-app-backend packages/base/src/commands/tag @DataDog/datadog-ci-admins @DataDog/ci-app-libraries @DataDog/ci-app-backend packages/datadog-ci/src/commands/trace @DataDog/datadog-ci-admins @DataDog/ci-app-libraries @DataDog/ci-app-backend diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b0ddd16388..5f34b4f47e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -124,6 +124,7 @@ jobs: "@datadog/datadog-ci-plugin-deployment": "file:./artifacts/@datadog-datadog-ci-plugin-deployment-${{ matrix.version }}.tgz", "@datadog/datadog-ci-plugin-dora": "file:./artifacts/@datadog-datadog-ci-plugin-dora-${{ matrix.version }}.tgz", "@datadog/datadog-ci-plugin-gate": "file:./artifacts/@datadog-datadog-ci-plugin-gate-${{ matrix.version }}.tgz", + "@datadog/datadog-ci-plugin-junit": "file:./artifacts/@datadog-datadog-ci-plugin-junit-${{ matrix.version }}.tgz", "@datadog/datadog-ci-plugin-sarif": "file:./artifacts/@datadog-datadog-ci-plugin-sarif-${{ matrix.version }}.tgz", "@datadog/datadog-ci-plugin-sbom": "file:./artifacts/@datadog-datadog-ci-plugin-sbom-${{ matrix.version }}.tgz" }, @@ -230,6 +231,7 @@ jobs: "@datadog/datadog-ci-plugin-deployment": "file:./artifacts/@datadog-datadog-ci-plugin-deployment-${{ matrix.version }}.tgz", "@datadog/datadog-ci-plugin-dora": "file:./artifacts/@datadog-datadog-ci-plugin-dora-${{ matrix.version }}.tgz", "@datadog/datadog-ci-plugin-gate": "file:./artifacts/@datadog-datadog-ci-plugin-gate-${{ matrix.version }}.tgz", + "@datadog/datadog-ci-plugin-junit": "file:./artifacts/@datadog-datadog-ci-plugin-junit-${{ matrix.version }}.tgz", "@datadog/datadog-ci-plugin-sarif": "file:./artifacts/@datadog-datadog-ci-plugin-sarif-${{ matrix.version }}.tgz", "@datadog/datadog-ci-plugin-sbom": "file:./artifacts/@datadog-datadog-ci-plugin-sbom-${{ matrix.version }}.tgz" }, @@ -325,6 +327,7 @@ jobs: -p ./artifacts/@datadog-datadog-ci-plugin-deployment-20.tgz \ -p ./artifacts/@datadog-datadog-ci-plugin-dora-20.tgz \ -p ./artifacts/@datadog-datadog-ci-plugin-gate-20.tgz \ + -p ./artifacts/@datadog-datadog-ci-plugin-junit-20.tgz \ -p ./artifacts/@datadog-datadog-ci-plugin-sarif-20.tgz \ -p ./artifacts/@datadog-datadog-ci-plugin-sbom-20.tgz \ datadog-ci aas instrument || true @@ -370,6 +373,7 @@ jobs: "@datadog/datadog-ci-plugin-deployment": "file:./artifacts/@datadog-datadog-ci-plugin-deployment-20.tgz", "@datadog/datadog-ci-plugin-dora": "file:./artifacts/@datadog-datadog-ci-plugin-dora-20.tgz", "@datadog/datadog-ci-plugin-gate": "file:./artifacts/@datadog-datadog-ci-plugin-gate-20.tgz", + "@datadog/datadog-ci-plugin-junit": "file:./artifacts/@datadog-datadog-ci-plugin-junit-20.tgz", "@datadog/datadog-ci-plugin-sarif": "file:./artifacts/@datadog-datadog-ci-plugin-sarif-20.tgz", "@datadog/datadog-ci-plugin-sbom": "file:./artifacts/@datadog-datadog-ci-plugin-sbom-20.tgz" }, @@ -450,6 +454,7 @@ jobs: "@datadog/datadog-ci-plugin-deployment": "file:./artifacts/@datadog-datadog-ci-plugin-deployment-20.tgz", "@datadog/datadog-ci-plugin-dora": "file:./artifacts/@datadog-datadog-ci-plugin-dora-20.tgz", "@datadog/datadog-ci-plugin-gate": "file:./artifacts/@datadog-datadog-ci-plugin-gate-20.tgz", + "@datadog/datadog-ci-plugin-junit": "file:./artifacts/@datadog-datadog-ci-plugin-junit-20.tgz", "@datadog/datadog-ci-plugin-sarif": "file:./artifacts/@datadog-datadog-ci-plugin-sarif-20.tgz", "@datadog/datadog-ci-plugin-sbom": "file:./artifacts/@datadog-datadog-ci-plugin-sbom-20.tgz" }, diff --git a/README.md b/README.md deleted file mode 120000 index cb2f15689b..0000000000 --- a/README.md +++ /dev/null @@ -1 +0,0 @@ -packages/datadog-ci/README.md \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000000..ebbaf3b3ba --- /dev/null +++ b/README.md @@ -0,0 +1,350 @@ +# Datadog CI + +[![NPM Version](https://img.shields.io/npm/v/@datadog/datadog-ci)](https://www.npmjs.com/package/@datadog/datadog-ci) [![Continuous Integration](https://github.com/DataDog/datadog-ci/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/DataDog/datadog-ci/actions/workflows/ci.yml?query=branch%3Amaster) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) ![Node.js Version](https://img.shields.io/badge/Node.js-20+-green) + +Execute commands from your Continuous Integration (CI) and Continuous Delivery (CD) pipelines to integrate with existing Datadog products. + +See the [Usage section](#usage) for a list of available commands. + +## How to install the CLI + +The package is under [@datadog/datadog-ci](https://www.npmjs.com/package/@datadog/datadog-ci) and can be installed through NPM or Yarn: + +```sh +# NPM +npm install --save-dev @datadog/datadog-ci + +# Yarn +yarn add --dev @datadog/datadog-ci +``` + +If you need `datadog-ci` as a CLI tool instead of a package, you can run it with [`npx`](https://www.npmjs.com/package/npx) or install it globally: + +```sh +# npx +npx @datadog/datadog-ci@v4 [scope] + +# NPM install globally +npm install -g @datadog/datadog-ci + +# Yarn v1 add globally +yarn global add @datadog/datadog-ci +``` + +For more ways to install the CLI, see [this section](#more-ways-to-install-the-cli). + +## Installing a plugin + +Plugins are separate packages that were split from the `@datadog/datadog-ci` package to reduce its installation size. + +Use `datadog-ci plugin list` to list the available plugins: + +```sh +datadog-ci plugin list +``` + +Use `datadog-ci plugin install` to install a plugin: + +```sh +datadog-ci plugin install +``` + +By default, running a command that requires a plugin will automatically install the plugin if it is not already installed. You can disable this behavior with `DISABLE_PLUGIN_AUTO_INSTALL=1`. + +## Usage + +```bash +Usage: datadog-ci [options] +``` + +The following `` and `` values are available. + +#### `aas` + +**README:** [📚](/packages/plugin-aas) | **Documentation:** [🔗](https://docs.datadoghq.com/serverless/azure_app_services/) | **Plugin:** `@datadog/datadog-ci-plugin-aas` + +- `instrument`: Apply Datadog instrumentation to the given Azure App Services. +- `uninstrument`: Revert Datadog instrumentation from the given Azure App Services. + +#### `cloud-run` + +**README:** [📚](/packages/plugin-cloud-run) | **Documentation:** [🔗](https://docs.datadoghq.com/serverless/google_cloud_run/) | **Plugin:** `@datadog/datadog-ci-plugin-cloud-run` + +- `flare`: Troubleshoot your issues with Cloud Run service configuration. +- `instrument`: Apply Datadog instrumentation to the given Cloud Run Services. +- `uninstrument`: Revert Datadog instrumentation from the given Cloud Run Services. + +#### `container-app` + +**README:** [📚](/packages/plugin-container-app) | **Documentation:** [🔗](https://docs.datadoghq.com/serverless/azure_container_apps/sidecar/) | **Plugin:** `@datadog/datadog-ci-plugin-container-app` + +- `instrument`: Apply Datadog instrumentation to Azure Container Apps. +- `uninstrument`: Revert Datadog instrumentation from Azure Container Apps. + +#### `coverage` + +**README:** [📚](/packages/plugin-coverage) | **Documentation:** [🔗](https://docs.datadoghq.com/code_coverage/) + +- `upload`: Upload code coverage report files to Datadog. + +#### `dora` + +**README:** [📚](/packages/plugin-dora) | **Documentation:** [🔗](https://docs.datadoghq.com/dora_metrics/) + +- `deployment`: Send a new deployment event for DORA Metrics to Datadog. + +#### `dsyms` + +**README:** [📚](/packages/datadog-ci/src/commands/dsyms) | **Documentation:** [🔗](https://docs.datadoghq.com/real_user_monitoring/error_tracking/ios/) + +- `upload`: Upload iOS dSYM files for Error Tracking (macOS only). + +#### `flutter-symbols` + +**README:** [📚](/packages/datadog-ci/src/commands/flutter-symbols) | **Documentation:** [🔗](https://docs.datadoghq.com/real_user_monitoring/error_tracking/flutter/) + +- `upload`: Upload Flutter symbols for Error Tracking. + +#### `gate` + +**README:** [📚](/packages/plugin-gate) | **Documentation:** [🔗](https://docs.datadoghq.com/quality_gates/) + +> [!WARNING] +> +> **Deprecated:** Datadog Quality Gates is being replaced by the new PR Gates in 2026. To start the migration, please fill out this form: https://forms.gle/qnhANsE1ABtHrjqz9 +> +> Learn more about PR Gates: https://docs.datadoghq.com/pr_gates + +- `evaluate`: Evaluate Quality Gates rules in Datadog. + +#### `git-metadata` + +**README:** [📚](/packages/base/src/commands/git-metadata) | **Documentation:** [🔗](https://docs.datadoghq.com/integrations/guide/source-code-integration/) + +- `upload`: Upload Git metadata for the Source Code Integration. + +#### `junit` + +**README:** [📚](/packages/datadog-ci/src/commands/junit) | **Documentation:** [🔗](https://docs.datadoghq.com/tests/setup/junit_xml/) + +- `upload`: Upload JUnit test reports for Test Visibility. + +#### `lambda` + +**README:** [📚](/packages/plugin-lambda) | **Documentation:** [🔗](https://docs.datadoghq.com/serverless/aws_lambda/) | **Plugin:** `@datadog/datadog-ci-plugin-lambda` + +- `flare`: Troubleshoot your issues with Datadog instrumentation on your AWS Lambda functions. +- `instrument`: Apply Datadog instrumentation to the given AWS Lambda functions. +- `uninstrument`: Revert Datadog instrumentation from the given AWS Lambda functions. + +#### `measure` + +**README:** [📚](/packages/datadog-ci/src/commands/measure) | **Documentation:** [🔗](https://docs.datadoghq.com/continuous_integration/pipelines/custom_tags_and_measures/) + +- Add measures to a CI Visibility pipeline trace or job span in Datadog. + +#### `pe-symbols` + +**README:** [📚](/packages/datadog-ci/src/commands/pe-symbols) | **Documentation:** [🔗](https://docs.datadoghq.com/profiler/enabling/ddprof/) + +- `upload`: Upload Windows PE debug info files for Profiling. + +#### `react-native` + +**README:** [📚](/packages/datadog-ci/src/commands/react-native) | **Documentation:** [🔗](https://docs.datadoghq.com/real_user_monitoring/error_tracking/reactnative/) + +- `codepush`: Upload React Native CodePush sourcemaps for Error Tracking. [🔗](https://docs.datadoghq.com/real_user_monitoring/mobile_and_tv_monitoring/setup/codepush/) +- `upload`: Upload React Native sourcemaps for Error Tracking. +- `xcode`: Upload React Native sourcemaps for Error Tracking from the XCode bundle build phase. + +#### `sarif` + +**README:** [📚](/packages/plugin-sarif) | **Documentation:** [🔗](https://docs.datadoghq.com/code_analysis/static_analysis/) + +- `upload`: Upload Static Analysis Results Interchange Format (SARIF) reports to Datadog. + +#### `sbom` + +**README:** [📚](/packages/plugin-sbom) | **Documentation:** [🔗](https://docs.datadoghq.com/code_analysis/software_composition_analysis/) + +- `upload`: Upload Software Bill of Materials (SBOM) files to Datadog. + +#### `sourcemaps` + +**README:** [📚](/packages/datadog-ci/src/commands/sourcemaps) | **Documentation:** [🔗](https://docs.datadoghq.com/real_user_monitoring/guide/upload-javascript-source-maps/) + +- `upload`: Upload JavaScript sourcemaps for Error Tracking. + +#### `stepfunctions` + +**README:** [📚](/packages/plugin-stepfunctions) | **Documentation:** [🔗](https://docs.datadoghq.com/serverless/step_functions/installation/?tab=datadogcli) | **Plugin:** `@datadog/datadog-ci-plugin-stepfunctions` + +- `instrument`: Instrument AWS Step Function with Datadog to get logs and traces. +- `uninstrument`: Uninstrument AWS Step Function. + +#### `synthetics` + +**README:** [📚](/packages/plugin-synthetics) | **Plugin:** `@datadog/datadog-ci-plugin-synthetics` + +- `run-tests`: Run Continuous Testing tests from the CI. [🔗](https://docs.datadoghq.com/continuous_testing/) +- `upload-application`: Upload a new version to an existing mobile application in Datadog. [🔗](https://docs.datadoghq.com/mobile_app_testing/) + +#### `tag` + +**README:** [📚](/packages/base/src/commands/tag) | **Documentation:** [🔗](https://docs.datadoghq.com/continuous_integration/pipelines/custom_tags_and_measures/) + +- Add custom tags to a CI Visibility pipeline trace or job span in Datadog. + +#### `trace` + +**README:** [📚](/packages/datadog-ci/src/commands/trace) | **Documentation:** [🔗](https://docs.datadoghq.com/continuous_integration/pipelines/custom_commands/) + +- Add custom commands to a CI Visibility pipeline in Datadog. + +#### `unity-symbols` + +**README:** [📚](/packages/datadog-ci/src/commands/unity-symbols) | **Documentation:** [🔗](https://docs.datadoghq.com/real_user_monitoring/error_tracking/unity/) + +- `upload`: Upload Unity symbols for Error Tracking. + +### Beta commands + +The following are **beta** commands, you can enable them with with `DD_BETA_COMMANDS_ENABLED=1`: + +#### `deployment` + +**README:** [📚](/packages/plugin-deployment) | **Documentation:** [🔗](https://docs.datadoghq.com/continuous_delivery/) + +- `mark`: Mark a CI job as a deployment. +- `correlate`: Correlate GitOps CD deployments with application repositories CI pipelines. [🔗](https://docs.datadoghq.com/continuous_delivery/deployments/argocd#correlate-deployments-with-ci-pipelines) +- `correlate-image`: Correlate an image from a CD provider with its source commit. [🔗](https://docs.datadoghq.com/continuous_delivery/deployments/argocd#correlate-images-with-source-code) +- `gate`: Evaluate a Deployment Gate. [🔗](https://docs.datadoghq.com/deployment_gates/) + +#### `elf-symbols` + +**README:** [📚](/packages/datadog-ci/src/commands/elf-symbols) | **Documentation:** [🔗](https://docs.datadoghq.com/profiler/enabling/ddprof/) + +- `upload`: Upload Elf debug info files for Profiling. + +### FIPS support + +The `fips` option allows `datadog-ci` to use a FIPS cryptographic module provider if the OpenSSL library installed on the host system provides it. + +**Note**: `datadog-ci` cannot assert if such a provider is available, and doesn't throw any error if the provider is not FIPS validated. + +Node.js versions below 17 are incompatible with OpenSSL 3, which provides FIPS support. +If you are using a Node.js version below 17, enabling the `fips` option causes the command to throw an error. +The option `fips-ignore-error` ignores this error. + +#### `fips` +Enable `datadog-ci` FIPS support if a FIPS validated provider is installed on the host system. +If you do not have a FIPS provider installed, `datadog-ci` does not raise an error. + +- ENV variable: `DATADOG_FIPS=true` +- CLI param: `--fips` + +#### `fips-ignore-error` +Ignore Node.js errors if FIPS cannot be enabled on the host system. + +**Note**: the absence of an error doesn't indicate that FIPS is enabled successfully. + +- ENV variable: `DATADOG_FIPS_IGNORE_ERROR=true` +- CLI param: `--fips-ignore-error` + + +## More ways to install the CLI + +### Standalone binary + +If installing Node.js in the CI is an issue, standalone binaries are attached to each [GitHub release](https://github.com/DataDog/datadog-ci/releases). + +Supported architectures: +- `linux-x64` +- `linux-arm64` +- `darwin-x64` (MacOS) +- `darwin-arm64` (MacOS) +- `win-x64` (Windows) + +> [!WARNING] +> Using `strip` to remove debugging symbols from the standalone binary as an attempt to make it smaller may cause segmentation faults when running `datadog-ci`. See https://github.com/nodejs/postject/issues/90. + +> [!NOTE] +> To determine the version when unable to run `datadog-ci --version` (for example, because of segmentation faults or an architecture mismatch), run `grep --text STANDALONE_BINARY_VERSION path/to/datadog-ci`. + +To install the standalone binary: + +#### Linux + +```sh +# Linux x64 +curl -L --fail "https://github.com/DataDog/datadog-ci/releases/latest/download/datadog-ci_linux-x64" --output "/usr/local/bin/datadog-ci" && chmod +x /usr/local/bin/datadog-ci + +# Linux arm64 +curl -L --fail "https://github.com/DataDog/datadog-ci/releases/latest/download/datadog-ci_linux-arm64" --output "/usr/local/bin/datadog-ci" && chmod +x /usr/local/bin/datadog-ci +``` + +#### MacOS + +```sh +# MacOS x64 +curl -L --fail "https://github.com/DataDog/datadog-ci/releases/latest/download/datadog-ci_darwin-x64" --output "/usr/local/bin/datadog-ci" && chmod +x /usr/local/bin/datadog-ci + +# MacOS arm64 +curl -L --fail "https://github.com/DataDog/datadog-ci/releases/latest/download/datadog-ci_darwin-arm64" --output "/usr/local/bin/datadog-ci" && chmod +x /usr/local/bin/datadog-ci +``` + +#### Windows + +```sh +# Windows x64 +Invoke-WebRequest -Uri "https://github.com/DataDog/datadog-ci/releases/latest/download/datadog-ci_win-x64" -OutFile "datadog-ci.exe" +``` + +Then, you can run `datadog-ci` commands normally: + +```sh +datadog-ci version +``` + +### Container image + +To run `datadog-ci` from a container, you can use the `datadog/ci` image available in [Docker Hub](https://hub.docker.com/r/datadog/ci) as well as the public [Amazon ECR](https://gallery.ecr.aws/datadog/ci) and [Google GC](https://console.cloud.google.com/gcr/images/datadoghq/global/ci) registries. + +```shell +docker pull datadog/ci +``` + +This example demonstrates how to run a command using the container and passing in the API and app keys: + +```shell +export DD_API_KEY=$(cat /secret/dd_api_key) +export DD_APP_KEY=$(cat /secret/dd_app_key) +docker run --rm -it -v $(pwd):/w -e DD_API_KEY -e DD_APP_KEY datadog/ci [] [options] +``` + +#### Building your own container image + +You can build an image using the provided [Dockerfile](https://github.com/DataDog/datadog-ci/blob/master/container/Dockerfile): + +```sh +cd container +docker build --tag datadog-ci . +``` + +Optionally, you can use the `VERSION` build argument to build an image for a specific version: + +```sh +docker build --build-arg "VERSION=v3.9.0" --tag datadog-ci . +``` + +## Migration guide + +If you are upgrading from a previous major version, read our [MIGRATING.md](MIGRATING.md) document to understand the changes and how to adapt your scripts. + +## Development + +Before contributing to this open source project, read our [CONTRIBUTING.md](CONTRIBUTING.md) document. + +## License + +[Apache License, v2.0](LICENSE) diff --git a/packages/base/package.json b/packages/base/package.json index 650c75b232..9b33515635 100644 --- a/packages/base/package.json +++ b/packages/base/package.json @@ -63,6 +63,7 @@ "@datadog/datadog-ci-plugin-deployment": "workspace:*", "@datadog/datadog-ci-plugin-dora": "workspace:*", "@datadog/datadog-ci-plugin-gate": "workspace:*", + "@datadog/datadog-ci-plugin-junit": "workspace:*", "@datadog/datadog-ci-plugin-lambda": "workspace:*", "@datadog/datadog-ci-plugin-sarif": "workspace:*", "@datadog/datadog-ci-plugin-sbom": "workspace:*", @@ -91,6 +92,9 @@ "@datadog/datadog-ci-plugin-gate": { "optional": true }, + "@datadog/datadog-ci-plugin-junit": { + "optional": true + }, "@datadog/datadog-ci-plugin-lambda": { "optional": true }, diff --git a/packages/base/src/cli.ts b/packages/base/src/cli.ts index 83147576b0..d2e36b53ce 100644 --- a/packages/base/src/cli.ts +++ b/packages/base/src/cli.ts @@ -10,6 +10,7 @@ import {commands as doraCommands} from './commands/dora/cli' import {commands as dsymsCommands} from './commands/dsyms/cli' import {commands as gateCommands} from './commands/gate/cli' import {commands as gitMetadataCommands} from './commands/git-metadata/cli' +import {commands as junitCommands} from './commands/junit/cli' import {commands as lambdaCommands} from './commands/lambda/cli' import {commands as pluginCommands} from './commands/plugin/cli' import {commands as sarifCommands} from './commands/sarif/cli' @@ -29,6 +30,7 @@ export const commands = { 'dsyms': dsymsCommands, 'gate': gateCommands, 'git-metadata': gitMetadataCommands, + 'junit': junitCommands, 'lambda': lambdaCommands, 'plugin': pluginCommands, 'sarif': sarifCommands, diff --git a/packages/base/src/commands/junit/cli.ts b/packages/base/src/commands/junit/cli.ts new file mode 100644 index 0000000000..d8f8b6df8b --- /dev/null +++ b/packages/base/src/commands/junit/cli.ts @@ -0,0 +1,7 @@ +/* eslint-disable import-x/order */ +import {JunitUploadCommand} from './upload' + +// prettier-ignore +export const commands = [ + JunitUploadCommand, +] diff --git a/packages/base/src/commands/junit/upload.ts b/packages/base/src/commands/junit/upload.ts new file mode 100644 index 0000000000..f3bc3ef694 --- /dev/null +++ b/packages/base/src/commands/junit/upload.ts @@ -0,0 +1,97 @@ +import {Command, Option} from 'clipanion' +import * as t from 'typanion' + +import {BaseCommand} from '@datadog/datadog-ci-base' +import * as validation from '@datadog/datadog-ci-base/helpers/validation' + +import {executePluginCommand} from '../../helpers/plugin' + +export class JunitUploadCommand extends BaseCommand { + public static paths = [['junit', 'upload']] + + public static usage = Command.Usage({ + category: 'CI Visibility', + description: 'Upload jUnit XML test reports files to Datadog.', + details: ` + This command will upload to jUnit XML test reports files to Datadog.\n + See README for details. + `, + examples: [ + ['Upload all jUnit XML test report files in current directory', 'datadog-ci junit upload --service my-service .'], + [ + 'Discover and upload all jUnit XML test report files doing recursive search in current directory', + 'datadog-ci junit upload --service my-service --auto-discovery .', + ], + [ + 'Discover and upload all jUnit XML test report files doing recursive search in current directory, ignoring src/ignored-module-a and src/ignored-module-b', + 'datadog-ci junit upload --service my-service --ignored-paths src/ignored-module-a,src/ignored-module-b --auto-discovery .', + ], + [ + 'Upload all jUnit XML test report files in src/unit-test-reports and src/acceptance-test-reports', + 'datadog-ci junit upload --service my-service src/unit-test-reports src/acceptance-test-reports', + ], + [ + 'Upload all jUnit XML test report files in current directory and add extra tags globally', + 'datadog-ci junit upload --service my-service --tags key1:value1 --tags key2:value2 .', + ], + [ + 'Upload all jUnit XML test report files in current directory and add extra measures globally', + 'datadog-ci junit upload --service my-service --measures key1:123 --measures key2:321 .', + ], + [ + 'Upload all jUnit XML test report files in current directory to the datadoghq.eu site', + 'DD_SITE=datadoghq.eu datadog-ci junit upload --service my-service .', + ], + [ + 'Upload all jUnit XML test report files in current directory while also collecting logs', + 'datadog-ci junit upload --service my-service --logs .', + ], + [ + 'Upload all jUnit XML test report files in current directory customizing test suite with xpath', + 'datadog-ci junit upload --service my-service --xpath-tag test.suite=/testcase/@classname .', + ], + [ + 'Upload all jUnit XML test report files in current directory adding a custom tag from property with xpath', + "datadog-ci junit upload --service my-service --xpath-tag custom_tag=/testcase/..//property[@name='property-name'] .", + ], + [ + 'Upload all jUnit XML test report files in current directory with extra verbosity', + 'datadog-ci junit upload --verbose --service my-service .', + ], + ], + }) + + public basePaths = Option.Rest({required: 1}) + public verbose = Option.Boolean('--verbose', false) + public dryRun = Option.Boolean('--dry-run', false) + public env = Option.String('--env') + public logs = Option.String('--logs', 'false', { + env: 'DD_CIVISIBILITY_LOGS_ENABLED', + tolerateBoolean: true, + validator: t.isBoolean(), + }) + public maxConcurrency = Option.String('--max-concurrency', '20', {validator: validation.isInteger()}) + public measures = Option.Array('--measures') + public service = Option.String('--service', {env: 'DD_SERVICE'}) + public tags = Option.Array('--tags') + public reportTags = Option.Array('--report-tags') + public reportMeasures = Option.Array('--report-measures') + public rawXPathTags = Option.Array('--xpath-tag') + public gitRepositoryURL = Option.String('--git-repository-url') + public skipGitMetadataUpload = Option.String('--skip-git-metadata-upload', 'false', { + validator: t.isBoolean(), + tolerateBoolean: true, + }) + public automaticReportsDiscovery = Option.String('--auto-discovery', 'false', { + validator: t.isBoolean(), + tolerateBoolean: true, + }) + public ignoredPaths = Option.String('--ignored-paths') + + public fips = Option.Boolean('--fips', false) + public fipsIgnoreError = Option.Boolean('--fips-ignore-error', false) + + public async execute(): Promise { + return executePluginCommand(this) + } +} diff --git a/packages/base/src/commands/plugin/__tests__/list.test.ts b/packages/base/src/commands/plugin/__tests__/list.test.ts index 7261166b56..7a7f850b4a 100644 --- a/packages/base/src/commands/plugin/__tests__/list.test.ts +++ b/packages/base/src/commands/plugin/__tests__/list.test.ts @@ -21,12 +21,12 @@ describe('PluginListCommand', () => { { title: '--all', args: ['--all'], - expectedOutput: `The following plugins are available:\n\n - (built-in) @datadog/datadog-ci-plugin-aas\n - (built-in) @datadog/datadog-ci-plugin-cloud-run\n - (built-in) @datadog/datadog-ci-plugin-container-app\n - (built-in) @datadog/datadog-ci-plugin-coverage\n - (built-in) @datadog/datadog-ci-plugin-deployment\n - (built-in) @datadog/datadog-ci-plugin-dora\n - (built-in) @datadog/datadog-ci-plugin-gate\n - (built-in) @datadog/datadog-ci-plugin-lambda\n - (built-in) @datadog/datadog-ci-plugin-sarif\n - (built-in) @datadog/datadog-ci-plugin-sbom\n - (built-in) @datadog/datadog-ci-plugin-stepfunctions\n - (built-in) @datadog/datadog-ci-plugin-synthetics\n`, + expectedOutput: `The following plugins are available:\n\n - (built-in) @datadog/datadog-ci-plugin-aas\n - (built-in) @datadog/datadog-ci-plugin-cloud-run\n - (built-in) @datadog/datadog-ci-plugin-container-app\n - (built-in) @datadog/datadog-ci-plugin-coverage\n - (built-in) @datadog/datadog-ci-plugin-deployment\n - (built-in) @datadog/datadog-ci-plugin-dora\n - (built-in) @datadog/datadog-ci-plugin-gate\n - (built-in) @datadog/datadog-ci-plugin-junit\n - (built-in) @datadog/datadog-ci-plugin-lambda\n - (built-in) @datadog/datadog-ci-plugin-sarif\n - (built-in) @datadog/datadog-ci-plugin-sbom\n - (built-in) @datadog/datadog-ci-plugin-stepfunctions\n - (built-in) @datadog/datadog-ci-plugin-synthetics\n`, }, { title: '--all --json', args: ['--all', '--json'], - expectedOutput: `[{"name":"@datadog/datadog-ci-plugin-aas","scope":"aas","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-cloud-run","scope":"cloud-run","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-container-app","scope":"container-app","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-coverage","scope":"coverage","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-deployment","scope":"deployment","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-dora","scope":"dora","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-gate","scope":"gate","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-lambda","scope":"lambda","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-sarif","scope":"sarif","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-sbom","scope":"sbom","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-stepfunctions","scope":"stepfunctions","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-synthetics","scope":"synthetics","isBuiltin":true}]\n`, + expectedOutput: `[{"name":"@datadog/datadog-ci-plugin-aas","scope":"aas","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-cloud-run","scope":"cloud-run","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-container-app","scope":"container-app","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-coverage","scope":"coverage","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-deployment","scope":"deployment","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-dora","scope":"dora","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-gate","scope":"gate","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-junit","scope":"junit","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-lambda","scope":"lambda","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-sarif","scope":"sarif","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-sbom","scope":"sbom","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-stepfunctions","scope":"stepfunctions","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-synthetics","scope":"synthetics","isBuiltin":true}]\n`, }, ])('all plugins are built-in ($title)', async ({args, expectedOutput}) => { const cli = new Cli() @@ -55,12 +55,12 @@ describe('PluginListCommand', () => { { title: '--all', args: ['--all'], - expectedOutput: `The following plugins are available:\n\n - @datadog/datadog-ci-plugin-aas (install with datadog-ci plugin install aas)\n - (built-in) @datadog/datadog-ci-plugin-cloud-run\n - (built-in) @datadog/datadog-ci-plugin-container-app\n - (built-in) @datadog/datadog-ci-plugin-coverage\n - (built-in) @datadog/datadog-ci-plugin-deployment\n - (built-in) @datadog/datadog-ci-plugin-dora\n - (built-in) @datadog/datadog-ci-plugin-gate\n - (built-in) @datadog/datadog-ci-plugin-lambda\n - (built-in) @datadog/datadog-ci-plugin-sarif\n - (built-in) @datadog/datadog-ci-plugin-sbom\n - (built-in) @datadog/datadog-ci-plugin-stepfunctions\n - (built-in) @datadog/datadog-ci-plugin-synthetics\n`, + expectedOutput: `The following plugins are available:\n\n - @datadog/datadog-ci-plugin-aas (install with datadog-ci plugin install aas)\n - (built-in) @datadog/datadog-ci-plugin-cloud-run\n - (built-in) @datadog/datadog-ci-plugin-container-app\n - (built-in) @datadog/datadog-ci-plugin-coverage\n - (built-in) @datadog/datadog-ci-plugin-deployment\n - (built-in) @datadog/datadog-ci-plugin-dora\n - (built-in) @datadog/datadog-ci-plugin-gate\n - (built-in) @datadog/datadog-ci-plugin-junit\n - (built-in) @datadog/datadog-ci-plugin-lambda\n - (built-in) @datadog/datadog-ci-plugin-sarif\n - (built-in) @datadog/datadog-ci-plugin-sbom\n - (built-in) @datadog/datadog-ci-plugin-stepfunctions\n - (built-in) @datadog/datadog-ci-plugin-synthetics\n`, }, { title: '--all --json', args: ['--all', '--json'], - expectedOutput: `[{"name":"@datadog/datadog-ci-plugin-aas","scope":"aas","isBuiltin":false},{"name":"@datadog/datadog-ci-plugin-cloud-run","scope":"cloud-run","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-container-app","scope":"container-app","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-coverage","scope":"coverage","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-deployment","scope":"deployment","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-dora","scope":"dora","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-gate","scope":"gate","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-lambda","scope":"lambda","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-sarif","scope":"sarif","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-sbom","scope":"sbom","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-stepfunctions","scope":"stepfunctions","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-synthetics","scope":"synthetics","isBuiltin":true}]\n`, + expectedOutput: `[{"name":"@datadog/datadog-ci-plugin-aas","scope":"aas","isBuiltin":false},{"name":"@datadog/datadog-ci-plugin-cloud-run","scope":"cloud-run","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-container-app","scope":"container-app","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-coverage","scope":"coverage","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-deployment","scope":"deployment","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-dora","scope":"dora","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-gate","scope":"gate","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-junit","scope":"junit","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-lambda","scope":"lambda","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-sarif","scope":"sarif","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-sbom","scope":"sbom","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-stepfunctions","scope":"stepfunctions","isBuiltin":true},{"name":"@datadog/datadog-ci-plugin-synthetics","scope":"synthetics","isBuiltin":true}]\n`, }, ])('list available plugins ($title)', async ({args, expectedOutput}) => { const cli = new Cli() diff --git a/packages/datadog-ci/package.json b/packages/datadog-ci/package.json index e61b6cd802..b43ae54d0d 100644 --- a/packages/datadog-ci/package.json +++ b/packages/datadog-ci/package.json @@ -46,24 +46,21 @@ "@datadog/datadog-ci-plugin-deployment": "workspace:*", "@datadog/datadog-ci-plugin-dora": "workspace:*", "@datadog/datadog-ci-plugin-gate": "workspace:*", + "@datadog/datadog-ci-plugin-junit": "workspace:*", "@datadog/datadog-ci-plugin-sarif": "workspace:*", "@datadog/datadog-ci-plugin-sbom": "workspace:*", "axios": "^1.12.1", "chalk": "3.0.0", "clipanion": "^3.2.1", - "fast-xml-parser": "^4.4.1", - "form-data": "^4.0.4", "js-yaml": "4.1.1", "semver": "^7.5.3", "typanion": "^3.14.0", - "upath": "^2.0.1", - "uuid": "^9.0.0" + "upath": "^2.0.1" }, "devDependencies": { "@types/jest": "29.5.3", "@types/js-yaml": "^4.0.5", "@types/semver": "^7.7.1", - "@types/uuid": "^9.0.2", "esbuild": "^0.25.9" } } diff --git a/packages/datadog-ci/shims/injected-plugin-submodules.js b/packages/datadog-ci/shims/injected-plugin-submodules.js index c2a6311af8..fb2875bb66 100644 --- a/packages/datadog-ci/shims/injected-plugin-submodules.js +++ b/packages/datadog-ci/shims/injected-plugin-submodules.js @@ -31,6 +31,9 @@ const injectedPluginSubmodules = { 'gate': { 'evaluate': require('@datadog/datadog-ci-plugin-gate/commands/evaluate'), }, + 'junit': { + 'upload': require('@datadog/datadog-ci-plugin-junit/commands/upload'), + }, 'lambda': { 'flare': require('@datadog/datadog-ci-plugin-lambda/commands/flare'), 'instrument': require('@datadog/datadog-ci-plugin-lambda/commands/instrument'), diff --git a/packages/datadog-ci/src/commands/cli.ts b/packages/datadog-ci/src/commands/cli.ts index 38a729a541..59cc056ab2 100644 --- a/packages/datadog-ci/src/commands/cli.ts +++ b/packages/datadog-ci/src/commands/cli.ts @@ -3,7 +3,6 @@ import type {RecordWithKebabCaseKeys} from '@datadog/datadog-ci-base/helpers/typ import {commands as elfSymbolsCommands} from './elf-symbols/cli' import {commands as flutterSymbolsCommands} from './flutter-symbols/cli' -import {commands as junitCommands} from './junit/cli' import {commands as measureCommands} from './measure/cli' import {commands as peSymbolsCommands} from './pe-symbols/cli' import {commands as reactNativeCommands} from './react-native/cli' @@ -17,7 +16,6 @@ import {commands as versionCommands} from './version/cli' export const commands = { 'elf-symbols': elfSymbolsCommands, 'flutter-symbols': flutterSymbolsCommands, - 'junit': junitCommands, 'measure': measureCommands, 'pe-symbols': peSymbolsCommands, 'react-native': reactNativeCommands, diff --git a/packages/datadog-ci/src/commands/junit/cli.ts b/packages/datadog-ci/src/commands/junit/cli.ts deleted file mode 100644 index d351611174..0000000000 --- a/packages/datadog-ci/src/commands/junit/cli.ts +++ /dev/null @@ -1,3 +0,0 @@ -import {JunitUploadCommand} from './upload' - -export const commands = [JunitUploadCommand] diff --git a/packages/datadog-ci/tsconfig.json b/packages/datadog-ci/tsconfig.json index 1218d7f247..eb38917b33 100644 --- a/packages/datadog-ci/tsconfig.json +++ b/packages/datadog-ci/tsconfig.json @@ -13,6 +13,7 @@ {"path": "../plugin-deployment"}, {"path": "../plugin-dora"}, {"path": "../plugin-gate"}, + {"path": "../plugin-junit"}, {"path": "../plugin-sarif"}, {"path": "../plugin-sbom"} ] diff --git a/packages/plugin-junit/LICENSE b/packages/plugin-junit/LICENSE new file mode 100644 index 0000000000..f66d768ce9 --- /dev/null +++ b/packages/plugin-junit/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2020 Datadog, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/datadog-ci/src/commands/junit/README.md b/packages/plugin-junit/README.md similarity index 94% rename from packages/datadog-ci/src/commands/junit/README.md rename to packages/plugin-junit/README.md index 0f5abf9982..e1296e97d3 100644 --- a/packages/datadog-ci/src/commands/junit/README.md +++ b/packages/plugin-junit/README.md @@ -66,7 +66,7 @@ To verify this command works as expected, you can use `--dry-run`: ```bash export DD_API_KEY='' -yarn launch junit upload ./packages/datadog-ci/src/commands/junit/__tests__/fixtures/java-report.xml --service example-upload --dry-run +yarn launch junit upload ./packages/plugin-junit/__tests__/fixtures/java-report.xml --service example-upload --dry-run ``` Successful output should look like this: @@ -74,9 +74,9 @@ Successful output should look like this: ```bash ⚠️ DRY-RUN MODE ENABLED. WILL NOT UPLOAD JUNIT XML Starting upload with concurrency 20. -Will upload jUnit XML file src/commands/junit/__tests__/fixtures/java-report.xml +Will upload jUnit XML file src/__tests__/fixtures/java-report.xml service: example-upload -[DRYRUN] Uploading jUnit XML test report file in src/commands/junit/__tests__/fixtures/java-report.xml +[DRYRUN] Uploading jUnit XML test report file in src/__tests__/fixtures/java-report.xml ✅ Uploaded 1 files in 0 seconds. ``` diff --git a/packages/plugin-junit/package.json b/packages/plugin-junit/package.json new file mode 100644 index 0000000000..1880094de2 --- /dev/null +++ b/packages/plugin-junit/package.json @@ -0,0 +1,48 @@ +{ + "name": "@datadog/datadog-ci-plugin-junit", + "version": "5.2.1", + "description": "Datadog CI plugin for `junit` commands", + "license": "Apache-2.0", + "keywords": [ + "datadog", + "datadog-ci", + "plugin" + ], + "homepage": "https://github.com/DataDog/datadog-ci/tree/master/packages/plugin-junit", + "repository": { + "type": "git", + "url": "https://github.com/DataDog/datadog-ci.git", + "directory": "packages/plugin-junit" + }, + "exports": { + "./package.json": "./package.json", + "./commands/*": { + "development": "./src/commands/*.ts", + "default": "./dist/commands/*.js" + } + }, + "files": [ + "dist/**/*", + "README", + "LICENSE" + ], + "publishConfig": { + "access": "public" + }, + "scripts": { + "build": "yarn package:clean; yarn package:build", + "lint": "yarn package:lint", + "prepack": "yarn package:clean-dist" + }, + "peerDependencies": { + "@datadog/datadog-ci-base": "workspace:*" + }, + "dependencies": { + "axios": "^1.12.1", + "chalk": "3.0.0", + "fast-xml-parser": "^4.4.1", + "form-data": "^4.0.4", + "upath": "^2.0.1", + "uuid": "^9.0.0" + } +} diff --git a/packages/datadog-ci/src/commands/junit/__tests__/fixtures/autodiscovery/junit-report.xml b/packages/plugin-junit/src/__tests__/fixtures/autodiscovery/junit-report.xml similarity index 100% rename from packages/datadog-ci/src/commands/junit/__tests__/fixtures/autodiscovery/junit-report.xml rename to packages/plugin-junit/src/__tests__/fixtures/autodiscovery/junit-report.xml diff --git a/packages/datadog-ci/src/commands/junit/__tests__/fixtures/autodiscovery/nested/TEST-suite.xml b/packages/plugin-junit/src/__tests__/fixtures/autodiscovery/nested/TEST-suite.xml similarity index 100% rename from packages/datadog-ci/src/commands/junit/__tests__/fixtures/autodiscovery/nested/TEST-suite.xml rename to packages/plugin-junit/src/__tests__/fixtures/autodiscovery/nested/TEST-suite.xml diff --git a/packages/datadog-ci/src/commands/junit/__tests__/fixtures/autodiscovery/regular-file.xml b/packages/plugin-junit/src/__tests__/fixtures/autodiscovery/regular-file.xml similarity index 100% rename from packages/datadog-ci/src/commands/junit/__tests__/fixtures/autodiscovery/regular-file.xml rename to packages/plugin-junit/src/__tests__/fixtures/autodiscovery/regular-file.xml diff --git a/packages/datadog-ci/src/commands/junit/__tests__/fixtures/autodiscovery/test-results.xml b/packages/plugin-junit/src/__tests__/fixtures/autodiscovery/test-results.xml similarity index 100% rename from packages/datadog-ci/src/commands/junit/__tests__/fixtures/autodiscovery/test-results.xml rename to packages/plugin-junit/src/__tests__/fixtures/autodiscovery/test-results.xml diff --git a/packages/datadog-ci/src/commands/junit/__tests__/fixtures/empty.xml b/packages/plugin-junit/src/__tests__/fixtures/empty.xml similarity index 100% rename from packages/datadog-ci/src/commands/junit/__tests__/fixtures/empty.xml rename to packages/plugin-junit/src/__tests__/fixtures/empty.xml diff --git a/packages/datadog-ci/src/commands/junit/__tests__/fixtures/go-report.xml b/packages/plugin-junit/src/__tests__/fixtures/go-report.xml similarity index 100% rename from packages/datadog-ci/src/commands/junit/__tests__/fixtures/go-report.xml rename to packages/plugin-junit/src/__tests__/fixtures/go-report.xml diff --git a/packages/datadog-ci/src/commands/junit/__tests__/fixtures/invalid.xml b/packages/plugin-junit/src/__tests__/fixtures/invalid.xml similarity index 100% rename from packages/datadog-ci/src/commands/junit/__tests__/fixtures/invalid.xml rename to packages/plugin-junit/src/__tests__/fixtures/invalid.xml diff --git a/packages/datadog-ci/src/commands/junit/__tests__/fixtures/java-report.xml b/packages/plugin-junit/src/__tests__/fixtures/java-report.xml similarity index 100% rename from packages/datadog-ci/src/commands/junit/__tests__/fixtures/java-report.xml rename to packages/plugin-junit/src/__tests__/fixtures/java-report.xml diff --git a/packages/datadog-ci/src/commands/junit/__tests__/fixtures/junit.xml/valid-report-2.xml b/packages/plugin-junit/src/__tests__/fixtures/junit.xml/valid-report-2.xml similarity index 100% rename from packages/datadog-ci/src/commands/junit/__tests__/fixtures/junit.xml/valid-report-2.xml rename to packages/plugin-junit/src/__tests__/fixtures/junit.xml/valid-report-2.xml diff --git a/packages/datadog-ci/src/commands/junit/__tests__/fixtures/junit.xml/valid-report.xml b/packages/plugin-junit/src/__tests__/fixtures/junit.xml/valid-report.xml similarity index 100% rename from packages/datadog-ci/src/commands/junit/__tests__/fixtures/junit.xml/valid-report.xml rename to packages/plugin-junit/src/__tests__/fixtures/junit.xml/valid-report.xml diff --git a/packages/datadog-ci/src/commands/junit/__tests__/fixtures/other/other-non-junit-file.txt b/packages/plugin-junit/src/__tests__/fixtures/other/other-non-junit-file.txt similarity index 100% rename from packages/datadog-ci/src/commands/junit/__tests__/fixtures/other/other-non-junit-file.txt rename to packages/plugin-junit/src/__tests__/fixtures/other/other-non-junit-file.txt diff --git a/packages/datadog-ci/src/commands/junit/__tests__/fixtures/subfolder/invalid-no-tests.xml b/packages/plugin-junit/src/__tests__/fixtures/subfolder/invalid-no-tests.xml similarity index 100% rename from packages/datadog-ci/src/commands/junit/__tests__/fixtures/subfolder/invalid-no-tests.xml rename to packages/plugin-junit/src/__tests__/fixtures/subfolder/invalid-no-tests.xml diff --git a/packages/datadog-ci/src/commands/junit/__tests__/fixtures/subfolder/js-report.xml b/packages/plugin-junit/src/__tests__/fixtures/subfolder/js-report.xml similarity index 100% rename from packages/datadog-ci/src/commands/junit/__tests__/fixtures/subfolder/js-report.xml rename to packages/plugin-junit/src/__tests__/fixtures/subfolder/js-report.xml diff --git a/packages/datadog-ci/src/commands/junit/__tests__/fixtures/subfolder/non-junit-file.txt b/packages/plugin-junit/src/__tests__/fixtures/subfolder/non-junit-file.txt similarity index 100% rename from packages/datadog-ci/src/commands/junit/__tests__/fixtures/subfolder/non-junit-file.txt rename to packages/plugin-junit/src/__tests__/fixtures/subfolder/non-junit-file.txt diff --git a/packages/datadog-ci/src/commands/junit/__tests__/upload.test.ts b/packages/plugin-junit/src/__tests__/upload.test.ts similarity index 72% rename from packages/datadog-ci/src/commands/junit/__tests__/upload.test.ts rename to packages/plugin-junit/src/__tests__/upload.test.ts index bff855c174..458250244d 100644 --- a/packages/datadog-ci/src/commands/junit/__tests__/upload.test.ts +++ b/packages/plugin-junit/src/__tests__/upload.test.ts @@ -5,8 +5,8 @@ import id from '@datadog/datadog-ci-base/helpers/id' import {SpanTags} from '@datadog/datadog-ci-base/helpers/interfaces' import upath from 'upath' +import {PluginCommand as JunitUploadCommand} from '../commands/upload' import {renderInvalidFile} from '../renderer' -import {JunitUploadCommand} from '../upload' jest.mock('@datadog/datadog-ci-base/helpers/id', () => jest.fn()) @@ -29,7 +29,7 @@ describe('upload', () => { const command = createCommand(JunitUploadCommand) const files = await command['getMatchingJUnitXMLFiles'].call( { - basePaths: ['src/commands/junit/__tests__/fixtures'], + basePaths: ['src/__tests__/fixtures'], config: {}, context: command.context, service: 'service', @@ -43,16 +43,14 @@ describe('upload', () => { expect(files.length).toBe(2) const filePaths = files.map((file) => file.xmlPath) - expect(filePaths).toContain('src/commands/junit/__tests__/fixtures/go-report.xml') - expect(filePaths).toContain('src/commands/junit/__tests__/fixtures/java-report.xml') + expect(filePaths).toContain('src/__tests__/fixtures/go-report.xml') + expect(filePaths).toContain('src/__tests__/fixtures/java-report.xml') const output = command.context.stdout.toString() - expect(output).toContain( - renderInvalidFile('src/commands/junit/__tests__/fixtures/empty.xml', 'Start tag expected.') - ) + expect(output).toContain(renderInvalidFile('src/__tests__/fixtures/empty.xml', 'Start tag expected.')) expect(output).toContain( renderInvalidFile( - 'src/commands/junit/__tests__/fixtures/invalid.xml', + 'src/__tests__/fixtures/invalid.xml', 'Neither nor are the root tag.' ) ) @@ -62,7 +60,7 @@ describe('upload', () => { const command = createCommand(JunitUploadCommand) const files = await command['getMatchingJUnitXMLFiles'].call( { - basePaths: ['src/commands/junit/__tests__/fixtures/go-report.xml'], + basePaths: ['src/__tests__/fixtures/go-report.xml'], config: {}, context: command.context, service: 'service', @@ -77,7 +75,7 @@ describe('upload', () => { expect(files.length).toEqual(1) expect(files[0]).toMatchObject({ - xmlPath: 'src/commands/junit/__tests__/fixtures/go-report.xml', + xmlPath: 'src/__tests__/fixtures/go-report.xml', }) }) @@ -85,7 +83,7 @@ describe('upload', () => { const command = createCommand(JunitUploadCommand) const files = await command['getMatchingJUnitXMLFiles'].call( { - basePaths: ['src/commands/junit/__tests__/fixtures/does-not-exist.xml'], + basePaths: ['src/__tests__/fixtures/does-not-exist.xml'], config: {}, context: command.context, service: 'service', @@ -104,10 +102,7 @@ describe('upload', () => { const command = createCommand(JunitUploadCommand) const files = await command['getMatchingJUnitXMLFiles'].call( { - basePaths: [ - 'src/commands/junit/__tests__/fixtures', - 'src/commands/junit/__tests__/fixtures/subfolder/js-report.xml', - ], + basePaths: ['src/__tests__/fixtures', 'src/__tests__/fixtures/subfolder/js-report.xml'], config: {}, context: command.context, service: 'service', @@ -122,16 +117,16 @@ describe('upload', () => { // Check that all expected files are present, regardless of order const filePaths = files.map((file) => file.xmlPath) expect(filePaths.length).toEqual(3) - expect(filePaths).toContain('src/commands/junit/__tests__/fixtures/go-report.xml') - expect(filePaths).toContain('src/commands/junit/__tests__/fixtures/java-report.xml') - expect(filePaths).toContain('src/commands/junit/__tests__/fixtures/subfolder/js-report.xml') + expect(filePaths).toContain('src/__tests__/fixtures/go-report.xml') + expect(filePaths).toContain('src/__tests__/fixtures/java-report.xml') + expect(filePaths).toContain('src/__tests__/fixtures/subfolder/js-report.xml') }) test('should allow folders with extensions', async () => { const command = createCommand(JunitUploadCommand) const files = await command['getMatchingJUnitXMLFiles'].call( { - basePaths: ['src/commands/junit/__tests__/fixtures/junit.xml'], + basePaths: ['src/__tests__/fixtures/junit.xml'], config: {}, context: command.context, service: 'service', @@ -145,15 +140,15 @@ describe('upload', () => { expect(files.length).toBe(2) const filePaths = files.map((file) => file.xmlPath) - expect(filePaths).toContain('src/commands/junit/__tests__/fixtures/junit.xml/valid-report-2.xml') - expect(filePaths).toContain('src/commands/junit/__tests__/fixtures/junit.xml/valid-report.xml') + expect(filePaths).toContain('src/__tests__/fixtures/junit.xml/valid-report-2.xml') + expect(filePaths).toContain('src/__tests__/fixtures/junit.xml/valid-report.xml') }) test('should not have repeated files', async () => { const command = createCommand(JunitUploadCommand) const files = await command['getMatchingJUnitXMLFiles'].call( { - basePaths: ['src/commands/junit/__tests__/fixtures', 'src/commands/junit/__tests__/fixtures/go-report.xml'], + basePaths: ['src/__tests__/fixtures', 'src/__tests__/fixtures/go-report.xml'], config: {}, context: command.context, service: 'service', @@ -172,7 +167,7 @@ describe('upload', () => { const command = createCommand(JunitUploadCommand) const files = await command['getMatchingJUnitXMLFiles'].call( { - basePaths: ['src/commands/junit/__tests__/fixtures'], + basePaths: ['src/__tests__/fixtures'], config: {}, context: command.context, service: 'service', @@ -195,7 +190,7 @@ describe('upload', () => { const command = createCommand(JunitUploadCommand) const files = await command['getMatchingJUnitXMLFiles'].call( { - basePaths: ['src/commands/junit/__tests__/fixtures'], + basePaths: ['src/__tests__/fixtures'], config: {}, context: command.context, logs: true, @@ -219,7 +214,7 @@ describe('upload', () => { const command = createCommand(JunitUploadCommand) await command['getMatchingJUnitXMLFiles'].call( { - basePaths: ['src/commands/junit/__tests__/fixtures/subfolder/invalid-no-tests.xml'], + basePaths: ['src/__tests__/fixtures/subfolder/invalid-no-tests.xml'], config: {}, context: command.context, logs: true, @@ -234,7 +229,7 @@ describe('upload', () => { const output = command.context.stdout.toString() expect(output).toContain( renderInvalidFile( - 'src/commands/junit/__tests__/fixtures/subfolder/invalid-no-tests.xml', + 'src/__tests__/fixtures/subfolder/invalid-no-tests.xml', 'The junit report file is empty, there are no elements.' ) ) @@ -244,7 +239,7 @@ describe('upload', () => { const command = createCommand(JunitUploadCommand) const files = await command['getMatchingJUnitXMLFiles'].call( { - basePaths: ['**/junit/**/*.xml'], + basePaths: ['**/*.xml'], config: {}, context: command.context, service: 'service', @@ -258,21 +253,21 @@ describe('upload', () => { const fileNames = files.map((file) => file.xmlPath) expect(fileNames.length).toBe(9) - expect(fileNames).toContain('./src/commands/junit/__tests__/fixtures/go-report.xml') - expect(fileNames).toContain('./src/commands/junit/__tests__/fixtures/java-report.xml') - expect(fileNames).toContain('./src/commands/junit/__tests__/fixtures/junit.xml/valid-report-2.xml') - expect(fileNames).toContain('./src/commands/junit/__tests__/fixtures/junit.xml/valid-report.xml') - expect(fileNames).toContain('./src/commands/junit/__tests__/fixtures/subfolder/js-report.xml') - expect(fileNames).toContain('./src/commands/junit/__tests__/fixtures/autodiscovery/junit-report.xml') - expect(fileNames).toContain('./src/commands/junit/__tests__/fixtures/autodiscovery/test-results.xml') - expect(fileNames).toContain('./src/commands/junit/__tests__/fixtures/autodiscovery/nested/TEST-suite.xml') + expect(fileNames).toContain('./src/__tests__/fixtures/go-report.xml') + expect(fileNames).toContain('./src/__tests__/fixtures/java-report.xml') + expect(fileNames).toContain('./src/__tests__/fixtures/junit.xml/valid-report-2.xml') + expect(fileNames).toContain('./src/__tests__/fixtures/junit.xml/valid-report.xml') + expect(fileNames).toContain('./src/__tests__/fixtures/subfolder/js-report.xml') + expect(fileNames).toContain('./src/__tests__/fixtures/autodiscovery/junit-report.xml') + expect(fileNames).toContain('./src/__tests__/fixtures/autodiscovery/test-results.xml') + expect(fileNames).toContain('./src/__tests__/fixtures/autodiscovery/nested/TEST-suite.xml') }) test('should fetch nested folders and ignore non xml files', async () => { const command = createCommand(JunitUploadCommand) const files = await command['getMatchingJUnitXMLFiles'].call( { - basePaths: ['**/junit/**'], + basePaths: ['**'], config: {}, context: command.context, service: 'service', @@ -286,21 +281,21 @@ describe('upload', () => { const fileNames = files.map((file) => file.xmlPath) expect(fileNames.length).toBe(9) - expect(fileNames).toContain('./src/commands/junit/__tests__/fixtures/go-report.xml') - expect(fileNames).toContain('./src/commands/junit/__tests__/fixtures/java-report.xml') - expect(fileNames).toContain('./src/commands/junit/__tests__/fixtures/junit.xml/valid-report-2.xml') - expect(fileNames).toContain('./src/commands/junit/__tests__/fixtures/junit.xml/valid-report.xml') - expect(fileNames).toContain('./src/commands/junit/__tests__/fixtures/subfolder/js-report.xml') - expect(fileNames).toContain('./src/commands/junit/__tests__/fixtures/autodiscovery/junit-report.xml') - expect(fileNames).toContain('./src/commands/junit/__tests__/fixtures/autodiscovery/test-results.xml') - expect(fileNames).toContain('./src/commands/junit/__tests__/fixtures/autodiscovery/nested/TEST-suite.xml') + expect(fileNames).toContain('./src/__tests__/fixtures/go-report.xml') + expect(fileNames).toContain('./src/__tests__/fixtures/java-report.xml') + expect(fileNames).toContain('./src/__tests__/fixtures/junit.xml/valid-report-2.xml') + expect(fileNames).toContain('./src/__tests__/fixtures/junit.xml/valid-report.xml') + expect(fileNames).toContain('./src/__tests__/fixtures/subfolder/js-report.xml') + expect(fileNames).toContain('./src/__tests__/fixtures/autodiscovery/junit-report.xml') + expect(fileNames).toContain('./src/__tests__/fixtures/autodiscovery/test-results.xml') + expect(fileNames).toContain('./src/__tests__/fixtures/autodiscovery/nested/TEST-suite.xml') }) test('should discover junit XML files automatically with recursive search', async () => { const command = createCommand(JunitUploadCommand) const files = await command['getMatchingJUnitXMLFiles'].call( { - basePaths: ['src/commands/junit/__tests__/fixtures/autodiscovery'], + basePaths: ['src/__tests__/fixtures/autodiscovery'], automaticReportsDiscovery: true, config: {}, context: command.context, @@ -315,19 +310,19 @@ describe('upload', () => { const fileNames = files.map((file) => file.xmlPath) expect(fileNames.length).toBe(3) - expect(fileNames).toContain('src/commands/junit/__tests__/fixtures/autodiscovery/junit-report.xml') - expect(fileNames).toContain('src/commands/junit/__tests__/fixtures/autodiscovery/test-results.xml') - expect(fileNames).toContain('src/commands/junit/__tests__/fixtures/autodiscovery/nested/TEST-suite.xml') - expect(fileNames).not.toContain('src/commands/junit/__tests__/fixtures/autodiscovery/regular-file.xml') + expect(fileNames).toContain('src/__tests__/fixtures/autodiscovery/junit-report.xml') + expect(fileNames).toContain('src/__tests__/fixtures/autodiscovery/test-results.xml') + expect(fileNames).toContain('src/__tests__/fixtures/autodiscovery/nested/TEST-suite.xml') + expect(fileNames).not.toContain('src/__tests__/fixtures/autodiscovery/regular-file.xml') }) test('should discover junit XML files automatically excluding ignored paths', async () => { const command = createCommand(JunitUploadCommand) const files = await command['getMatchingJUnitXMLFiles'].call( { - basePaths: ['src/commands/junit/__tests__/fixtures/autodiscovery'], + basePaths: ['src/__tests__/fixtures/autodiscovery'], automaticReportsDiscovery: true, - ignoredPaths: 'src/commands/junit/__tests__/fixtures/autodiscovery/nested', + ignoredPaths: 'src/__tests__/fixtures/autodiscovery/nested', config: {}, context: command.context, service: 'service', @@ -341,8 +336,8 @@ describe('upload', () => { const fileNames = files.map((file) => file.xmlPath) expect(fileNames.length).toBe(2) - expect(fileNames).toContain('src/commands/junit/__tests__/fixtures/autodiscovery/junit-report.xml') - expect(fileNames).toContain('src/commands/junit/__tests__/fixtures/autodiscovery/test-results.xml') + expect(fileNames).toContain('src/__tests__/fixtures/autodiscovery/junit-report.xml') + expect(fileNames).toContain('src/__tests__/fixtures/autodiscovery/test-results.xml') }) test('should combine explicit file paths with auto-discovered files', async () => { @@ -350,8 +345,8 @@ describe('upload', () => { const files = await command['getMatchingJUnitXMLFiles'].call( { basePaths: [ - 'src/commands/junit/__tests__/fixtures/autodiscovery/nested', - 'src/commands/junit/__tests__/fixtures/autodiscovery/junit-report.xml', + 'src/__tests__/fixtures/autodiscovery/nested', + 'src/__tests__/fixtures/autodiscovery/junit-report.xml', ], automaticReportsDiscovery: true, config: {}, @@ -367,8 +362,8 @@ describe('upload', () => { const fileNames = files.map((file) => file.xmlPath) expect(fileNames.length).toBe(2) - expect(fileNames).toContain('src/commands/junit/__tests__/fixtures/autodiscovery/junit-report.xml') - expect(fileNames).toContain('src/commands/junit/__tests__/fixtures/autodiscovery/nested/TEST-suite.xml') + expect(fileNames).toContain('src/__tests__/fixtures/autodiscovery/junit-report.xml') + expect(fileNames).toContain('src/__tests__/fixtures/autodiscovery/nested/TEST-suite.xml') }) }) describe('getSpanTags', () => { @@ -481,7 +476,7 @@ describe('upload', () => { const command = createCommand(JunitUploadCommand) const xPathTags = command['parseXPathTags'].call( { - basePaths: ['src/commands/junit/__tests__/fixtures'], + basePaths: ['src/__tests__/fixtures'], config: {}, context: command.context, service: 'service', @@ -498,7 +493,7 @@ describe('upload', () => { const command = createCommand(JunitUploadCommand) command['parseXPathTags'].call( { - basePaths: ['src/commands/junit/__tests__/fixtures'], + basePaths: ['src/__tests__/fixtures'], config: {}, context: command.context, service: 'service', @@ -515,42 +510,42 @@ describe('execute', () => { const runCLI = makeRunCLI(JunitUploadCommand, ['junit', 'upload', '--service', 'test-service', '--dry-run', '--logs']) test('relative path with double dots', async () => { - const {context, code} = await runCLI(['src/commands/junit/__tests__/doesnotexist/../fixtures']) + const {context, code} = await runCLI(['src/__tests__/doesnotexist/../fixtures']) const output = context.stdout.toString().split('\n') expect(code).toBe(0) checkConsoleOutput(output, { - basePaths: ['src/commands/junit/__tests__/fixtures'], + basePaths: ['src/__tests__/fixtures'], concurrency: 20, service: 'test-service', }) }) test('multiple paths', async () => { - const {context, code} = await runCLI(['src/commands/junit/first/', 'src/commands/junit/second/']) + const {context, code} = await runCLI(['src/first/', 'src/second/']) const output = context.stdout.toString().split('\n') expect(code).toBe(0) checkConsoleOutput(output, { - basePaths: ['src/commands/junit/first/', 'src/commands/junit/second/'], + basePaths: ['src/first/', 'src/second/'], concurrency: 20, service: 'test-service', }) }) test('absolute path', async () => { - const {context, code} = await runCLI([CWD + '/src/commands/junit/__tests__/fixtures']) + const {context, code} = await runCLI([CWD + '/src/__tests__/fixtures']) const output = context.stdout.toString().split('\n') expect(code).toBe(0) checkConsoleOutput(output, { - basePaths: [`${CWD}/src/commands/junit/__tests__/fixtures`], + basePaths: [`${CWD}/src/__tests__/fixtures`], concurrency: 20, service: 'test-service', }) }) test('single file', async () => { - const {context, code} = await runCLI([CWD + '/src/commands/junit/__tests__/fixtures/single_file.xml']) + const {context, code} = await runCLI([CWD + '/src/__tests__/fixtures/single_file.xml']) const output = context.stdout.toString().split('\n') - const path = `${CWD}/src/commands/junit/__tests__/fixtures/single_file.xml` + const path = `${CWD}/src/__tests__/fixtures/single_file.xml` expect(code).toBe(0) expect(output[0]).toContain('DRY-RUN MODE ENABLED. WILL NOT UPLOAD JUNIT XML') expect(output[1]).toContain('Starting upload with concurrency 20.') @@ -559,7 +554,7 @@ describe('execute', () => { }) test('with git metadata without argument (default value is true)', async () => { - const {context, code} = await runCLI(['--verbose', CWD + '/src/commands/junit/__tests__/fixtures/single_file.xml']) + const {context, code} = await runCLI(['--verbose', CWD + '/src/__tests__/fixtures/single_file.xml']) const output = context.stdout.toString().split('\n') expect(id).toHaveBeenCalled() expect(code).toBe(0) @@ -570,7 +565,7 @@ describe('execute', () => { const {context, code} = await runCLI([ '--verbose', '--skip-git-metadata-upload', // should tolerate the option as a boolean flag - CWD + '/src/commands/junit/__tests__/fixtures/single_file.xml', + CWD + '/src/__tests__/fixtures/single_file.xml', ]) const output = context.stdout.toString().split('\n') expect(id).not.toHaveBeenCalled() @@ -582,7 +577,7 @@ describe('execute', () => { const {context, code} = await runCLI([ '--verbose', '--skip-git-metadata-upload=1', // should tolerate the option as a boolean flag - CWD + '/src/commands/junit/__tests__/fixtures/single_file.xml', + CWD + '/src/__tests__/fixtures/single_file.xml', ]) const output = context.stdout.toString().split('\n') expect(id).not.toHaveBeenCalled() @@ -593,7 +588,7 @@ describe('execute', () => { test('with git metadata (with argument set to 0)', async () => { const {context, code} = await runCLI([ '--skip-git-metadata-upload=0', - CWD + '/src/commands/junit/__tests__/fixtures/single_file.xml', + CWD + '/src/__tests__/fixtures/single_file.xml', ]) const output = context.stdout.toString().split('\n') expect(code).toBe(0) @@ -601,7 +596,7 @@ describe('execute', () => { }) test('id headers are added when git metadata is uploaded', async () => { - await runCLI(['--skip-git-metadata-upload=0', CWD + '/src/commands/junit/__tests__/fixtures/single_file.xml']) + await runCLI(['--skip-git-metadata-upload=0', CWD + '/src/__tests__/fixtures/single_file.xml']) expect(id).toHaveBeenCalled() }, 10000000) }) diff --git a/packages/datadog-ci/src/commands/junit/__tests__/utils.test.ts b/packages/plugin-junit/src/__tests__/utils.test.ts similarity index 100% rename from packages/datadog-ci/src/commands/junit/__tests__/utils.test.ts rename to packages/plugin-junit/src/__tests__/utils.test.ts diff --git a/packages/datadog-ci/src/commands/junit/api.ts b/packages/plugin-junit/src/api.ts similarity index 100% rename from packages/datadog-ci/src/commands/junit/api.ts rename to packages/plugin-junit/src/api.ts diff --git a/packages/datadog-ci/src/commands/junit/upload.ts b/packages/plugin-junit/src/commands/upload.ts similarity index 71% rename from packages/datadog-ci/src/commands/junit/upload.ts rename to packages/plugin-junit/src/commands/upload.ts index 1d0856305c..18d6e008ea 100644 --- a/packages/datadog-ci/src/commands/junit/upload.ts +++ b/packages/plugin-junit/src/commands/upload.ts @@ -1,10 +1,10 @@ import fs from 'fs' import os from 'os' -import {BaseCommand} from '@datadog/datadog-ci-base' import {newSimpleGit} from '@datadog/datadog-ci-base/commands/git-metadata/git' import {uploadToGitDB} from '@datadog/datadog-ci-base/commands/git-metadata/gitdb' import {isGitRepo} from '@datadog/datadog-ci-base/commands/git-metadata/library' +import {JunitUploadCommand} from '@datadog/datadog-ci-base/commands/junit/upload' import {FIPS_ENV_VAR, FIPS_IGNORE_ERROR_ENV_VAR} from '@datadog/datadog-ci-base/constants' import {getCISpanTags} from '@datadog/datadog-ci-base/helpers/ci' import {doWithMaxConcurrency} from '@datadog/datadog-ci-base/helpers/concurrency' @@ -20,15 +20,12 @@ import {retryRequest} from '@datadog/datadog-ci-base/helpers/retry' import {parseTags, parseMetrics} from '@datadog/datadog-ci-base/helpers/tags' import {getUserGitSpanTags} from '@datadog/datadog-ci-base/helpers/user-provided-git' import {getRequestBuilder, timedExecAsync} from '@datadog/datadog-ci-base/helpers/utils' -import * as validation from '@datadog/datadog-ci-base/helpers/validation' import chalk from 'chalk' -import {Command, Option} from 'clipanion' import {XMLParser, XMLValidator} from 'fast-xml-parser' -import * as t from 'typanion' import upath from 'upath' -import {apiConstructor, apiUrl, intakeUrl} from './api' -import {APIHelper, Payload} from './interfaces' +import {apiConstructor, apiUrl, intakeUrl} from '../api' +import {APIHelper, Payload} from '../interfaces' import { renderCommandInfo, renderDryRunUpload, @@ -40,7 +37,7 @@ import { renderSuccessfulGitDBSync, renderSuccessfulUpload, renderUpload, -} from './renderer' +} from '../renderer' const TRACE_ID_HTTP_HEADER = 'x-datadog-trace-id' const PARENT_ID_HTTP_HEADER = 'x-datadog-parent-id' @@ -77,92 +74,7 @@ const validateXml = (xmlFilePath: string) => { return undefined } -export class JunitUploadCommand extends BaseCommand { - public static paths = [['junit', 'upload']] - - public static usage = Command.Usage({ - category: 'CI Visibility', - description: 'Upload jUnit XML test reports files to Datadog.', - details: ` - This command will upload to jUnit XML test reports files to Datadog.\n - See README for details. - `, - examples: [ - ['Upload all jUnit XML test report files in current directory', 'datadog-ci junit upload --service my-service .'], - [ - 'Discover and upload all jUnit XML test report files doing recursive search in current directory', - 'datadog-ci junit upload --service my-service --auto-discovery .', - ], - [ - 'Discover and upload all jUnit XML test report files doing recursive search in current directory, ignoring src/ignored-module-a and src/ignored-module-b', - 'datadog-ci junit upload --service my-service --ignored-paths src/ignored-module-a,src/ignored-module-b --auto-discovery .', - ], - [ - 'Upload all jUnit XML test report files in src/unit-test-reports and src/acceptance-test-reports', - 'datadog-ci junit upload --service my-service src/unit-test-reports src/acceptance-test-reports', - ], - [ - 'Upload all jUnit XML test report files in current directory and add extra tags globally', - 'datadog-ci junit upload --service my-service --tags key1:value1 --tags key2:value2 .', - ], - [ - 'Upload all jUnit XML test report files in current directory and add extra measures globally', - 'datadog-ci junit upload --service my-service --measures key1:123 --measures key2:321 .', - ], - [ - 'Upload all jUnit XML test report files in current directory to the datadoghq.eu site', - 'DD_SITE=datadoghq.eu datadog-ci junit upload --service my-service .', - ], - [ - 'Upload all jUnit XML test report files in current directory while also collecting logs', - 'datadog-ci junit upload --service my-service --logs .', - ], - [ - 'Upload all jUnit XML test report files in current directory customizing test suite with xpath', - 'datadog-ci junit upload --service my-service --xpath-tag test.suite=/testcase/@classname .', - ], - [ - 'Upload all jUnit XML test report files in current directory adding a custom tag from property with xpath', - "datadog-ci junit upload --service my-service --xpath-tag custom_tag=/testcase/..//property[@name='property-name'] .", - ], - [ - 'Upload all jUnit XML test report files in current directory with extra verbosity', - 'datadog-ci junit upload --verbose --service my-service .', - ], - ], - }) - - private basePaths = Option.Rest({required: 1}) - private verbose = Option.Boolean('--verbose', false) - private dryRun = Option.Boolean('--dry-run', false) - private env = Option.String('--env') - private logs = Option.String('--logs', 'false', { - env: 'DD_CIVISIBILITY_LOGS_ENABLED', - tolerateBoolean: true, - validator: t.isBoolean(), - }) - private maxConcurrency = Option.String('--max-concurrency', '20', {validator: validation.isInteger()}) - private measures = Option.Array('--measures') - private service = Option.String('--service', {env: 'DD_SERVICE'}) - private tags = Option.Array('--tags') - private reportTags = Option.Array('--report-tags') - private reportMeasures = Option.Array('--report-measures') - private rawXPathTags = Option.Array('--xpath-tag') - private gitRepositoryURL = Option.String('--git-repository-url') - private skipGitMetadataUpload = Option.String('--skip-git-metadata-upload', 'false', { - validator: t.isBoolean(), - tolerateBoolean: true, - }) - - private fips = Option.Boolean('--fips', false) - private fipsIgnoreError = Option.Boolean('--fips-ignore-error', false) - - private automaticReportsDiscovery = Option.String('--auto-discovery', 'false', { - validator: t.isBoolean(), - tolerateBoolean: true, - }) - private ignoredPaths = Option.String('--ignored-paths') - +export class PluginCommand extends JunitUploadCommand { private config = { apiKey: process.env.DATADOG_API_KEY || process.env.DD_API_KEY, env: process.env.DD_ENV, diff --git a/packages/datadog-ci/src/commands/junit/interfaces.ts b/packages/plugin-junit/src/interfaces.ts similarity index 100% rename from packages/datadog-ci/src/commands/junit/interfaces.ts rename to packages/plugin-junit/src/interfaces.ts diff --git a/packages/datadog-ci/src/commands/junit/renderer.ts b/packages/plugin-junit/src/renderer.ts similarity index 100% rename from packages/datadog-ci/src/commands/junit/renderer.ts rename to packages/plugin-junit/src/renderer.ts diff --git a/packages/datadog-ci/src/commands/junit/utils.ts b/packages/plugin-junit/src/utils.ts similarity index 100% rename from packages/datadog-ci/src/commands/junit/utils.ts rename to packages/plugin-junit/src/utils.ts diff --git a/packages/plugin-junit/tsconfig.json b/packages/plugin-junit/tsconfig.json new file mode 100644 index 0000000000..bfc7b6bf3f --- /dev/null +++ b/packages/plugin-junit/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "rootDir": "./src", + "outDir": "./dist" + }, + "include": ["src"] +} diff --git a/tsconfig.json b/tsconfig.json index 50400b0adb..cd8ce001c5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,6 +14,7 @@ {"path": "./packages/plugin-deployment"}, {"path": "./packages/plugin-dora"}, {"path": "./packages/plugin-gate"}, + {"path": "./packages/plugin-junit"}, {"path": "./packages/plugin-lambda"}, {"path": "./packages/plugin-sarif"}, {"path": "./packages/plugin-sbom"}, diff --git a/yarn.lock b/yarn.lock index 49ce596d47..69ac94e678 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1532,6 +1532,7 @@ __metadata: "@datadog/datadog-ci-plugin-deployment": "workspace:*" "@datadog/datadog-ci-plugin-dora": "workspace:*" "@datadog/datadog-ci-plugin-gate": "workspace:*" + "@datadog/datadog-ci-plugin-junit": "workspace:*" "@datadog/datadog-ci-plugin-lambda": "workspace:*" "@datadog/datadog-ci-plugin-sarif": "workspace:*" "@datadog/datadog-ci-plugin-sbom": "workspace:*" @@ -1552,6 +1553,8 @@ __metadata: optional: true "@datadog/datadog-ci-plugin-gate": optional: true + "@datadog/datadog-ci-plugin-junit": + optional: true "@datadog/datadog-ci-plugin-lambda": optional: true "@datadog/datadog-ci-plugin-sarif": @@ -1660,6 +1663,21 @@ __metadata: languageName: unknown linkType: soft +"@datadog/datadog-ci-plugin-junit@workspace:*, @datadog/datadog-ci-plugin-junit@workspace:packages/plugin-junit": + version: 0.0.0-use.local + resolution: "@datadog/datadog-ci-plugin-junit@workspace:packages/plugin-junit" + dependencies: + axios: "npm:^1.12.1" + chalk: "npm:3.0.0" + fast-xml-parser: "npm:^4.4.1" + form-data: "npm:^4.0.4" + upath: "npm:^2.0.1" + uuid: "npm:^9.0.0" + peerDependencies: + "@datadog/datadog-ci-base": "workspace:*" + languageName: unknown + linkType: soft + "@datadog/datadog-ci-plugin-lambda@workspace:packages/plugin-lambda": version: 0.0.0-use.local resolution: "@datadog/datadog-ci-plugin-lambda@workspace:packages/plugin-lambda" @@ -1774,23 +1792,20 @@ __metadata: "@datadog/datadog-ci-plugin-deployment": "workspace:*" "@datadog/datadog-ci-plugin-dora": "workspace:*" "@datadog/datadog-ci-plugin-gate": "workspace:*" + "@datadog/datadog-ci-plugin-junit": "workspace:*" "@datadog/datadog-ci-plugin-sarif": "workspace:*" "@datadog/datadog-ci-plugin-sbom": "workspace:*" "@types/jest": "npm:29.5.3" "@types/js-yaml": "npm:^4.0.5" "@types/semver": "npm:^7.7.1" - "@types/uuid": "npm:^9.0.2" axios: "npm:^1.12.1" chalk: "npm:3.0.0" clipanion: "npm:^3.2.1" esbuild: "npm:^0.25.9" - fast-xml-parser: "npm:^4.4.1" - form-data: "npm:^4.0.4" js-yaml: "npm:4.1.1" semver: "npm:^7.5.3" typanion: "npm:^3.14.0" upath: "npm:^2.0.1" - uuid: "npm:^9.0.0" bin: datadog-ci: dist/cli.js languageName: unknown