diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..526c8a38 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.sh text eol=lf \ No newline at end of file diff --git a/.github/actions/run-unity-test-batch/action.yml b/.github/actions/run-unity-test-batch/action.yml new file mode 100644 index 00000000..237c73ff --- /dev/null +++ b/.github/actions/run-unity-test-batch/action.yml @@ -0,0 +1,56 @@ +name: Run Unity UTP Test Batch +description: Runs a batch of Unity UTP tests in a given Unity project. +inputs: + unity-project-path: + description: Absolute path to the Unity project. + required: true + build-target: + description: Build target to use. + required: true + build-args: + description: Additional build args. + required: false + default: "" + artifact-name: + description: Artifact name for uploaded UTP logs (must be unique per matrix job). + required: false + default: unity-tests-batch-utp-logs +runs: + using: composite + steps: + - name: Prepare test list and install packages + shell: bash + working-directory: ${{ inputs.unity-project-path }} + run: | + set -euo pipefail + tests_input="CompilerWarnings,CompilerErrors,BuildWarnings,BuildErrors,PlaymodeTestsErrors,EditmodeTestsErrors" + echo "TESTS_INPUT=$tests_input" >> $GITHUB_ENV + + needs_test_framework=false + if [[ "$tests_input" == *"PlaymodeTestsErrors"* || "$tests_input" == *"EditmodeTestsErrors"* ]]; then + needs_test_framework=true + fi + + npm install -g openupm-cli + openupm add com.utilities.buildpipeline + if [ "$needs_test_framework" = true ]; then + openupm add com.unity.test-framework + fi + + - name: Run tests + shell: bash + env: + UNITY_PROJECT_PATH: ${{ inputs.unity-project-path }} + BUILD_TARGET: ${{ inputs.build-target }} + BUILD_ARGS: ${{ inputs.build-args }} + continue-on-error: true + run: | + bash "${GITHUB_WORKSPACE}/.github/actions/scripts/run-utp-tests.sh" + + - name: Upload UTP logs + if: always() + uses: actions/upload-artifact@v6 + with: + name: ${{ inputs.artifact-name }} + path: utp-artifacts/**/*-utp-json.log + if-no-files-found: ignore diff --git a/.github/actions/scripts/run-utp-tests.sh b/.github/actions/scripts/run-utp-tests.sh new file mode 100755 index 00000000..360f97d0 --- /dev/null +++ b/.github/actions/scripts/run-utp-tests.sh @@ -0,0 +1,233 @@ +#!/usr/bin/env bash +set -uo pipefail + +UNITY_PROJECT_PATH=${UNITY_PROJECT_PATH:?UNITY_PROJECT_PATH is required} +BUILD_TARGET=${BUILD_TARGET:?BUILD_TARGET is required} +BUILD_ARGS=${BUILD_ARGS:-} +TESTS_INPUT=${TESTS_INPUT:-} + +if printf '%s' "$BUILD_ARGS" | grep -qE '[;&`|]'; then + echo "::error::BUILD_ARGS contains disallowed shell metacharacters" + exit 1 +fi + +declare -a build_args=() +if [ -n "$BUILD_ARGS" ]; then + # Split on whitespace into an array without invoking the shell + read -r -a build_args <<< "$BUILD_ARGS" +fi + +IFS=',' read -ra tests <<< "$TESTS_INPUT" +failures=0 + +clean_tests() { + rm -f "$UNITY_PROJECT_PATH/Assets/UnityCliTests"/*.cs 2>/dev/null || true + rm -f "$UNITY_PROJECT_PATH/Assets/Editor/UnityCliTests"/*.cs 2>/dev/null || true + rm -f "$UNITY_PROJECT_PATH/Assets/Tests/PlayMode/UnityCliTests"/*.cs 2>/dev/null || true + rm -f "$UNITY_PROJECT_PATH/Assets/Tests/EditMode/UnityCliTests"/*.cs 2>/dev/null || true + rm -f "$UNITY_PROJECT_PATH/Assets/Tests/EditMode/UnityCliTests"/*.asmdef 2>/dev/null || true + rm -f "$UNITY_PROJECT_PATH/Assets/Tests/EditMode/Editor/UnityCliTests"/*.cs 2>/dev/null || true +} + +clean_build_outputs() { + rm -rf "$UNITY_PROJECT_PATH/Builds" 2>/dev/null || true + mkdir -p "$UNITY_PROJECT_PATH/Builds/Logs" +} + +# Expectations for each synthetic test +# expected_status: 0 = should succeed, 1 = should fail +expected_status_for() { + case "$1" in + CompilerWarnings) echo 0 ;; + BuildWarnings) echo 0 ;; + CompilerErrors) echo 1 ;; + BuildErrors) echo 1 ;; + PlaymodeTestsErrors) echo 1 ;; + EditmodeTestsErrors) echo 1 ;; + *) echo 0 ;; + esac +} + +expected_message_for() { + case "$1" in + CompilerErrors) echo "Intentional compiler error" ;; + BuildErrors) echo "Intentional build failure" ;; + PlaymodeTestsErrors) echo "Intentional playmode failure" ;; + EditmodeTestsErrors) echo "Intentional editmode failure" ;; + CompilerWarnings) echo "Intentional warning" ;; + BuildWarnings) echo "Intentional build warning" ;; + *) echo "" ;; + esac +} + +mkdir -p "$GITHUB_WORKSPACE/utp-artifacts" + +for raw_test in "${tests[@]}"; do + test_name="$(echo "$raw_test" | xargs)" + if [ -z "$test_name" ] || [ "$test_name" = "None" ]; then + echo "Skipping empty/None test entry" + continue + fi + + src="$GITHUB_WORKSPACE/unity-tests/${test_name}.cs" + if [ ! -f "$src" ]; then + echo "::error::Requested test '$test_name' not found at $src" + failures=$((failures+1)) + continue + fi + + clean_tests + clean_build_outputs + + asmdef_src="" + + case "$test_name" in + CompilerWarnings|CompilerErrors) + dest="$UNITY_PROJECT_PATH/Assets/UnityCliTests" + ;; + BuildWarnings|BuildErrors) + dest="$UNITY_PROJECT_PATH/Assets/Editor/UnityCliTests" + ;; + PlaymodeTestsErrors) + dest="$UNITY_PROJECT_PATH/Assets/Tests/PlayMode/UnityCliTests" + ;; + EditmodeTestsErrors) + dest="$UNITY_PROJECT_PATH/Assets/Tests/EditMode/UnityCliTests" + asmdef_src="$GITHUB_WORKSPACE/unity-tests/UnityCliTests.EditMode.Editor.asmdef" + ;; + *) + echo "::error::Unknown test selection '$test_name'" + failures=$((failures+1)) + continue + ;; + esac + + mkdir -p "$dest" + if [ -n "$asmdef_src" ]; then + if [ ! -f "$asmdef_src" ]; then + echo "::error::Assembly definition for editmode tests not found at $asmdef_src" + failures=$((failures+1)) + continue + fi + cp "$asmdef_src" "$dest/" + fi + cp "$src" "$dest/" + echo "Running test: $test_name (copied to $dest)" + + validate_rc=0 + build_rc=0 + + ran_custom_flow=0 + + if [ "$test_name" = "EditmodeTestsErrors" ]; then + unity-cli run --log-name "${test_name}-EditMode" -runTests -testPlatform editmode -assemblyNames "UnityCli.EditMode.EditorTests" -testResults "$UNITY_PROJECT_PATH/Builds/Logs/${test_name}-results.xml" -quit || validate_rc=$? + + results_xml="$UNITY_PROJECT_PATH/Builds/Logs/${test_name}-results.xml" + if ! grep -q "/dev/null; then + validate_rc=1 + fi + build_rc=$validate_rc + ran_custom_flow=1 + fi + + if [ "$ran_custom_flow" -eq 0 ]; then + unity-cli run --log-name "${test_name}-Validate" -quit -executeMethod Utilities.Editor.BuildPipeline.UnityPlayerBuildTools.ValidateProject -importTMProEssentialsAsset || validate_rc=$? + + build_cmd=( + unity-cli run + --log-name "${test_name}-Build" + -buildTarget "$BUILD_TARGET" + -quit + -executeMethod Utilities.Editor.BuildPipeline.UnityPlayerBuildTools.StartCommandLineBuild + -sceneList Assets/Scenes/SampleScene.unity + ) + + if [ ${#build_args[@]} -gt 0 ]; then + build_cmd+=("${build_args[@]}") + fi + + "${build_cmd[@]}" || build_rc=$? + fi + + expected=$(expected_status_for "$test_name") + exp_msg=$(expected_message_for "$test_name") + + test_failed=0 + message_found=0 + utp_error_found=0 + + if [ -n "$exp_msg" ]; then + while IFS= read -r log_file; do + if [ -z "$log_file" ]; then + continue + fi + if grep -qi -- "$exp_msg" "$log_file" 2>/dev/null; then + message_found=1 + break + fi + done < <(find "$UNITY_PROJECT_PATH/Builds/Logs" -maxdepth 1 -type f -name "*${test_name}*.log") + fi + + # Look for error-level UTP entries for this test to treat as expected failure evidence. + while IFS= read -r utp_file; do + if [ -z "$utp_file" ]; then + continue + fi + if grep -qi '"severity"[[:space:]]*:[[:space:]]*"\(Error\|Exception\|Assert\)"' "$utp_file" 2>/dev/null; then + utp_error_found=1 + break + fi + done < <(find "$UNITY_PROJECT_PATH/Builds/Logs" -maxdepth 1 -type f -name "*${test_name}*-utp-json.log") + + if [ "$expected" -eq 0 ]; then + if [ "$validate_rc" -ne 0 ] || [ "$build_rc" -ne 0 ]; then + echo "::error::Test $test_name was expected to succeed but failed (validate_rc=$validate_rc, build_rc=$build_rc)" + test_failed=1 + fi + if [ "$utp_error_found" -eq 1 ]; then + echo "::error::Test $test_name produced UTP errors but was expected to succeed" + test_failed=1 + fi + if [ -n "$exp_msg" ] && [ "$message_found" -eq 0 ]; then + echo "::error::Test $test_name did not emit expected message '$exp_msg'" + test_failed=1 + fi + else + if [ "$validate_rc" -ne 0 ] || [ "$build_rc" -ne 0 ] || [ "$message_found" -eq 1 ] || [ "$utp_error_found" -eq 1 ]; then + : # Expected failure observed + else + echo "::error::Test $test_name was expected to fail but succeeded" + test_failed=1 + fi + + # Only insist on the expected message if both invocations claimed success. + if [ -n "$exp_msg" ] && [ "$message_found" -eq 0 ] && [ "$validate_rc" -eq 0 ] && [ "$build_rc" -eq 0 ]; then + echo "::error::Test $test_name did not emit expected message '$exp_msg'" + test_failed=1 + fi + fi + + if [ "$test_failed" -eq 0 ]; then + echo "::notice::Test $test_name behaved as expected (validate_rc=$validate_rc, build_rc=$build_rc)" + else + failures=$((failures+1)) + fi + + test_artifacts="$GITHUB_WORKSPACE/utp-artifacts/$test_name" + mkdir -p "$test_artifacts" + find "$GITHUB_WORKSPACE" -path "$test_artifacts" -prune -o -type f -name "*${test_name}*-utp-json.log" -print | while IFS= read -r utp_src; do + [ -z "$utp_src" ] && continue + dest_file="$test_artifacts/$(basename "$utp_src")" + if [ ! -f "$dest_file" ]; then + cp "$utp_src" "$dest_file" || true + fi + done || true + +done + +if [ "$failures" -gt 0 ]; then + echo "::error::One or more tests did not meet expectations ($failures)" + exit 1 +fi + +exit 0 diff --git a/.github/workflows/build-options.json b/.github/workflows/build-options.json index b1671291..5eec9059 100644 --- a/.github/workflows/build-options.json +++ b/.github/workflows/build-options.json @@ -5,17 +5,14 @@ "macos-latest" ], "unity-version": [ - "4.7.2", - "5.6.7f1 (e80cc3114ac1)", - "2017.4.40f1", - "2018", "2019.x", "2020.*", "2021.3.x", "2022.3.*", "6000.0.x", "6000.1.*", - "6000.2" + "6000.2", + "6000" ], "include": [ { diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 06d1bd8e..9b02d643 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -15,6 +15,7 @@ jobs: runs-on: ubuntu-latest permissions: contents: read + checks: write # to publish unit test results via checks github api steps: - uses: actions/checkout@v6 with: @@ -32,6 +33,7 @@ jobs: name: build ${{ matrix.jobs.name }} permissions: contents: read + checks: write # required by nested unity-build workflow strategy: matrix: ${{ fromJSON(needs.setup.outputs.jobs) }} fail-fast: false diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 564f9015..62ed36d8 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,4 +1,4 @@ -name: Publish +name: publish on: push: branches: [main] diff --git a/.github/workflows/unity-build.yml b/.github/workflows/unity-build.yml index 1e66910b..21c44ed7 100644 --- a/.github/workflows/unity-build.yml +++ b/.github/workflows/unity-build.yml @@ -17,6 +17,9 @@ jobs: strategy: matrix: ${{ fromJSON(inputs.matrix) }} fail-fast: false + permissions: + contents: read + checks: write # to publish unit test results via checks github api defaults: run: shell: bash @@ -97,12 +100,21 @@ jobs: else echo "Skipping build: Unity version $version does not support the build pipeline package (requires 2019.4+)" fi - - name: Install OpenUPM and build pipeline package - if: ${{ steps.verify-project-path.outputs.RUN_BUILD == 'true' }} - working-directory: ${{ env.UNITY_PROJECT_PATH }} + - name: Compute safe artifact name + id: artifact-name run: | - npm install -g openupm-cli - openupm add com.utilities.buildpipeline + unity_version="${{ matrix.unity-version }}" + unity_version="${unity_version//'*'/x}" + echo "name=${{ matrix.os }}-${unity_version}-${{ matrix.build-target }}-tests-batch-utp-logs" >> $GITHUB_OUTPUT + shell: bash + - name: Run Unity UTP test batches + if: ${{ steps.verify-project-path.outputs.RUN_BUILD == 'true' }} + uses: ./.github/actions/run-unity-test-batch + with: + unity-project-path: ${{ env.UNITY_PROJECT_PATH }} + build-target: ${{ matrix.build-target }} + build-args: ${{ matrix.build-args }} + artifact-name: ${{ steps.artifact-name.outputs.name }} - name: Update Android Target Sdk Version if: ${{ matrix.build-target == 'Android' }} run: | @@ -110,13 +122,6 @@ jobs: sed -i 's/AndroidTargetSdkVersion: [0-9]*/AndroidTargetSdkVersion: 32/' "${UNITY_PROJECT_PATH}/ProjectSettings/ProjectSettings.asset" # ensure android dependencies are installed unity-cli setup-unity -p "${UNITY_PROJECT_PATH}" -m android - - name: Build Project - if: ${{ steps.verify-project-path.outputs.RUN_BUILD == 'true' }} - timeout-minutes: 60 - run: | - # we don't have to specify the project path or unity editor path as unity-cli will use the environment variables - unity-cli run --log-name Validate -quit -executeMethod Utilities.Editor.BuildPipeline.UnityPlayerBuildTools.ValidateProject -importTMProEssentialsAsset - unity-cli run --log-name Build -buildTarget ${{ matrix.build-target }} -quit -executeMethod Utilities.Editor.BuildPipeline.UnityPlayerBuildTools.StartCommandLineBuild -sceneList Assets/Scenes/SampleScene.unity ${{ matrix.build-args }} - name: Uninstall Editor if: ${{ matrix.unity-version != 'none' }} run: | @@ -132,12 +137,12 @@ jobs: PACKAGE_MANAGER_LOG_PATH=$(unity-cli package-manager-logs) LICENSING_CLIENT_LOG_PATH=$(unity-cli licensing-client-logs) LICENSING_AUDIT_LOG_PATH=$(unity-cli licensing-audit-logs) - + echo "Hub Log Path: ${HUB_LOG_PATH}" echo "Package Manager Log Path: ${PACKAGE_MANAGER_LOG_PATH}" echo "Licensing Client Log Path: ${LICENSING_CLIENT_LOG_PATH}" echo "Licensing Audit Log Path: ${LICENSING_AUDIT_LOG_PATH}" - + if [ ! -f "${HUB_LOG_PATH}" ]; then echo "::warning:: Hub log file does not exist at ${HUB_LOG_PATH}" # find all info-log.json files in ~/.config/unity3d/ - print their paths @@ -151,18 +156,32 @@ jobs: find ~/.config/ -type f -exec echo "{}" \; echo "::warning:: Hub log file does not exist at any known location" fi - + if [ ! -f "${PACKAGE_MANAGER_LOG_PATH}" ]; then echo "::warning::Package Manager log file does not exist at ${PACKAGE_MANAGER_LOG_PATH}" fi - + if [ ! -f "${LICENSING_CLIENT_LOG_PATH}" ]; then echo "::error::Licensing Client log file does not exist at ${LICENSING_CLIENT_LOG_PATH}" fi - + if [ ! -f "${LICENSING_AUDIT_LOG_PATH}" ]; then echo "::error::Licensing Audit log file does not exist at ${LICENSING_AUDIT_LOG_PATH}" fi + - name: Compute UTP artifact name + if: always() + id: utp-artifact-name + env: + MATRIX_OS: ${{ matrix.os }} + MATRIX_UNITY_VERSION: ${{ matrix.unity-version }} + MATRIX_BUILD_TARGET: ${{ matrix.build-target }} + run: | + set -euo pipefail + unity_version="$MATRIX_UNITY_VERSION" + unity_version="${unity_version//\*/x}" + artifact_name="${MATRIX_OS}-${unity_version}-${MATRIX_BUILD_TARGET}-tests-batch-utp-logs" + echo "name=$artifact_name" >> $GITHUB_OUTPUT + shell: bash - name: Return License if: always() run: unity-cli return-license --license personal diff --git a/.gitignore b/.gitignore index 9a5acedf..b34eee66 100644 --- a/.gitignore +++ b/.gitignore @@ -137,3 +137,5 @@ dist # Vite logs files vite.config.js.timestamp-* vite.config.ts.timestamp-* + +.artifacts/ diff --git a/package-lock.json b/package-lock.json index 55a86a3b..10f98985 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@rage-against-the-pixel/unity-cli", - "version": "1.8.1", + "version": "1.8.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@rage-against-the-pixel/unity-cli", - "version": "1.8.1", + "version": "1.8.2", "license": "MIT", "dependencies": { "@electron/asar": "^4.0.1", @@ -15,7 +15,7 @@ "glob": "^11.1.0", "semver": "^7.7.3", "source-map-support": "^0.5.21", - "tar": "^7.5.2", + "tar": "^7.5.6", "update-notifier": "^7.3.1", "yaml": "^2.8.2" }, @@ -24,7 +24,7 @@ }, "devDependencies": { "@types/jest": "^30.0.0", - "@types/node": "^24.10.4", + "@types/node": "^24.10.9", "@types/semver": "^7.7.1", "@types/update-notifier": "^6.0.8", "jest": "^30.2.0", @@ -34,13 +34,13 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", + "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -49,9 +49,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", - "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", + "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", "dev": true, "license": "MIT", "engines": { @@ -59,21 +59,21 @@ } }, "node_modules/@babel/core": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", - "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", + "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5", + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -100,14 +100,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", - "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", + "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.5", - "@babel/types": "^7.28.5", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -117,13 +117,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.27.2", + "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", @@ -154,29 +154,29 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", - "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -186,9 +186,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", "dev": true, "license": "MIT", "engines": { @@ -226,27 +226,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", - "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", + "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.5" + "@babel/types": "^7.28.6" }, "bin": { "parser": "bin/babel-parser.js" @@ -311,13 +311,13 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -353,13 +353,13 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", - "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -479,13 +479,13 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", - "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -495,33 +495,33 @@ } }, "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", - "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", + "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.5", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6", "debug": "^4.3.1" }, "engines": { @@ -529,9 +529,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", + "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", "dev": true, "license": "MIT", "dependencies": { @@ -600,9 +600,9 @@ } }, "node_modules/@emnapi/core": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", - "integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", + "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", "dev": true, "license": "MIT", "optional": true, @@ -612,9 +612,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", - "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", "dev": true, "license": "MIT", "optional": true, @@ -1452,9 +1452,9 @@ "license": "ISC" }, "node_modules/@pnpm/npm-conf": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-2.3.1.tgz", - "integrity": "sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-3.0.2.tgz", + "integrity": "sha512-h104Kh26rR8tm+a3Qkc5S4VLYint3FE48as7+/5oCEcKR2idC/pF1G6AhIXKI+eHPJa/3J9i5z0Al47IeGHPkA==", "license": "MIT", "dependencies": { "@pnpm/config.env-replace": "^1.1.0", @@ -1475,9 +1475,9 @@ } }, "node_modules/@sinclair/typebox": { - "version": "0.34.41", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz", - "integrity": "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==", + "version": "0.34.48", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", + "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", "dev": true, "license": "MIT" }, @@ -1631,9 +1631,9 @@ } }, "node_modules/@types/node": { - "version": "24.10.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.4.tgz", - "integrity": "sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==", + "version": "24.10.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.9.tgz", + "integrity": "sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==", "dev": true, "license": "MIT", "dependencies": { @@ -2269,9 +2269,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.9.7", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.7.tgz", - "integrity": "sha512-k9xFKplee6KIio3IDbwj+uaCLpqzOwakOgmqzPezM0sFJlFKcg30vk2wOiAJtkTSfx0SSQDSe8q+mWA/fSH5Zg==", + "version": "2.9.18", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.18.tgz", + "integrity": "sha512-e23vBV1ZLfjb9apvfPk4rHVu2ry6RIr2Wfs+O324okSidrX7pTAnEJPCh/O5BtRlr7QtZI7ktOP3vsqr7Z5XoA==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2411,9 +2411,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001760", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001760.tgz", - "integrity": "sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==", + "version": "1.0.30001766", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz", + "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==", "dev": true, "funding": [ { @@ -2479,9 +2479,9 @@ } }, "node_modules/cjs-module-lexer": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.1.1.tgz", - "integrity": "sha512-+CmxIZ/L2vNcEfvNtLdU0ZQ6mbq3FZnwAP2PPTiKP+1QOoKwlKlPgb8UKV0Dds7QVaMnHm+FwSft2VB0s/SLjQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", "dev": true, "license": "MIT" }, @@ -2724,9 +2724,9 @@ } }, "node_modules/dedent": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz", - "integrity": "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.1.tgz", + "integrity": "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==", "dev": true, "license": "MIT", "peerDependencies": { @@ -2768,9 +2768,9 @@ } }, "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -2811,9 +2811,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.267", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", - "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", + "version": "1.5.278", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.278.tgz", + "integrity": "sha512-dQ0tM1svDRQOwxnXxm+twlGTjr9Upvt8UFWAgmLsxEzFQxhbti4VwxmMjsDxVC51Zo84swW7FVCXEV+VAkhuPw==", "dev": true, "license": "ISC" }, @@ -4719,9 +4719,9 @@ } }, "node_modules/ky": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/ky/-/ky-1.14.1.tgz", - "integrity": "sha512-hYje4L9JCmpEQBtudo+v52X5X8tgWXUYyPcxKSuxQNboqufecl9VMWjGiucAFH060AwPXHZuH+WB2rrqfkmafw==", + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/ky/-/ky-1.14.2.tgz", + "integrity": "sha512-q3RBbsO5A5zrPhB6CaCS8ZUv+NWCXv6JJT4Em0i264G9W0fdPB8YRfnnEi7Dm7X7omAkBIPojzYJ2D1oHTHqug==", "license": "MIT", "engines": { "node": ">=18" @@ -5287,12 +5287,12 @@ "license": "MIT" }, "node_modules/registry-auth-token": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.1.0.tgz", - "integrity": "sha512-GdekYuwLXLxMuFTwAPg5UKGLW/UXzQrZvH/Zj791BQif5T05T0RsaLfHc9q3ZOKi7n+BoprPD9mJ0O0k4xzUlw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.1.1.tgz", + "integrity": "sha512-P7B4+jq8DeD2nMsAcdfaqHbssgHtZ7Z5+++a5ask90fvmJ8p5je4mOa+wzu+DB4vQ5tdJV/xywY+UnVFeQLV5Q==", "license": "MIT", "dependencies": { - "@pnpm/npm-conf": "^2.1.0" + "@pnpm/npm-conf": "^3.0.2" }, "engines": { "node": ">=14" @@ -5635,9 +5635,9 @@ } }, "node_modules/synckit": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", - "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", + "version": "0.11.12", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", + "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5651,9 +5651,9 @@ } }, "node_modules/tar": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz", - "integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==", + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.6.tgz", + "integrity": "sha512-xqUeu2JAIJpXyvskvU3uvQW8PAmHrtXp2KDuMJwQqW8Sqq0CaZBAQ+dKS3RBXVhU4wC5NjAdKrmh84241gO9cA==", "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/fs-minipass": "^4.0.0", @@ -5968,9 +5968,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.2.tgz", - "integrity": "sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, "funding": [ { diff --git a/package.json b/package.json index 759c2d44..29138c96 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@rage-against-the-pixel/unity-cli", - "version": "1.8.1", + "version": "1.8.2", "description": "A command line utility for the Unity Game Engine.", "author": "RageAgainstThePixel", "license": "MIT", @@ -54,13 +54,13 @@ "glob": "^11.1.0", "semver": "^7.7.3", "source-map-support": "^0.5.21", - "tar": "^7.5.2", + "tar": "^7.5.6", "update-notifier": "^7.3.1", "yaml": "^2.8.2" }, "devDependencies": { "@types/jest": "^30.0.0", - "@types/node": "^24.10.4", + "@types/node": "^24.10.9", "@types/semver": "^7.7.1", "@types/update-notifier": "^6.0.8", "jest": "^30.2.0", diff --git a/src/logging.ts b/src/logging.ts index 16a1f15a..739760a5 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -1,4 +1,5 @@ import * as fs from 'fs'; +import { UTP } from './utp/utp'; export enum LogLevel { DEBUG = 'debug', @@ -258,19 +259,55 @@ export class Logger { } } - public CI_appendWorkflowSummary(telemetry: any[]) { + public CI_appendWorkflowSummary(name: string, telemetry: UTP[]) { + if (telemetry.length === 0) { return; } switch (this._ci) { case 'GITHUB_ACTIONS': { const githubSummary = process.env.GITHUB_STEP_SUMMARY; if (githubSummary) { - let table = `| Key | Value |\n| --- | ----- |\n`; - telemetry.forEach(item => { - table += `| ${item.key} | ${item.value} |\n`; - }); + // for now lets just log the number of items we get per type + const typeCounts: Record = {}; + for (const entry of telemetry) { + const type = entry.type || 'unknown'; + + if (!typeCounts[type]) { + typeCounts[type] = 0; + } + + typeCounts[type]++; + } + + let table = `## ${name} Summary\n\n| Type | Count |\n| --- | ---: |\n`; + for (const [type, count] of Object.entries(typeCounts)) { + table += `| ${type} | ${count} |\n`; + } + + // guard against very large summaries over 1MB. Trim at a row boundary to avoid mangled tables. + const byteLimit = 1024 * 1024; + if (Buffer.byteLength(table, 'utf8') > byteLimit) { + const footer = `\n| ... | ... |\n\n***Summary truncated due to size limits.***\n`; + const footerSize = Buffer.byteLength(footer, 'utf8'); + + const lines = table.split('\n'); + let rebuilt = ''; + + for (const line of lines) { + const nextSize = Buffer.byteLength(rebuilt + line + '\n', 'utf8') + footerSize; + + if (nextSize > byteLimit) { + break; + } + + rebuilt += `${line}\n`; + } + + table = rebuilt + footer; + } fs.appendFileSync(githubSummary, table, { encoding: 'utf8' }); } + break; } } } diff --git a/src/unity-editor.ts b/src/unity-editor.ts index 62d1fd77..bc3feafa 100644 --- a/src/unity-editor.ts +++ b/src/unity-editor.ts @@ -277,6 +277,7 @@ export class UnityEditor { const baseEditorEnv: NodeJS.ProcessEnv = { ...process.env, UNITY_THISISABUILDMACHINE: '1', + DISABLE_EMBEDDED_BUILD_PIPELINE_PLUGIN_LOGGING: '1', ...(linuxEnvOverrides ?? {}) }; diff --git a/src/unity-logging.ts b/src/unity-logging.ts index 52cbb9a2..d149bb5a 100644 --- a/src/unity-logging.ts +++ b/src/unity-logging.ts @@ -973,6 +973,8 @@ export function TailLogFile(logPath: string, projectPath: string | undefined): L if (telemetryFlushed) { return; } telemetryFlushed = true; await writeUtpTelemetryLog(utpLogPath, telemetry, logger); + const parsed = path.parse(logPath); + Logger.instance.CI_appendWorkflowSummary(parsed.name, telemetry); }; const writeStdoutThenTableContent = (content: string, restoreTable: boolean = true): void => { diff --git a/src/utp/utp.ts b/src/utp/utp.ts index a549304a..446f2c8c 100644 --- a/src/utp/utp.ts +++ b/src/utp/utp.ts @@ -20,13 +20,19 @@ export class UTPBase { errors?: unknown[]; } +export class UTPAction extends UTPBase { } + export class UTPMemoryLeak extends UTPBase { allocatedMemory?: number; memoryLabels?: Record | Array>; } +export class UTPMemoryLeaks extends UTPMemoryLeak { } + export class UTPLogEntry extends UTPBase { } +export class UTPCompiler extends UTPBase { } + export class UTPTestPlan extends UTPBase { tests?: string[]; } @@ -117,6 +123,8 @@ export class UTPPlayerBuildInfo extends UTPBase { } export type UTP = + | UTPAction + | UTPCompiler | UTPBase | UTPLogEntry | UTPTestPlan @@ -127,6 +135,7 @@ export type UTP = | UTPQualitySettings | UTPTestStatus | UTPMemoryLeak + | UTPMemoryLeaks | UTPPlayerBuildInfo; export enum Phase { diff --git a/unity-tests/BuildErrors.cs b/unity-tests/BuildErrors.cs new file mode 100644 index 00000000..dc344195 --- /dev/null +++ b/unity-tests/BuildErrors.cs @@ -0,0 +1,20 @@ +using UnityEditor; +using UnityEditor.Build; +using UnityEditor.Build.Reporting; + +namespace UnityCli.UtpSamples +{ + /// + /// Forces the build pipeline to fail by throwing a BuildFailedException. + /// Place under an Editor folder when copying into a project. + /// + public class BuildErrors : IPreprocessBuildWithReport + { + public int callbackOrder => 0; + + public void OnPreprocessBuild(BuildReport report) + { + throw new System.Exception("Intentional build failure."); + } + } +} diff --git a/unity-tests/BuildWarnings.cs b/unity-tests/BuildWarnings.cs new file mode 100644 index 00000000..e4c2a3d7 --- /dev/null +++ b/unity-tests/BuildWarnings.cs @@ -0,0 +1,20 @@ +using UnityEditor; +using UnityEditor.Build; +using UnityEditor.Build.Reporting; + +namespace UnityCli.UtpSamples +{ + /// + /// Emits a build-time warning via the build pipeline (no custom UTP JSON logging). + /// Place under an Editor folder when copying into a project. + /// + public class BuildWarnings : IPreprocessBuildWithReport + { + public int callbackOrder => 0; + + public void OnPreprocessBuild(BuildReport report) + { + UnityEngine.Debug.LogWarning("Intentional build warning."); + } + } +} diff --git a/unity-tests/CompilerErrors.cs b/unity-tests/CompilerErrors.cs new file mode 100644 index 00000000..056f16ee --- /dev/null +++ b/unity-tests/CompilerErrors.cs @@ -0,0 +1,4 @@ +// Intentional compiler error for matrix scenario coverage. +#error Intentional compiler error: CS1029 + +// Note: file is kept minimal so it can be copied into a project to force a build failure. diff --git a/unity-tests/CompilerWarnings.cs b/unity-tests/CompilerWarnings.cs new file mode 100644 index 00000000..ae35bd22 --- /dev/null +++ b/unity-tests/CompilerWarnings.cs @@ -0,0 +1,20 @@ +using UnityEngine; + +namespace UnityCli.UtpSamples +{ + /// + /// Introduces a benign compiler warning (unused variable) without emitting custom logs. + /// + public class CompilerWarnings : MonoBehaviour + { + private void Awake() + { + ObsoleteApi(); // CS0618: call to obsolete member + } + + [System.Obsolete("Intentional warning", false)] + private static void ObsoleteApi() + { + } + } +} diff --git a/unity-tests/EditmodeTestsErrors.cs b/unity-tests/EditmodeTestsErrors.cs new file mode 100644 index 00000000..a0304d8d --- /dev/null +++ b/unity-tests/EditmodeTestsErrors.cs @@ -0,0 +1,16 @@ +using NUnit.Framework; + +namespace UnityCli.UtpSamples +{ + /// + /// Editmode test that intentionally fails to produce real test failure output. + /// + public class EditmodeTestsErrors + { + [Test] + public void FailsEditmodeSuite() + { + Assert.Fail("Intentional editmode failure"); + } + } +} diff --git a/unity-tests/PlaymodeTestsErrors.cs b/unity-tests/PlaymodeTestsErrors.cs new file mode 100644 index 00000000..729b1664 --- /dev/null +++ b/unity-tests/PlaymodeTestsErrors.cs @@ -0,0 +1,19 @@ +using System.Collections; +using NUnit.Framework; +using UnityEngine.TestTools; + +namespace UnityCli.UtpSamples +{ + /// + /// Playmode test that intentionally fails to generate real test failure output. + /// + public class PlaymodeTestsErrors + { + [UnityTest] + public IEnumerator FailsPlaymodeSuite() + { + yield return null; + Assert.Fail("Intentional playmode failure for test matrix coverage."); + } + } +} diff --git a/unity-tests/UnityCliTests.EditMode.Editor.asmdef b/unity-tests/UnityCliTests.EditMode.Editor.asmdef new file mode 100644 index 00000000..ce9be1f3 --- /dev/null +++ b/unity-tests/UnityCliTests.EditMode.Editor.asmdef @@ -0,0 +1,14 @@ +{ + "name": "UnityCli.EditMode.EditorTests", + "references": [], + "optionalUnityReferences": ["TestAssemblies"], + "includePlatforms": ["Editor"], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +}