From b10438d53c4efbe4052dbef67908a5ca1988291d Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sat, 20 Dec 2025 16:37:46 -0500 Subject: [PATCH 01/56] unity-cli@v1.8.2 - add additional unity utp log handling --- .github/workflows/build-options.json | 8 ++++++++ .github/workflows/unity-build.yml | 17 ++++++++++++----- unity-tests/BuildErrors.cs | 20 ++++++++++++++++++++ unity-tests/BuildWarnings.cs | 20 ++++++++++++++++++++ unity-tests/CompilerErrors.cs | 4 ++++ unity-tests/CompilerWarnings.cs | 20 ++++++++++++++++++++ unity-tests/EditmodeTestsErrors.cs | 16 ++++++++++++++++ unity-tests/PlaymodeTestsErrors.cs | 19 +++++++++++++++++++ 8 files changed, 119 insertions(+), 5 deletions(-) create mode 100644 unity-tests/BuildErrors.cs create mode 100644 unity-tests/BuildWarnings.cs create mode 100644 unity-tests/CompilerErrors.cs create mode 100644 unity-tests/CompilerWarnings.cs create mode 100644 unity-tests/EditmodeTestsErrors.cs create mode 100644 unity-tests/PlaymodeTestsErrors.cs diff --git a/.github/workflows/build-options.json b/.github/workflows/build-options.json index b1671291..7da9e5d3 100644 --- a/.github/workflows/build-options.json +++ b/.github/workflows/build-options.json @@ -17,6 +17,14 @@ "6000.1.*", "6000.2" ], + "tests": [ + "CompilerWarnings", + "CompilerErrors", + "BuildWarnings", + "BuildErrors", + "PlaymodeTestsErrors", + "EditmodeTestsErrors" + ], "include": [ { "os": "ubuntu-latest", diff --git a/.github/workflows/unity-build.yml b/.github/workflows/unity-build.yml index 1e66910b..527eb23d 100644 --- a/.github/workflows/unity-build.yml +++ b/.github/workflows/unity-build.yml @@ -132,12 +132,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 +151,25 @@ 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: Upload UTP logs + if: always() + uses: actions/upload-artifact@v6 + with: + name: utp-logs-${{ matrix.name }} + path: '**/*-utp-json.log' + if-no-files-found: ignore - name: Return License if: always() run: unity-cli return-license --license personal diff --git a/unity-tests/BuildErrors.cs b/unity-tests/BuildErrors.cs new file mode 100644 index 00000000..ce75c3fe --- /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 for test matrix coverage."); + } + } +} diff --git a/unity-tests/BuildWarnings.cs b/unity-tests/BuildWarnings.cs new file mode 100644 index 00000000..f365a770 --- /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 for test matrix coverage."); + } + } +} 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..c1fc0778 --- /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 for test matrix coverage", false)] + private static void ObsoleteApi() + { + } + } +} diff --git a/unity-tests/EditmodeTestsErrors.cs b/unity-tests/EditmodeTestsErrors.cs new file mode 100644 index 00000000..4c4d89c9 --- /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 for test matrix coverage."); + } + } +} 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."); + } + } +} From ed170475fcdeba6af410bde2baa3d83f71873afb Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sat, 20 Dec 2025 16:38:47 -0500 Subject: [PATCH 02/56] bump --- package-lock.json | 28 ++++++++++++++-------------- package.json | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index 55a86a3b..8796015b 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", @@ -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.11", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz", + "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==", "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.30001761", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001761.tgz", + "integrity": "sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==", "dev": true, "funding": [ { @@ -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": { @@ -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..581f58cc 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", From 80515c3119c5abcbb6ea162e4e4d82a0b2427001 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sat, 20 Dec 2025 16:40:16 -0500 Subject: [PATCH 03/56] integrate tests --- .github/workflows/unity-build.yml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/.github/workflows/unity-build.yml b/.github/workflows/unity-build.yml index 527eb23d..85234f45 100644 --- a/.github/workflows/unity-build.yml +++ b/.github/workflows/unity-build.yml @@ -97,6 +97,37 @@ jobs: else echo "Skipping build: Unity version $version does not support the build pipeline package (requires 2019.4+)" fi + - name: Copy selected Unity test + if: ${{ matrix.unity-version != 'none' && matrix.tests != '' }} + run: | + set -euo pipefail + test_name="${{ matrix.tests }}" + src="${GITHUB_WORKSPACE}/unity-tests/${test_name}.cs" + if [ ! -f "$src" ]; then + echo "::error::Requested test '$test_name' not found at $src" && exit 1 + fi + + 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" + ;; + *) + echo "::error::Unknown test selection '$test_name'" && exit 1 + ;; + esac + + mkdir -p "$dest" + cp "$src" "$dest/" + echo "Copied $test_name to $dest" - name: Install OpenUPM and build pipeline package if: ${{ steps.verify-project-path.outputs.RUN_BUILD == 'true' }} working-directory: ${{ env.UNITY_PROJECT_PATH }} From f38b09944f9c778497378a00ae7be90215e80983 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sat, 20 Dec 2025 16:40:48 -0500 Subject: [PATCH 04/56] default permissions --- .github/workflows/unity-build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/unity-build.yml b/.github/workflows/unity-build.yml index 85234f45..b60e366b 100644 --- a/.github/workflows/unity-build.yml +++ b/.github/workflows/unity-build.yml @@ -17,6 +17,8 @@ jobs: strategy: matrix: ${{ fromJSON(inputs.matrix) }} fail-fast: false + permissions: + contents: read defaults: run: shell: bash From ca76f67d4684fbde99fdb1647dcaa8c9ecde7934 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sat, 20 Dec 2025 16:42:33 -0500 Subject: [PATCH 05/56] update test logic --- .github/workflows/unity-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/unity-build.yml b/.github/workflows/unity-build.yml index b60e366b..c8b3aeb4 100644 --- a/.github/workflows/unity-build.yml +++ b/.github/workflows/unity-build.yml @@ -100,7 +100,7 @@ jobs: echo "Skipping build: Unity version $version does not support the build pipeline package (requires 2019.4+)" fi - name: Copy selected Unity test - if: ${{ matrix.unity-version != 'none' && matrix.tests != '' }} + if: ${{ steps.verify-project-path.outputs.RUN_BUILD == 'true' && matrix.tests != '' }} run: | set -euo pipefail test_name="${{ matrix.tests }}" From b531116b9701f543d239ab286ea70c542e927c2f Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sat, 20 Dec 2025 18:00:06 -0500 Subject: [PATCH 06/56] update artifact names --- .github/workflows/unity-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/unity-build.yml b/.github/workflows/unity-build.yml index c8b3aeb4..075fe5ea 100644 --- a/.github/workflows/unity-build.yml +++ b/.github/workflows/unity-build.yml @@ -200,7 +200,7 @@ jobs: if: always() uses: actions/upload-artifact@v6 with: - name: utp-logs-${{ matrix.name }} + name: ${{ matrix.os }}-${{ matrix.unity-version }}-${{ matrix.build-target }}-utp-logs path: '**/*-utp-json.log' if-no-files-found: ignore - name: Return License From 0d99a0c53bdeed0246396bbc8be8f81de0cb2d4d Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sun, 21 Dec 2025 19:18:50 -0500 Subject: [PATCH 07/56] test dev job builder --- .github/workflows/build-options.json | 1 + .github/workflows/integration-tests.yml | 3 +- .github/workflows/unity-build.yml | 12 +++++-- .gitignore | 2 ++ src/logging.ts | 46 ++++++++++++++++++++++--- src/unity-editor.ts | 1 + src/unity-logging.ts | 2 ++ 7 files changed, 59 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build-options.json b/.github/workflows/build-options.json index 7da9e5d3..86304101 100644 --- a/.github/workflows/build-options.json +++ b/.github/workflows/build-options.json @@ -18,6 +18,7 @@ "6000.2" ], "tests": [ + "None", "CompilerWarnings", "CompilerErrors", "BuildWarnings", diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 06d1bd8e..02c80c42 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -15,11 +15,12 @@ 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: sparse-checkout: .github/ - - uses: RageAgainstThePixel/job-builder@v1 + - uses: RageAgainstThePixel/job-builder@development id: setup-jobs with: build-options: ./.github/workflows/build-options.json diff --git a/.github/workflows/unity-build.yml b/.github/workflows/unity-build.yml index 075fe5ea..b8323bdc 100644 --- a/.github/workflows/unity-build.yml +++ b/.github/workflows/unity-build.yml @@ -19,6 +19,7 @@ jobs: fail-fast: false permissions: contents: read + checks: write # to publish unit test results via checks github api defaults: run: shell: bash @@ -100,7 +101,7 @@ jobs: echo "Skipping build: Unity version $version does not support the build pipeline package (requires 2019.4+)" fi - name: Copy selected Unity test - if: ${{ steps.verify-project-path.outputs.RUN_BUILD == 'true' && matrix.tests != '' }} + if: ${{ steps.verify-project-path.outputs.RUN_BUILD == 'true' && matrix.tests != '' && matrix.tests != 'None' }} run: | set -euo pipefail test_name="${{ matrix.tests }}" @@ -136,6 +137,11 @@ jobs: run: | npm install -g openupm-cli openupm add com.utilities.buildpipeline + case "${{ matrix.tests }}" in + PlaymodeTestsErrors|EditmodeTestsErrors) + openupm add com.unity.test-framework + ;; + esac - name: Update Android Target Sdk Version if: ${{ matrix.build-target == 'Android' }} run: | @@ -147,9 +153,11 @@ jobs: if: ${{ steps.verify-project-path.outputs.RUN_BUILD == 'true' }} timeout-minutes: 60 run: | + set -euo pipefail # 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 }} + continue-on-error: ${{ matrix.tests != 'None' }} - name: Uninstall Editor if: ${{ matrix.unity-version != 'none' }} run: | @@ -200,7 +208,7 @@ jobs: if: always() uses: actions/upload-artifact@v6 with: - name: ${{ matrix.os }}-${{ matrix.unity-version }}-${{ matrix.build-target }}-utp-logs + name: ${{ matrix.os }}-${{ matrix.unity-version }}-${{ matrix.build-target }}-${{ matrix.tests }}-utp-logs path: '**/*-utp-json.log' if-no-files-found: ignore - name: Return License 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/src/logging.ts b/src/logging.ts index 16a1f15a..f8439b08 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,54 @@ export class Logger { } } - public CI_appendWorkflowSummary(telemetry: any[]) { + public CI_appendWorkflowSummary(name: string, telemetry: UTP[]) { 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 => { From 7d0df3d5f790c463119752bb7b192784bc6f053a Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sun, 21 Dec 2025 19:20:43 -0500 Subject: [PATCH 08/56] fix permissions --- .github/workflows/integration-tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 02c80c42..f72d8f10 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -33,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 From 2ce7a94abb69757216cdfdcc10bc5d9cb3fe433a Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sun, 21 Dec 2025 19:31:52 -0500 Subject: [PATCH 09/56] misc --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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] From 28d2f8e132321dbdfacea693e89e311d3751f7cf Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sun, 21 Dec 2025 19:41:25 -0500 Subject: [PATCH 10/56] don't write summary if no telemetry output --- src/logging.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/logging.ts b/src/logging.ts index f8439b08..739760a5 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -260,6 +260,7 @@ export class Logger { } 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; From fe5423a38984b967fbdd8f887a91265d692fa219 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sun, 28 Dec 2025 11:39:46 -0500 Subject: [PATCH 11/56] add additional utp types for logging [skip ci] --- src/utp/utp.ts | 9 +++++++++ 1 file changed, 9 insertions(+) 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 { From 545be7a98fd1acb8572230ddcb3a06991a1dbb5e Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sun, 28 Dec 2025 11:49:27 -0500 Subject: [PATCH 12/56] rework tests --- .../actions/run-unity-test-batch/action.yml | 134 ++++++++++++++++++ .github/workflows/build-options.json | 9 -- .github/workflows/unity-build.yml | 58 +------- 3 files changed, 141 insertions(+), 60 deletions(-) create mode 100644 .github/actions/run-unity-test-batch/action.yml 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..eb280d20 --- /dev/null +++ b/.github/actions/run-unity-test-batch/action.yml @@ -0,0 +1,134 @@ +name: Run Unity Test Batch +description: Run a list of Unity tests in a single job/install and upload UTP logs per test. +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: "" + workspace: + description: Root workspace path (defaults to github.workspace). + required: false + default: ${{ github.workspace }} +runs: + using: composite + steps: + - name: Prepare test list and install packages + shell: bash + 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 sequentially + shell: bash + env: + UNITY_PROJECT_PATH: ${{ inputs.unity_project_path }} + BUILD_TARGET: ${{ inputs.build_target }} + BUILD_ARGS: ${{ inputs.build_args }} + WORKSPACE_ROOT: ${{ inputs.workspace }} + run: | + set -euo pipefail + + tests_input="$TESTS_INPUT" + 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 + } + + mkdir -p "$WORKSPACE_ROOT/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="$WORKSPACE_ROOT/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 + + 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" + ;; + *) + echo "::error::Unknown test selection '$test_name'" + failures=$((failures+1)) + continue + ;; + esac + + mkdir -p "$dest" + cp "$src" "$dest/" + echo "Running test: $test_name (copied to $dest)" + + if unity-cli run --log-name "${test_name}-Validate" -quit -executeMethod Utilities.Editor.BuildPipeline.UnityPlayerBuildTools.ValidateProject -importTMProEssentialsAsset && \ + unity-cli run --log-name "${test_name}-Build" -buildTarget "$BUILD_TARGET" -quit -executeMethod Utilities.Editor.BuildPipeline.UnityPlayerBuildTools.StartCommandLineBuild -sceneList Assets/Scenes/SampleScene.unity $BUILD_ARGS; then + echo "::notice::Test $test_name succeeded" + else + echo "::error::Test $test_name failed" + failures=$((failures+1)) + fi + + # Collect logs for this test + test_artifacts="$WORKSPACE_ROOT/utp-artifacts/$test_name" + mkdir -p "$test_artifacts" + find "$WORKSPACE_ROOT" -type f -name "*${test_name}*-utp-json.log" -print -exec cp {} "$test_artifacts" \; || true + + # Reset the Unity project to a clean state before the next test + if git -C "$UNITY_PROJECT_PATH" rev-parse --is-inside-work-tree >/dev/null 2>&1; then + git -C "$UNITY_PROJECT_PATH" clean -fdx + git -C "$UNITY_PROJECT_PATH" reset --hard + else + echo "::warning::UNITY_PROJECT_PATH is not a git repository; skipping git clean/reset" + fi + done + + if [ "$failures" -gt 0 ]; then + echo "::error::One or more tests failed in batch ($failures)" + exit 1 + fi + + - name: Upload UTP logs (per test) + uses: actions/upload-artifact@v6 + with: + name: unity-tests-batch-utp-logs + path: utp-artifacts/**/*-utp-json.log + if-no-files-found: ignore diff --git a/.github/workflows/build-options.json b/.github/workflows/build-options.json index 86304101..b1671291 100644 --- a/.github/workflows/build-options.json +++ b/.github/workflows/build-options.json @@ -17,15 +17,6 @@ "6000.1.*", "6000.2" ], - "tests": [ - "None", - "CompilerWarnings", - "CompilerErrors", - "BuildWarnings", - "BuildErrors", - "PlaymodeTestsErrors", - "EditmodeTestsErrors" - ], "include": [ { "os": "ubuntu-latest", diff --git a/.github/workflows/unity-build.yml b/.github/workflows/unity-build.yml index b8323bdc..ec7ccbae 100644 --- a/.github/workflows/unity-build.yml +++ b/.github/workflows/unity-build.yml @@ -100,48 +100,13 @@ jobs: else echo "Skipping build: Unity version $version does not support the build pipeline package (requires 2019.4+)" fi - - name: Copy selected Unity test - if: ${{ steps.verify-project-path.outputs.RUN_BUILD == 'true' && matrix.tests != '' && matrix.tests != 'None' }} - run: | - set -euo pipefail - test_name="${{ matrix.tests }}" - src="${GITHUB_WORKSPACE}/unity-tests/${test_name}.cs" - if [ ! -f "$src" ]; then - echo "::error::Requested test '$test_name' not found at $src" && exit 1 - fi - - 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" - ;; - *) - echo "::error::Unknown test selection '$test_name'" && exit 1 - ;; - esac - - mkdir -p "$dest" - cp "$src" "$dest/" - echo "Copied $test_name to $dest" - - name: Install OpenUPM and build pipeline package + - name: Run Unity test batch (single install) if: ${{ steps.verify-project-path.outputs.RUN_BUILD == 'true' }} - working-directory: ${{ env.UNITY_PROJECT_PATH }} - run: | - npm install -g openupm-cli - openupm add com.utilities.buildpipeline - case "${{ matrix.tests }}" in - PlaymodeTestsErrors|EditmodeTestsErrors) - openupm add com.unity.test-framework - ;; - esac + 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 }} - name: Update Android Target Sdk Version if: ${{ matrix.build-target == 'Android' }} run: | @@ -149,15 +114,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: | - set -euo pipefail - # 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 }} - continue-on-error: ${{ matrix.tests != 'None' }} - name: Uninstall Editor if: ${{ matrix.unity-version != 'none' }} run: | @@ -208,7 +164,7 @@ jobs: if: always() uses: actions/upload-artifact@v6 with: - name: ${{ matrix.os }}-${{ matrix.unity-version }}-${{ matrix.build-target }}-${{ matrix.tests }}-utp-logs + name: ${{ matrix.os }}-${{ matrix.unity-version }}-${{ matrix.build-target }}-tests-batch-utp-logs path: '**/*-utp-json.log' if-no-files-found: ignore - name: Return License From 5b6735812b4a740e4a3c4432beb86f1cf826c0d5 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sun, 28 Dec 2025 12:09:53 -0500 Subject: [PATCH 13/56] tweaks --- .../actions/run-unity-test-batch/action.yml | 42 ++++++++----------- .github/workflows/unity-build.yml | 8 ++-- 2 files changed, 22 insertions(+), 28 deletions(-) diff --git a/.github/actions/run-unity-test-batch/action.yml b/.github/actions/run-unity-test-batch/action.yml index eb280d20..6b27391a 100644 --- a/.github/actions/run-unity-test-batch/action.yml +++ b/.github/actions/run-unity-test-batch/action.yml @@ -1,20 +1,15 @@ -name: Run Unity Test Batch -description: Run a list of Unity tests in a single job/install and upload UTP logs per test. +name: Run Unity UTP Test Batch +description: Runs a batch of Unity UTP tests in a given Unity project. inputs: - unity_project_path: + unity-project-path: description: Absolute path to the Unity project. required: true - build_target: + build-target: description: Build target to use. required: true - build_args: + build-args: description: Additional build args. - required: false - default: "" - workspace: - description: Root workspace path (defaults to github.workspace). - required: false - default: ${{ github.workspace }} + required: true runs: using: composite steps: @@ -39,10 +34,9 @@ runs: - name: Run tests sequentially shell: bash env: - UNITY_PROJECT_PATH: ${{ inputs.unity_project_path }} - BUILD_TARGET: ${{ inputs.build_target }} - BUILD_ARGS: ${{ inputs.build_args }} - WORKSPACE_ROOT: ${{ inputs.workspace }} + UNITY_PROJECT_PATH: ${{ inputs.unity-project-path }} + BUILD_TARGET: ${{ inputs.build-target }} + BUILD_ARGS: ${{ inputs.build-args }} run: | set -euo pipefail @@ -57,7 +51,7 @@ runs: rm -f "$UNITY_PROJECT_PATH/Assets/Tests/EditMode/UnityCliTests"/*.cs 2>/dev/null || true } - mkdir -p "$WORKSPACE_ROOT/utp-artifacts" + mkdir -p "$GITHUB_WORKSPACE/utp-artifacts" for raw_test in "${tests[@]}"; do test_name="$(echo "$raw_test" | xargs)" @@ -66,7 +60,7 @@ runs: continue fi - src="$WORKSPACE_ROOT/unity-tests/${test_name}.cs" + 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)) @@ -108,16 +102,16 @@ runs: fi # Collect logs for this test - test_artifacts="$WORKSPACE_ROOT/utp-artifacts/$test_name" + test_artifacts="$GITHUB_WORKSPACE/utp-artifacts/$test_name" mkdir -p "$test_artifacts" - find "$WORKSPACE_ROOT" -type f -name "*${test_name}*-utp-json.log" -print -exec cp {} "$test_artifacts" \; || true + find "$GITHUB_WORKSPACE" -type f -name "*${test_name}*-utp-json.log" -print -exec cp {} "$test_artifacts" \; || true # Reset the Unity project to a clean state before the next test - if git -C "$UNITY_PROJECT_PATH" rev-parse --is-inside-work-tree >/dev/null 2>&1; then - git -C "$UNITY_PROJECT_PATH" clean -fdx - git -C "$UNITY_PROJECT_PATH" reset --hard + if git -C "$GITHUB_WORKSPACE" rev-parse --is-inside-work-tree >/dev/null 2>&1; then + git -C "$GITHUB_WORKSPACE" clean -fdx + git -C "$GITHUB_WORKSPACE" reset --hard else - echo "::warning::UNITY_PROJECT_PATH is not a git repository; skipping git clean/reset" + echo "::warning::GITHUB_WORKSPACE is not a git repository; skipping git clean/reset" fi done @@ -126,7 +120,7 @@ runs: exit 1 fi - - name: Upload UTP logs (per test) + - name: Upload UTP logs uses: actions/upload-artifact@v6 with: name: unity-tests-batch-utp-logs diff --git a/.github/workflows/unity-build.yml b/.github/workflows/unity-build.yml index ec7ccbae..2defcb05 100644 --- a/.github/workflows/unity-build.yml +++ b/.github/workflows/unity-build.yml @@ -100,13 +100,13 @@ jobs: else echo "Skipping build: Unity version $version does not support the build pipeline package (requires 2019.4+)" fi - - name: Run Unity test batch (single install) + - 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 }} + unity-project-path: ${{ steps.verify-project-path.outputs.UNITY_PROJECT_PATH }} + build-target: ${{ matrix.build-target }} + build-args: ${{ matrix.build-args }} - name: Update Android Target Sdk Version if: ${{ matrix.build-target == 'Android' }} run: | From c7bbdd172515f617e17c3a7fd199a0b9fee29ef4 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sun, 28 Dec 2025 14:00:48 -0500 Subject: [PATCH 14/56] fixes to workflow --- .github/workflows/integration-tests.yml | 2 +- .github/workflows/unity-build.yml | 20 +++++++++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index f72d8f10..9b02d643 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -20,7 +20,7 @@ jobs: - uses: actions/checkout@v6 with: sparse-checkout: .github/ - - uses: RageAgainstThePixel/job-builder@development + - uses: RageAgainstThePixel/job-builder@v1 id: setup-jobs with: build-options: ./.github/workflows/build-options.json diff --git a/.github/workflows/unity-build.yml b/.github/workflows/unity-build.yml index 2defcb05..c40eb45c 100644 --- a/.github/workflows/unity-build.yml +++ b/.github/workflows/unity-build.yml @@ -104,7 +104,7 @@ jobs: if: ${{ steps.verify-project-path.outputs.RUN_BUILD == 'true' }} uses: ./.github/actions/run-unity-test-batch with: - unity-project-path: ${{ steps.verify-project-path.outputs.UNITY_PROJECT_PATH }} + unity-project-path: ${{ env.UNITY_PROJECT_PATH }} build-target: ${{ matrix.build-target }} build-args: ${{ matrix.build-args }} - name: Update Android Target Sdk Version @@ -160,11 +160,25 @@ jobs: if [ ! -f "${LICENSING_AUDIT_LOG_PATH}" ]; then echo "::error::Licensing Audit log file does not exist at ${LICENSING_AUDIT_LOG_PATH}" fi - - name: Upload UTP logs + - 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: Upload UTP logs artifact if: always() uses: actions/upload-artifact@v6 with: - name: ${{ matrix.os }}-${{ matrix.unity-version }}-${{ matrix.build-target }}-tests-batch-utp-logs + name: ${{ steps.utp-artifact-name.outputs.name }} path: '**/*-utp-json.log' if-no-files-found: ignore - name: Return License From 4a6cacc146d74fdd1b7d41d83d983b7ee85ef484 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sun, 28 Dec 2025 15:46:23 -0500 Subject: [PATCH 15/56] fix openupm installs --- .github/actions/run-unity-test-batch/action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/run-unity-test-batch/action.yml b/.github/actions/run-unity-test-batch/action.yml index 6b27391a..e14f98a0 100644 --- a/.github/actions/run-unity-test-batch/action.yml +++ b/.github/actions/run-unity-test-batch/action.yml @@ -15,6 +15,7 @@ runs: 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" From 32278ac06be2c994161e3f5d4dd0ba6c9d20ead7 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sun, 4 Jan 2026 11:11:18 -0500 Subject: [PATCH 16/56] update matrix build artifact names --- .github/actions/run-unity-test-batch/action.yml | 6 +++++- .github/workflows/unity-build.yml | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/actions/run-unity-test-batch/action.yml b/.github/actions/run-unity-test-batch/action.yml index e14f98a0..806e97c2 100644 --- a/.github/actions/run-unity-test-batch/action.yml +++ b/.github/actions/run-unity-test-batch/action.yml @@ -10,6 +10,10 @@ inputs: build-args: description: Additional build args. required: true + 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: @@ -124,6 +128,6 @@ runs: - name: Upload UTP logs uses: actions/upload-artifact@v6 with: - name: unity-tests-batch-utp-logs + name: ${{ inputs.artifact-name }} path: utp-artifacts/**/*-utp-json.log if-no-files-found: ignore diff --git a/.github/workflows/unity-build.yml b/.github/workflows/unity-build.yml index c40eb45c..97d2698a 100644 --- a/.github/workflows/unity-build.yml +++ b/.github/workflows/unity-build.yml @@ -107,6 +107,7 @@ jobs: unity-project-path: ${{ env.UNITY_PROJECT_PATH }} build-target: ${{ matrix.build-target }} build-args: ${{ matrix.build-args }} + artifact-name: ${{ matrix.os }}-${{ matrix.unity-version }}-${{ matrix.build-target }}-tests-batch-utp-logs - name: Update Android Target Sdk Version if: ${{ matrix.build-target == 'Android' }} run: | From 15bce316530014ea63427371fc7d73db0d698d31 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sun, 4 Jan 2026 21:38:06 -0500 Subject: [PATCH 17/56] don't clean between runs --- .github/actions/run-unity-test-batch/action.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/actions/run-unity-test-batch/action.yml b/.github/actions/run-unity-test-batch/action.yml index 806e97c2..f4863355 100644 --- a/.github/actions/run-unity-test-batch/action.yml +++ b/.github/actions/run-unity-test-batch/action.yml @@ -110,14 +110,6 @@ runs: test_artifacts="$GITHUB_WORKSPACE/utp-artifacts/$test_name" mkdir -p "$test_artifacts" find "$GITHUB_WORKSPACE" -type f -name "*${test_name}*-utp-json.log" -print -exec cp {} "$test_artifacts" \; || true - - # Reset the Unity project to a clean state before the next test - if git -C "$GITHUB_WORKSPACE" rev-parse --is-inside-work-tree >/dev/null 2>&1; then - git -C "$GITHUB_WORKSPACE" clean -fdx - git -C "$GITHUB_WORKSPACE" reset --hard - else - echo "::warning::GITHUB_WORKSPACE is not a git repository; skipping git clean/reset" - fi done if [ "$failures" -gt 0 ]; then From 937832a36667f32cb5beae9cd38b910c52f4525e Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sun, 4 Jan 2026 21:38:54 -0500 Subject: [PATCH 18/56] cleanup artifacts between tests --- .github/actions/run-unity-test-batch/action.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/actions/run-unity-test-batch/action.yml b/.github/actions/run-unity-test-batch/action.yml index f4863355..61f7f842 100644 --- a/.github/actions/run-unity-test-batch/action.yml +++ b/.github/actions/run-unity-test-batch/action.yml @@ -56,6 +56,11 @@ runs: rm -f "$UNITY_PROJECT_PATH/Assets/Tests/EditMode/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" + } + mkdir -p "$GITHUB_WORKSPACE/utp-artifacts" for raw_test in "${tests[@]}"; do @@ -73,6 +78,7 @@ runs: fi clean_tests + clean_build_outputs case "$test_name" in CompilerWarnings|CompilerErrors) @@ -109,7 +115,7 @@ runs: # Collect logs for this test test_artifacts="$GITHUB_WORKSPACE/utp-artifacts/$test_name" mkdir -p "$test_artifacts" - find "$GITHUB_WORKSPACE" -type f -name "*${test_name}*-utp-json.log" -print -exec cp {} "$test_artifacts" \; || true + find "$GITHUB_WORKSPACE" -path "$test_artifacts" -prune -o -type f -name "*${test_name}*-utp-json.log" -print -exec cp -n {} "$test_artifacts" \; || true done if [ "$failures" -gt 0 ]; then From 1c532076b88f36a2ed9b79e681180744e0f2c197 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sat, 10 Jan 2026 12:29:36 -0500 Subject: [PATCH 19/56] upgate utp tests --- .../actions/run-unity-test-batch/action.yml | 82 +---------- .github/actions/scripts/run-utp-tests.sh | 136 ++++++++++++++++++ 2 files changed, 138 insertions(+), 80 deletions(-) create mode 100755 .github/actions/scripts/run-utp-tests.sh diff --git a/.github/actions/run-unity-test-batch/action.yml b/.github/actions/run-unity-test-batch/action.yml index 61f7f842..0d32d79c 100644 --- a/.github/actions/run-unity-test-batch/action.yml +++ b/.github/actions/run-unity-test-batch/action.yml @@ -36,92 +36,14 @@ runs: openupm add com.unity.test-framework fi - - name: Run tests sequentially + - name: Run tests shell: bash env: UNITY_PROJECT_PATH: ${{ inputs.unity-project-path }} BUILD_TARGET: ${{ inputs.build-target }} BUILD_ARGS: ${{ inputs.build-args }} run: | - set -euo pipefail - - tests_input="$TESTS_INPUT" - 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 - } - - clean_build_outputs() { - rm -rf "$UNITY_PROJECT_PATH/Builds" 2>/dev/null || true - mkdir -p "$UNITY_PROJECT_PATH/Builds/Logs" - } - - 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 - - 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" - ;; - *) - echo "::error::Unknown test selection '$test_name'" - failures=$((failures+1)) - continue - ;; - esac - - mkdir -p "$dest" - cp "$src" "$dest/" - echo "Running test: $test_name (copied to $dest)" - - if unity-cli run --log-name "${test_name}-Validate" -quit -executeMethod Utilities.Editor.BuildPipeline.UnityPlayerBuildTools.ValidateProject -importTMProEssentialsAsset && \ - unity-cli run --log-name "${test_name}-Build" -buildTarget "$BUILD_TARGET" -quit -executeMethod Utilities.Editor.BuildPipeline.UnityPlayerBuildTools.StartCommandLineBuild -sceneList Assets/Scenes/SampleScene.unity $BUILD_ARGS; then - echo "::notice::Test $test_name succeeded" - else - echo "::error::Test $test_name failed" - failures=$((failures+1)) - fi - - # Collect logs for this test - 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 -exec cp -n {} "$test_artifacts" \; || true - done - - if [ "$failures" -gt 0 ]; then - echo "::error::One or more tests failed in batch ($failures)" - exit 1 - fi + bash "${GITHUB_WORKSPACE}/.github/actions/scripts/run-utp-tests.sh" - name: Upload UTP logs uses: actions/upload-artifact@v6 diff --git a/.github/actions/scripts/run-utp-tests.sh b/.github/actions/scripts/run-utp-tests.sh new file mode 100755 index 00000000..98d8ad1f --- /dev/null +++ b/.github/actions/scripts/run-utp-tests.sh @@ -0,0 +1,136 @@ +#!/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:-} + +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 +} + +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 +declare -A expected_status +expected_status[CompilerWarnings]=0 +expected_status[BuildWarnings]=0 +expected_status[CompilerErrors]=1 +expected_status[BuildErrors]=1 +expected_status[PlaymodeTestsErrors]=1 +expected_status[EditmodeTestsErrors]=1 + +declare -A expected_message +expected_message[CompilerErrors]="Intentional compiler error" +expected_message[BuildErrors]="Intentional build failure" +expected_message[PlaymodeTestsErrors]="Intentional playmode failure" +expected_message[EditmodeTestsErrors]="Intentional editmode failure" +expected_message[CompilerWarnings]="Intentional warning" +expected_message[BuildWarnings]="Intentional build warning" + +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 + + 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" + ;; + *) + echo "::error::Unknown test selection '$test_name'" + failures=$((failures+1)) + continue + ;; + esac + + mkdir -p "$dest" + cp "$src" "$dest/" + echo "Running test: $test_name (copied to $dest)" + + validate_rc=0 + build_rc=0 + + unity-cli run --log-name "${test_name}-Validate" -quit -executeMethod Utilities.Editor.BuildPipeline.UnityPlayerBuildTools.ValidateProject -importTMProEssentialsAsset || validate_rc=$? + unity-cli run --log-name "${test_name}-Build" -buildTarget "$BUILD_TARGET" -quit -executeMethod Utilities.Editor.BuildPipeline.UnityPlayerBuildTools.StartCommandLineBuild -sceneList Assets/Scenes/SampleScene.unity $BUILD_ARGS || build_rc=$? + + expected=${expected_status[$test_name]:-0} + exp_msg=${expected_message[$test_name]:-} + + test_failed=0 + + 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 + else + if [ "$validate_rc" -eq 0 ] && [ "$build_rc" -eq 0 ]; then + echo "::error::Test $test_name was expected to fail but succeeded" + test_failed=1 + fi + fi + + # Check logs for expected message when provided + if [ "$test_failed" -eq 0 ] && [ -n "$exp_msg" ]; then + validate_log=$(find "$UNITY_PROJECT_PATH/Builds/Logs" -maxdepth 1 -type f -name "*${test_name}-Validate*.log" | head -n 1) + build_log=$(find "$UNITY_PROJECT_PATH/Builds/Logs" -maxdepth 1 -type f -name "*${test_name}-Build*.log" | head -n 1) + + if ! grep -qi "$exp_msg" "$validate_log" "$build_log" 2>/dev/null; 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 -exec cp -n {} "$test_artifacts" \; || true + +done + +if [ "$failures" -gt 0 ]; then + echo "::error::One or more tests did not meet expectations ($failures)" + exit 1 +fi + +exit 0 From a9f7246dd327c09605ee14171f1cfc844773dcb7 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sat, 10 Jan 2026 19:58:07 -0500 Subject: [PATCH 20/56] update run utp tests --- .github/actions/scripts/run-utp-tests.sh | 30 +++++++++++++++++------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/.github/actions/scripts/run-utp-tests.sh b/.github/actions/scripts/run-utp-tests.sh index 98d8ad1f..a37dd431 100755 --- a/.github/actions/scripts/run-utp-tests.sh +++ b/.github/actions/scripts/run-utp-tests.sh @@ -92,25 +92,37 @@ for raw_test in "${tests[@]}"; do exp_msg=${expected_message[$test_name]:-} test_failed=0 + message_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 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 [ -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" -eq 0 ] && [ "$build_rc" -eq 0 ]; then + if [ "$validate_rc" -ne 0 ] || [ "$build_rc" -ne 0 ] || [ "$message_found" -eq 1 ]; then + : # Expected failure observed + else echo "::error::Test $test_name was expected to fail but succeeded" test_failed=1 fi - fi - - # Check logs for expected message when provided - if [ "$test_failed" -eq 0 ] && [ -n "$exp_msg" ]; then - validate_log=$(find "$UNITY_PROJECT_PATH/Builds/Logs" -maxdepth 1 -type f -name "*${test_name}-Validate*.log" | head -n 1) - build_log=$(find "$UNITY_PROJECT_PATH/Builds/Logs" -maxdepth 1 -type f -name "*${test_name}-Build*.log" | head -n 1) - - if ! grep -qi "$exp_msg" "$validate_log" "$build_log" 2>/dev/null; then + 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 From c48ad26963391d514d285f0243a5d4749bee2c29 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sun, 11 Jan 2026 11:28:19 -0500 Subject: [PATCH 21/56] relax expected message success check --- .github/actions/scripts/run-utp-tests.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/actions/scripts/run-utp-tests.sh b/.github/actions/scripts/run-utp-tests.sh index a37dd431..5feb61bc 100755 --- a/.github/actions/scripts/run-utp-tests.sh +++ b/.github/actions/scripts/run-utp-tests.sh @@ -122,7 +122,9 @@ for raw_test in "${tests[@]}"; do echo "::error::Test $test_name was expected to fail but succeeded" test_failed=1 fi - if [ -n "$exp_msg" ] && [ "$message_found" -eq 0 ]; then + + # 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 From cf870e68d943a96d7965a366ee238321438e7a21 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sun, 11 Jan 2026 14:08:00 -0500 Subject: [PATCH 22/56] update utp tests --- .github/actions/run-unity-test-batch/action.yml | 3 ++- .github/actions/scripts/run-utp-tests.sh | 13 ++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/actions/run-unity-test-batch/action.yml b/.github/actions/run-unity-test-batch/action.yml index 0d32d79c..746653b7 100644 --- a/.github/actions/run-unity-test-batch/action.yml +++ b/.github/actions/run-unity-test-batch/action.yml @@ -9,7 +9,8 @@ inputs: required: true build-args: description: Additional build args. - required: true + required: false + default: "" artifact-name: description: Artifact name for uploaded UTP logs (must be unique per matrix job). required: false diff --git a/.github/actions/scripts/run-utp-tests.sh b/.github/actions/scripts/run-utp-tests.sh index 5feb61bc..c79a50ed 100755 --- a/.github/actions/scripts/run-utp-tests.sh +++ b/.github/actions/scripts/run-utp-tests.sh @@ -6,6 +6,17 @@ 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 + +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 @@ -86,7 +97,7 @@ for raw_test in "${tests[@]}"; do build_rc=0 unity-cli run --log-name "${test_name}-Validate" -quit -executeMethod Utilities.Editor.BuildPipeline.UnityPlayerBuildTools.ValidateProject -importTMProEssentialsAsset || validate_rc=$? - unity-cli run --log-name "${test_name}-Build" -buildTarget "$BUILD_TARGET" -quit -executeMethod Utilities.Editor.BuildPipeline.UnityPlayerBuildTools.StartCommandLineBuild -sceneList Assets/Scenes/SampleScene.unity $BUILD_ARGS || build_rc=$? + unity-cli run --log-name "${test_name}-Build" -buildTarget "$BUILD_TARGET" -quit -executeMethod Utilities.Editor.BuildPipeline.UnityPlayerBuildTools.StartCommandLineBuild -sceneList Assets/Scenes/SampleScene.unity "${build_args[@]}" || build_rc=$? expected=${expected_status[$test_name]:-0} exp_msg=${expected_message[$test_name]:-} From 5e644f27542dd0284c43ab3f8ebe58bd6c8eed79 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sun, 11 Jan 2026 14:09:42 -0500 Subject: [PATCH 23/56] update utp tests --- .github/actions/scripts/run-utp-tests.sh | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/.github/actions/scripts/run-utp-tests.sh b/.github/actions/scripts/run-utp-tests.sh index c79a50ed..60784ec2 100755 --- a/.github/actions/scripts/run-utp-tests.sh +++ b/.github/actions/scripts/run-utp-tests.sh @@ -104,6 +104,7 @@ for raw_test in "${tests[@]}"; do test_failed=0 message_found=0 + utp_error_found=0 if [ -n "$exp_msg" ]; then while IFS= read -r log_file; do @@ -117,17 +118,32 @@ for raw_test in "${tests[@]}"; do 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 node -e "const fs=require('fs');const p=process.argv[1];try{const data=JSON.parse(fs.readFileSync(p,'utf8'));if(Array.isArray(data)&&data.some(e=>['Error','Exception','Assert'].includes(e?.severity))){process.exit(0);} }catch{}process.exit(1);" "$utp_file"; 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 ]; then + 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" From 78cd83d32ecbbaef3996275f6d8238c578aa994a Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sun, 11 Jan 2026 16:26:04 -0500 Subject: [PATCH 24/56] update edit mode test error --- .github/actions/scripts/run-utp-tests.sh | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/actions/scripts/run-utp-tests.sh b/.github/actions/scripts/run-utp-tests.sh index 60784ec2..2074956f 100755 --- a/.github/actions/scripts/run-utp-tests.sh +++ b/.github/actions/scripts/run-utp-tests.sh @@ -96,8 +96,18 @@ for raw_test in "${tests[@]}"; do validate_rc=0 build_rc=0 - unity-cli run --log-name "${test_name}-Validate" -quit -executeMethod Utilities.Editor.BuildPipeline.UnityPlayerBuildTools.ValidateProject -importTMProEssentialsAsset || validate_rc=$? - unity-cli run --log-name "${test_name}-Build" -buildTarget "$BUILD_TARGET" -quit -executeMethod Utilities.Editor.BuildPipeline.UnityPlayerBuildTools.StartCommandLineBuild -sceneList Assets/Scenes/SampleScene.unity "${build_args[@]}" || build_rc=$? + ran_custom_flow=0 + + if [ "$test_name" = "EditmodeTestsErrors" ]; then + unity-cli run --log-name "${test_name}-EditMode" -runTests -testPlatform editmode -testResults "$UNITY_PROJECT_PATH/Builds/Logs/${test_name}-results.xml" -quit || validate_rc=$? + 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=$? + unity-cli run --log-name "${test_name}-Build" -buildTarget "$BUILD_TARGET" -quit -executeMethod Utilities.Editor.BuildPipeline.UnityPlayerBuildTools.StartCommandLineBuild -sceneList Assets/Scenes/SampleScene.unity "${build_args[@]}" || build_rc=$? + fi expected=${expected_status[$test_name]:-0} exp_msg=${expected_message[$test_name]:-} From e4a60c65a6d4e91ecdfb3b07ce7ca125eaee2c55 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sun, 11 Jan 2026 18:35:56 -0500 Subject: [PATCH 25/56] update tests --- .github/actions/scripts/run-utp-tests.sh | 3 ++- .github/workflows/build-options.json | 3 ++- unity-tests/BuildErrors.cs | 2 +- unity-tests/BuildWarnings.cs | 2 +- unity-tests/CompilerWarnings.cs | 2 +- unity-tests/EditmodeTestsErrors.cs | 2 +- 6 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/actions/scripts/run-utp-tests.sh b/.github/actions/scripts/run-utp-tests.sh index 2074956f..a2e37e84 100755 --- a/.github/actions/scripts/run-utp-tests.sh +++ b/.github/actions/scripts/run-utp-tests.sh @@ -25,6 +25,7 @@ clean_tests() { 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/Editor/UnityCliTests"/*.cs 2>/dev/null || true } clean_build_outputs() { @@ -80,7 +81,7 @@ for raw_test in "${tests[@]}"; do dest="$UNITY_PROJECT_PATH/Assets/Tests/PlayMode/UnityCliTests" ;; EditmodeTestsErrors) - dest="$UNITY_PROJECT_PATH/Assets/Tests/EditMode/UnityCliTests" + dest="$UNITY_PROJECT_PATH/Assets/Tests/EditMode/Editor/UnityCliTests" ;; *) echo "::error::Unknown test selection '$test_name'" diff --git a/.github/workflows/build-options.json b/.github/workflows/build-options.json index b1671291..0d9a0e85 100644 --- a/.github/workflows/build-options.json +++ b/.github/workflows/build-options.json @@ -15,7 +15,8 @@ "2022.3.*", "6000.0.x", "6000.1.*", - "6000.2" + "6000.2", + "6000" ], "include": [ { diff --git a/unity-tests/BuildErrors.cs b/unity-tests/BuildErrors.cs index ce75c3fe..dc344195 100644 --- a/unity-tests/BuildErrors.cs +++ b/unity-tests/BuildErrors.cs @@ -14,7 +14,7 @@ public class BuildErrors : IPreprocessBuildWithReport public void OnPreprocessBuild(BuildReport report) { - throw new System.Exception("Intentional build failure for test matrix coverage."); + throw new System.Exception("Intentional build failure."); } } } diff --git a/unity-tests/BuildWarnings.cs b/unity-tests/BuildWarnings.cs index f365a770..e4c2a3d7 100644 --- a/unity-tests/BuildWarnings.cs +++ b/unity-tests/BuildWarnings.cs @@ -14,7 +14,7 @@ public class BuildWarnings : IPreprocessBuildWithReport public void OnPreprocessBuild(BuildReport report) { - UnityEngine.Debug.LogWarning("Intentional build warning for test matrix coverage."); + UnityEngine.Debug.LogWarning("Intentional build warning."); } } } diff --git a/unity-tests/CompilerWarnings.cs b/unity-tests/CompilerWarnings.cs index c1fc0778..ae35bd22 100644 --- a/unity-tests/CompilerWarnings.cs +++ b/unity-tests/CompilerWarnings.cs @@ -12,7 +12,7 @@ private void Awake() ObsoleteApi(); // CS0618: call to obsolete member } - [System.Obsolete("Intentional warning for test matrix coverage", false)] + [System.Obsolete("Intentional warning", false)] private static void ObsoleteApi() { } diff --git a/unity-tests/EditmodeTestsErrors.cs b/unity-tests/EditmodeTestsErrors.cs index 4c4d89c9..70ea08a5 100644 --- a/unity-tests/EditmodeTestsErrors.cs +++ b/unity-tests/EditmodeTestsErrors.cs @@ -10,7 +10,7 @@ public class EditmodeTestsErrors [Test] public void FailsEditmodeSuite() { - Assert.Fail("Intentional editmode failure for test matrix coverage."); + Assert.Fail("Intentional editmode failure."); } } } From 556902ea3b2a6e1b13fe4d0a576ff52a1f784497 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sun, 11 Jan 2026 21:03:50 -0500 Subject: [PATCH 26/56] add editor assembly for editor tests --- .github/actions/scripts/run-utp-tests.sh | 21 ++++++++++++++++++- .../UnityCliTests.EditMode.Editor.asmdef | 14 +++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 unity-tests/UnityCliTests.EditMode.Editor.asmdef diff --git a/.github/actions/scripts/run-utp-tests.sh b/.github/actions/scripts/run-utp-tests.sh index a2e37e84..8dc46b4e 100755 --- a/.github/actions/scripts/run-utp-tests.sh +++ b/.github/actions/scripts/run-utp-tests.sh @@ -25,6 +25,7 @@ clean_tests() { 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 } @@ -70,6 +71,8 @@ for raw_test in "${tests[@]}"; do clean_tests clean_build_outputs + asmdef_src="" + case "$test_name" in CompilerWarnings|CompilerErrors) dest="$UNITY_PROJECT_PATH/Assets/UnityCliTests" @@ -81,7 +84,8 @@ for raw_test in "${tests[@]}"; do dest="$UNITY_PROJECT_PATH/Assets/Tests/PlayMode/UnityCliTests" ;; EditmodeTestsErrors) - dest="$UNITY_PROJECT_PATH/Assets/Tests/EditMode/Editor/UnityCliTests" + 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'" @@ -91,6 +95,14 @@ for raw_test in "${tests[@]}"; do 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)" @@ -101,6 +113,13 @@ for raw_test in "${tests[@]}"; do if [ "$test_name" = "EditmodeTestsErrors" ]; then unity-cli run --log-name "${test_name}-EditMode" -runTests -testPlatform editmode -testResults "$UNITY_PROJECT_PATH/Builds/Logs/${test_name}-results.xml" -quit || validate_rc=$? + + # Guard against zero-discovery runs that exit 0 by treating no test cases as a failure. + results_xml="$UNITY_PROJECT_PATH/Builds/Logs/${test_name}-results.xml" + if [ -f "$results_xml" ] && ! node -e "const fs=require('fs');const p=process.argv[1];try{const t=fs.readFileSync(p,'utf8');const m=t.match(/0){process.exit(0);} }catch(e){}process.exit(1);" "$results_xml"; then + echo "::error::No editmode tests were discovered for $test_name" + validate_rc=1 + fi build_rc=$validate_rc ran_custom_flow=1 fi 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 +} From f7bfda986d5724e47c9afb9ee518819f24af6cb5 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sun, 11 Jan 2026 21:12:53 -0500 Subject: [PATCH 27/56] remove cp warning for gnu clobber --- .github/actions/scripts/run-utp-tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/scripts/run-utp-tests.sh b/.github/actions/scripts/run-utp-tests.sh index 8dc46b4e..9f4ae028 100755 --- a/.github/actions/scripts/run-utp-tests.sh +++ b/.github/actions/scripts/run-utp-tests.sh @@ -195,7 +195,7 @@ for raw_test in "${tests[@]}"; do 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 -exec cp -n {} "$test_artifacts" \; || true + find "$GITHUB_WORKSPACE" -path "$test_artifacts" -prune -o -type f -name "*${test_name}*-utp-json.log" -print -exec cp --update=none {} "$test_artifacts" \; || true done From 9410cc8815ba682694395f8ab4f9e82f8f3f51a8 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sun, 11 Jan 2026 22:21:22 -0500 Subject: [PATCH 28/56] fix test --- unity-tests/EditmodeTestsErrors.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unity-tests/EditmodeTestsErrors.cs b/unity-tests/EditmodeTestsErrors.cs index 70ea08a5..a0304d8d 100644 --- a/unity-tests/EditmodeTestsErrors.cs +++ b/unity-tests/EditmodeTestsErrors.cs @@ -10,7 +10,7 @@ public class EditmodeTestsErrors [Test] public void FailsEditmodeSuite() { - Assert.Fail("Intentional editmode failure."); + Assert.Fail("Intentional editmode failure"); } } } From ede76eee5b64162b7dbced79fdc4bfe907688f55 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sun, 11 Jan 2026 23:49:26 -0500 Subject: [PATCH 29/56] fix macos tests --- .github/actions/scripts/run-utp-tests.sh | 50 +++++++++++++++--------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/.github/actions/scripts/run-utp-tests.sh b/.github/actions/scripts/run-utp-tests.sh index 9f4ae028..4ad247bd 100755 --- a/.github/actions/scripts/run-utp-tests.sh +++ b/.github/actions/scripts/run-utp-tests.sh @@ -36,21 +36,29 @@ clean_build_outputs() { # Expectations for each synthetic test # expected_status: 0 = should succeed, 1 = should fail -declare -A expected_status -expected_status[CompilerWarnings]=0 -expected_status[BuildWarnings]=0 -expected_status[CompilerErrors]=1 -expected_status[BuildErrors]=1 -expected_status[PlaymodeTestsErrors]=1 -expected_status[EditmodeTestsErrors]=1 - -declare -A expected_message -expected_message[CompilerErrors]="Intentional compiler error" -expected_message[BuildErrors]="Intentional build failure" -expected_message[PlaymodeTestsErrors]="Intentional playmode failure" -expected_message[EditmodeTestsErrors]="Intentional editmode failure" -expected_message[CompilerWarnings]="Intentional warning" -expected_message[BuildWarnings]="Intentional build warning" +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" @@ -129,8 +137,8 @@ for raw_test in "${tests[@]}"; do unity-cli run --log-name "${test_name}-Build" -buildTarget "$BUILD_TARGET" -quit -executeMethod Utilities.Editor.BuildPipeline.UnityPlayerBuildTools.StartCommandLineBuild -sceneList Assets/Scenes/SampleScene.unity "${build_args[@]}" || build_rc=$? fi - expected=${expected_status[$test_name]:-0} - exp_msg=${expected_message[$test_name]:-} + expected=$(expected_status_for "$test_name") + exp_msg=$(expected_message_for "$test_name") test_failed=0 message_found=0 @@ -195,7 +203,13 @@ for raw_test in "${tests[@]}"; do 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 -exec cp --update=none {} "$test_artifacts" \; || true + 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 From 0f3a2c5b61c2e0aaee9fea13fc6785228806332f Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sun, 11 Jan 2026 23:52:17 -0500 Subject: [PATCH 30/56] refactor tets --- .gitattributes | 1 + .github/actions/scripts/run-utp-tests.sh | 8 +++----- 2 files changed, 4 insertions(+), 5 deletions(-) create mode 100644 .gitattributes 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/scripts/run-utp-tests.sh b/.github/actions/scripts/run-utp-tests.sh index 4ad247bd..e179e646 100755 --- a/.github/actions/scripts/run-utp-tests.sh +++ b/.github/actions/scripts/run-utp-tests.sh @@ -120,12 +120,10 @@ for raw_test in "${tests[@]}"; do ran_custom_flow=0 if [ "$test_name" = "EditmodeTestsErrors" ]; then - unity-cli run --log-name "${test_name}-EditMode" -runTests -testPlatform editmode -testResults "$UNITY_PROJECT_PATH/Builds/Logs/${test_name}-results.xml" -quit || validate_rc=$? + 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=$? - # Guard against zero-discovery runs that exit 0 by treating no test cases as a failure. results_xml="$UNITY_PROJECT_PATH/Builds/Logs/${test_name}-results.xml" - if [ -f "$results_xml" ] && ! node -e "const fs=require('fs');const p=process.argv[1];try{const t=fs.readFileSync(p,'utf8');const m=t.match(/0){process.exit(0);} }catch(e){}process.exit(1);" "$results_xml"; then - echo "::error::No editmode tests were discovered for $test_name" + if ! grep -q "/dev/null; then validate_rc=1 fi build_rc=$validate_rc @@ -161,7 +159,7 @@ for raw_test in "${tests[@]}"; do if [ -z "$utp_file" ]; then continue fi - if node -e "const fs=require('fs');const p=process.argv[1];try{const data=JSON.parse(fs.readFileSync(p,'utf8'));if(Array.isArray(data)&&data.some(e=>['Error','Exception','Assert'].includes(e?.severity))){process.exit(0);} }catch{}process.exit(1);" "$utp_file"; then + if grep -qi '"severity"[[:space:]]*:[[:space:]]*"\(Error\|Exception\|Assert\)"' "$utp_file" 2>/dev/null; then utp_error_found=1 break fi From 6a335384985ff7870cf815507080e0abfbd95a44 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sun, 25 Jan 2026 10:54:43 -0500 Subject: [PATCH 31/56] update utp tests --- .github/actions/scripts/run-utp-tests.sh | 18 +- package-lock.json | 250 +++++++++++------------ package.json | 4 +- 3 files changed, 143 insertions(+), 129 deletions(-) diff --git a/.github/actions/scripts/run-utp-tests.sh b/.github/actions/scripts/run-utp-tests.sh index e179e646..360f97d0 100755 --- a/.github/actions/scripts/run-utp-tests.sh +++ b/.github/actions/scripts/run-utp-tests.sh @@ -11,7 +11,7 @@ if printf '%s' "$BUILD_ARGS" | grep -qE '[;&`|]'; then exit 1 fi -build_args=() +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" @@ -132,7 +132,21 @@ for raw_test in "${tests[@]}"; do 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=$? - unity-cli run --log-name "${test_name}-Build" -buildTarget "$BUILD_TARGET" -quit -executeMethod Utilities.Editor.BuildPipeline.UnityPlayerBuildTools.StartCommandLineBuild -sceneList Assets/Scenes/SampleScene.unity "${build_args[@]}" || build_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") diff --git a/package-lock.json b/package-lock.json index 8796015b..10f98985 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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.11", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz", - "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==", + "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.30001761", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001761.tgz", - "integrity": "sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==", + "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" }, @@ -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", diff --git a/package.json b/package.json index 581f58cc..29138c96 100644 --- a/package.json +++ b/package.json @@ -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", From f638dded2a96dd983f5f6130d8dc90301d692072 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sun, 25 Jan 2026 11:38:20 -0500 Subject: [PATCH 32/56] remove dup step --- .github/workflows/unity-build.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/workflows/unity-build.yml b/.github/workflows/unity-build.yml index 97d2698a..dd46ebcd 100644 --- a/.github/workflows/unity-build.yml +++ b/.github/workflows/unity-build.yml @@ -175,13 +175,6 @@ jobs: artifact_name="${MATRIX_OS}-${unity_version}-${MATRIX_BUILD_TARGET}-tests-batch-utp-logs" echo "name=$artifact_name" >> $GITHUB_OUTPUT shell: bash - - name: Upload UTP logs artifact - if: always() - uses: actions/upload-artifact@v6 - with: - name: ${{ steps.utp-artifact-name.outputs.name }} - path: '**/*-utp-json.log' - if-no-files-found: ignore - name: Return License if: always() run: unity-cli return-license --license personal From f8d637c7234c51ce8da6429f0e65862fbe25b15b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 25 Jan 2026 11:41:12 -0500 Subject: [PATCH 33/56] Bump tar from 7.5.2 to 7.5.3 in the npm_and_yarn group across 1 directory (#64) Bumps the npm_and_yarn group with 1 update in the / directory: [tar](https://github.com/isaacs/node-tar). Updates `tar` from 7.5.2 to 7.5.3
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=tar&package-manager=npm_and_yarn&previous-version=7.5.2&new-version=7.5.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/RageAgainstThePixel/unity-cli/network/alerts).
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Stephen Hodgson From 18a04cf6662d9f639e026dcf9bc11531698e54a1 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sun, 25 Jan 2026 11:45:32 -0500 Subject: [PATCH 34/56] remove some build versions to speed up testing --- .github/workflows/build-options.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/build-options.json b/.github/workflows/build-options.json index 0d9a0e85..5eec9059 100644 --- a/.github/workflows/build-options.json +++ b/.github/workflows/build-options.json @@ -5,10 +5,6 @@ "macos-latest" ], "unity-version": [ - "4.7.2", - "5.6.7f1 (e80cc3114ac1)", - "2017.4.40f1", - "2018", "2019.x", "2020.*", "2021.3.x", From ce4aa2e6fe8f8c8427acf1c1205380db4b80b2d7 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sun, 25 Jan 2026 14:27:11 -0500 Subject: [PATCH 35/56] fix artifact names --- .github/workflows/unity-build.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/unity-build.yml b/.github/workflows/unity-build.yml index dd46ebcd..21c44ed7 100644 --- a/.github/workflows/unity-build.yml +++ b/.github/workflows/unity-build.yml @@ -100,6 +100,13 @@ jobs: else echo "Skipping build: Unity version $version does not support the build pipeline package (requires 2019.4+)" fi + - name: Compute safe artifact name + id: artifact-name + run: | + 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 @@ -107,7 +114,7 @@ jobs: unity-project-path: ${{ env.UNITY_PROJECT_PATH }} build-target: ${{ matrix.build-target }} build-args: ${{ matrix.build-args }} - artifact-name: ${{ matrix.os }}-${{ matrix.unity-version }}-${{ matrix.build-target }}-tests-batch-utp-logs + artifact-name: ${{ steps.artifact-name.outputs.name }} - name: Update Android Target Sdk Version if: ${{ matrix.build-target == 'Android' }} run: | From c56991b00fc5d63375f8c40dae9fab82bb9edb57 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sun, 25 Jan 2026 16:48:59 -0500 Subject: [PATCH 36/56] fix log uploads --- .github/actions/run-unity-test-batch/action.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/actions/run-unity-test-batch/action.yml b/.github/actions/run-unity-test-batch/action.yml index 746653b7..237c73ff 100644 --- a/.github/actions/run-unity-test-batch/action.yml +++ b/.github/actions/run-unity-test-batch/action.yml @@ -43,10 +43,12 @@ runs: 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 }} From 163b139404c932bb4bb5d517143e44f507b07620 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sun, 22 Feb 2026 01:55:26 -0500 Subject: [PATCH 37/56] bump version to 1.9.0 --- package-lock.json | 676 ++++++++++++++++++++++++++-------------------- package.json | 12 +- 2 files changed, 385 insertions(+), 303 deletions(-) diff --git a/package-lock.json b/package-lock.json index 10f98985..0e27a083 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,21 +1,21 @@ { "name": "@rage-against-the-pixel/unity-cli", - "version": "1.8.2", + "version": "1.9.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@rage-against-the-pixel/unity-cli", - "version": "1.8.2", + "version": "1.9.0", "license": "MIT", "dependencies": { "@electron/asar": "^4.0.1", "@rage-against-the-pixel/unity-releases-api": "^1.0.4", - "commander": "^14.0.2", + "commander": "^14.0.3", "glob": "^11.1.0", - "semver": "^7.7.3", + "semver": "^7.7.4", "source-map-support": "^0.5.21", - "tar": "^7.5.6", + "tar": "^7.5.9", "update-notifier": "^7.3.1", "yaml": "^2.8.2" }, @@ -24,7 +24,7 @@ }, "devDependencies": { "@types/jest": "^30.0.0", - "@types/node": "^24.10.9", + "@types/node": "^24.10.13", "@types/semver": "^7.7.1", "@types/update-notifier": "^6.0.8", "jest": "^30.2.0", @@ -34,9 +34,9 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", - "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, "license": "MIT", "dependencies": { @@ -49,9 +49,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", - "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", "dev": true, "license": "MIT", "engines": { @@ -59,21 +59,21 @@ } }, "node_modules/@babel/core": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", - "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/generator": "^7.28.6", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", "@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/parser": "^7.29.0", "@babel/template": "^7.28.6", - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", "@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.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", - "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -240,13 +240,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", - "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.6" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -510,18 +510,18 @@ } }, "node_modules/@babel/traverse": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", - "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/generator": "^7.28.6", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.6", + "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6", + "@babel/types": "^7.29.0", "debug": "^4.3.1" }, "engines": { @@ -529,9 +529,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", - "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, "license": "MIT", "dependencies": { @@ -633,42 +633,13 @@ "tslib": "^2.4.0" } }, - "node_modules/@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", - "license": "MIT", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", - "license": "MIT", - "dependencies": { - "@isaacs/balanced-match": "^4.0.1" - }, - "engines": { - "node": "20 || >=22" - } - }, "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", + "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@isaacs/fs-minipass": { @@ -1006,6 +977,37 @@ } } }, + "node_modules/@jest/reporters/node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jest/reporters/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, "node_modules/@jest/reporters/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -1043,6 +1045,7 @@ "version": "10.5.0", "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { @@ -1084,13 +1087,13 @@ "license": "ISC" }, "node_modules/@jest/reporters/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", + "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -1116,6 +1119,22 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@jest/reporters/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/@jest/schemas": { "version": "30.0.5", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", @@ -1631,9 +1650,9 @@ } }, "node_modules/@types/node": { - "version": "24.10.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.9.tgz", - "integrity": "sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==", + "version": "24.10.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.13.tgz", + "integrity": "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg==", "dev": true, "license": "MIT", "dependencies": { @@ -1959,9 +1978,9 @@ ] }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", "bin": { @@ -1972,9 +1991,9 @@ } }, "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", "dev": true, "license": "MIT", "dependencies": { @@ -1993,15 +2012,6 @@ "string-width": "^4.1.0" } }, - "node_modules/ansi-align/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/ansi-align/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -2022,18 +2032,6 @@ "node": ">=8" } }, - "node_modules/ansi-align/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -2064,15 +2062,12 @@ } }, "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "node": ">=8" } }, "node_modules/ansi-styles": { @@ -2120,9 +2115,9 @@ } }, "node_modules/atomically": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/atomically/-/atomically-2.1.0.tgz", - "integrity": "sha512-+gDffFXRW6sl/HCwbta7zK4uNqbPjv4YJEAdz7Vu+FLQHe77eZ4bvbJGi4hE0QPeJlMYMA3piXEr1UL3dAwx7Q==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/atomically/-/atomically-2.1.1.tgz", + "integrity": "sha512-P4w9o2dqARji6P7MHprklbfiArZAWvo07yW7qs3pdljb3BWr12FIB7W+p0zJiuiVsUpRO0iZn1kFFcpPegg0tQ==", "license": "MIT", "dependencies": { "stubborn-fs": "^2.0.0", @@ -2262,20 +2257,25 @@ } }, "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.3.tgz", + "integrity": "sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==", + "license": "MIT", + "engines": { + "node": "20 || >=22" + } }, "node_modules/baseline-browser-mapping": { - "version": "2.9.18", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.18.tgz", - "integrity": "sha512-e23vBV1ZLfjb9apvfPk4rHVu2ry6RIr2Wfs+O324okSidrX7pTAnEJPCh/O5BtRlr7QtZI7ktOP3vsqr7Z5XoA==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", + "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", "dev": true, "license": "Apache-2.0", "bin": { - "baseline-browser-mapping": "dist/cli.js" + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" } }, "node_modules/boxen": { @@ -2302,13 +2302,15 @@ } }, "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz", + "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==", "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" } }, "node_modules/braces": { @@ -2411,9 +2413,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001766", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz", - "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==", + "version": "1.0.30001772", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001772.tgz", + "integrity": "sha512-mIwLZICj+ntVTw4BT2zfp+yu/AqV6GMKfJVJMx3MwPxs+uk/uj2GLl2dH8LQbjiLDX66amCga5nKFyDgRR43kg==", "dev": true, "funding": [ { @@ -2463,9 +2465,9 @@ } }, "node_modules/ci-info": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", - "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", "dev": true, "funding": [ { @@ -2512,16 +2514,6 @@ "node": ">=12" } }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/cliui/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -2560,19 +2552,6 @@ "node": ">=8" } }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/cliui/node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -2613,6 +2592,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -2625,12 +2605,13 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, "license": "MIT" }, "node_modules/commander": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", - "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", "license": "MIT", "engines": { "node": ">=20" @@ -2808,12 +2789,13 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.278", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.278.tgz", - "integrity": "sha512-dQ0tM1svDRQOwxnXxm+twlGTjr9Upvt8UFWAgmLsxEzFQxhbti4VwxmMjsDxVC51Zo84swW7FVCXEV+VAkhuPw==", + "version": "1.5.302", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz", + "integrity": "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==", "dev": true, "license": "ISC" }, @@ -2834,6 +2816,7 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, "license": "MIT" }, "node_modules/error-ex": { @@ -3054,9 +3037,9 @@ } }, "node_modules/get-east-asian-width": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", - "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", + "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", "license": "MIT", "engines": { "node": ">=18" @@ -3092,6 +3075,7 @@ "version": "11.1.0", "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "license": "BlueOak-1.0.0", "dependencies": { "foreground-child": "^3.3.1", @@ -3421,12 +3405,12 @@ } }, "node_modules/jackspeak": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", - "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", + "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", "license": "BlueOak-1.0.0", "dependencies": { - "@isaacs/cliui": "^8.0.2" + "@isaacs/cliui": "^9.0.0" }, "engines": { "node": "20 || >=22" @@ -3660,6 +3644,37 @@ } } }, + "node_modules/jest-config/node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/jest-config/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, "node_modules/jest-config/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -3697,6 +3712,7 @@ "version": "10.5.0", "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { @@ -3738,13 +3754,13 @@ "license": "ISC" }, "node_modules/jest-config/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", + "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -3770,6 +3786,22 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/jest-config/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/jest-diff": { "version": "30.2.0", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz", @@ -4265,6 +4297,37 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-runtime/node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/jest-runtime/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, "node_modules/jest-runtime/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -4302,6 +4365,7 @@ "version": "10.5.0", "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { @@ -4343,13 +4407,13 @@ "license": "ISC" }, "node_modules/jest-runtime/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", + "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -4375,6 +4439,22 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/jest-runtime/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/jest-snapshot": { "version": "30.2.0", "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.2.0.tgz", @@ -4719,9 +4799,9 @@ } }, "node_modules/ky": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/ky/-/ky-1.14.2.tgz", - "integrity": "sha512-q3RBbsO5A5zrPhB6CaCS8ZUv+NWCXv6JJT4Em0i264G9W0fdPB8YRfnnEi7Dm7X7omAkBIPojzYJ2D1oHTHqug==", + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/ky/-/ky-1.14.3.tgz", + "integrity": "sha512-9zy9lkjac+TR1c2tG+mkNSVlyOpInnWdSMiue4F+kq8TwJSgv6o8jhLRg8Ho6SnZ9wOYUq/yozts9qQCfk7bIw==", "license": "MIT", "engines": { "node": ">=18" @@ -4857,15 +4937,15 @@ } }, "node_modules/minimatch": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", - "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", + "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", "license": "BlueOak-1.0.0", "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" + "brace-expansion": "^5.0.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -4881,10 +4961,10 @@ } }, "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } @@ -5129,25 +5209,25 @@ } }, "node_modules/path-scurry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz", - "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.2.4", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", - "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" @@ -5347,9 +5427,9 @@ } }, "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -5454,33 +5534,11 @@ "node": ">=10" } }, - "node_modules/string-length/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-length/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", @@ -5499,6 +5557,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -5509,37 +5568,31 @@ "node": ">=8" } }, - "node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/string-width-cjs/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, "license": "MIT" }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/strip-ansi": { + "node_modules/string-width/node_modules/strip-ansi": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" @@ -5551,8 +5604,7 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", + "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", @@ -5564,11 +5616,16 @@ "node": ">=8" } }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, "engines": { "node": ">=8" } @@ -5651,9 +5708,9 @@ } }, "node_modules/tar": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.6.tgz", - "integrity": "sha512-xqUeu2JAIJpXyvskvU3uvQW8PAmHrtXp2KDuMJwQqW8Sqq0CaZBAQ+dKS3RBXVhU4wC5NjAdKrmh84241gO9cA==", + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.9.tgz", + "integrity": "sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg==", "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/fs-minipass": "^4.0.0", @@ -5690,6 +5747,13 @@ "node": ">=8" } }, + "node_modules/test-exclude/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/test-exclude/node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -5705,7 +5769,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { @@ -5724,9 +5788,9 @@ } }, "node_modules/test-exclude/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", + "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", "dev": true, "license": "ISC", "dependencies": { @@ -6022,6 +6086,18 @@ "url": "https://github.com/yeoman/update-notifier?sponsor=1" } }, + "node_modules/update-notifier/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, "node_modules/update-notifier/node_modules/ansi-styles": { "version": "6.2.3", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", @@ -6091,6 +6167,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/update-notifier/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/update-notifier/node_modules/type-fest": { "version": "4.41.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", @@ -6215,6 +6306,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", @@ -6233,6 +6325,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -6246,19 +6339,11 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -6274,12 +6359,14 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, "license": "MIT" }, "node_modules/wrap-ansi-cjs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -6290,22 +6377,24 @@ "node": ">=8" } }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/wrap-ansi/node_modules/ansi-styles": { "version": "6.2.3", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -6314,6 +6403,22 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -6408,16 +6513,6 @@ "node": ">=12" } }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/yargs/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -6440,19 +6535,6 @@ "node": ">=8" } }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/package.json b/package.json index 29138c96..5a0ec8c9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@rage-against-the-pixel/unity-cli", - "version": "1.8.2", + "version": "1.9.0", "description": "A command line utility for the Unity Game Engine.", "author": "RageAgainstThePixel", "license": "MIT", @@ -50,17 +50,17 @@ "dependencies": { "@electron/asar": "^4.0.1", "@rage-against-the-pixel/unity-releases-api": "^1.0.4", - "commander": "^14.0.2", + "commander": "^14.0.3", "glob": "^11.1.0", - "semver": "^7.7.3", + "semver": "^7.7.4", "source-map-support": "^0.5.21", - "tar": "^7.5.6", + "tar": "^7.5.9", "update-notifier": "^7.3.1", "yaml": "^2.8.2" }, "devDependencies": { "@types/jest": "^30.0.0", - "@types/node": "^24.10.9", + "@types/node": "^24.10.13", "@types/semver": "^7.7.1", "@types/update-notifier": "^6.0.8", "jest": "^30.2.0", @@ -68,4 +68,4 @@ "ts-node": "^10.9.2", "typescript": "^5.9.3" } -} +} \ No newline at end of file From f54411ff573c67d53cf5a22abbe3e0e2649100fd Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sun, 22 Feb 2026 01:56:36 -0500 Subject: [PATCH 38/56] fix asmdef formatting --- .../UnityCliTests.EditMode.Editor.asmdef | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/unity-tests/UnityCliTests.EditMode.Editor.asmdef b/unity-tests/UnityCliTests.EditMode.Editor.asmdef index ce9be1f3..fc0a73b9 100644 --- a/unity-tests/UnityCliTests.EditMode.Editor.asmdef +++ b/unity-tests/UnityCliTests.EditMode.Editor.asmdef @@ -1,14 +1,18 @@ { - "name": "UnityCli.EditMode.EditorTests", - "references": [], - "optionalUnityReferences": ["TestAssemblies"], - "includePlatforms": ["Editor"], - "excludePlatforms": [], - "allowUnsafeCode": false, - "overrideReferences": false, - "precompiledReferences": [], - "autoReferenced": true, - "defineConstraints": [], - "versionDefines": [], - "noEngineReferences": false -} + "name": "UnityCli.EditMode.EditorTests", + "references": [], + "optionalUnityReferences": [ + "TestAssemblies" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file From e3104475269e8c320bf091eefd048b808e1b740e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 22 Feb 2026 01:58:36 -0500 Subject: [PATCH 39/56] Bump tar from 7.5.2 to 7.5.9 in the npm_and_yarn group across 1 directory (#69) Bumps the npm_and_yarn group with 1 update in the / directory: [tar](https://github.com/isaacs/node-tar). Updates `tar` from 7.5.2 to 7.5.8
Commits
  • 6b8eba0 7.5.8
  • 2cb1120 fix(unpack): improve UnpackSync symlink error "into" path accuracy
  • d18e4e1 fix: do not write linkpaths through symlinks
  • 4a37eb9 7.5.7
  • f4a7aa9 fix: properly sanitize hard links containing ..
  • 394ece6 7.5.6
  • 7d4cc17 fix race puting a Link ahead of its target File
  • 26ab904 7.5.5
  • e9a1ddb fix: do not prevent valid linkpaths within archive
  • 911c886 7.5.4
  • Additional commits viewable in compare view
Maintainer changes

This version was pushed to npm by isaacs, a new releaser for tar since your current version.

Install script changes

This version adds prepare script that runs during installation. Review the package contents before updating.


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=tar&package-manager=npm_and_yarn&previous-version=7.5.2&new-version=7.5.8)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/RageAgainstThePixel/unity-cli/network/alerts).
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Stephen Hodgson From 9570af37935b82cb39623eb6b0b67a609ae2b686 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sun, 22 Feb 2026 02:22:13 -0500 Subject: [PATCH 40/56] silly unity, they didn't update hub prod endpoint to latest hub version --- src/unity-hub.ts | 7 ++++++- src/utilities.ts | 11 ++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/unity-hub.ts b/src/unity-hub.ts index cae87609..4474d1ea 100644 --- a/src/unity-hub.ts +++ b/src/unity-hub.ts @@ -343,7 +343,12 @@ export class UnityHub { `Start-Process -FilePath '${uninstaller}' -ArgumentList '/S' -Verb RunAs -Wait` ], { silent: true, showCommand: true }); await DeleteDirectory(this.rootDirectory); - await this.installHub(version); + + if (fs.existsSync(this.rootDirectory)) { + this.logger.warn(`Unity Hub root directory still exists after uninstall: ${this.rootDirectory}`); + } + + await this.installHub(versionToInstall.version); } else if (process.platform === 'linux') { await Exec('sudo', ['sh', '-c', `#!/bin/bash set -e diff --git a/src/utilities.ts b/src/utilities.ts index 7b910b77..87d65a7a 100644 --- a/src/utilities.ts +++ b/src/utilities.ts @@ -232,13 +232,22 @@ export async function DownloadFile(url: string, downloadPath: string): Promise { if (targetPath && targetPath.length > 0 && fs.existsSync(targetPath)) { - logger.debug(`Attempting to delete directory: ${targetPath}...`); + logger.info(`Attempting to delete directory: ${targetPath}...`); try { await fs.promises.rm(targetPath, { recursive: true, force: true, maxRetries: 2, retryDelay: 100 }); } catch (error) { logger.warn(`Failed to delete directory: ${targetPath}\n${error}`); } } + if (targetPath && targetPath.length > 0 && fs.existsSync(targetPath)) { + logger.warn(`Directory still exists after deletion attempt: ${targetPath}`); + try { + const files = await fs.promises.readdir(targetPath); + logger.warn(`Contents of ${targetPath}: ${files.join(', ')}`); + } catch (error) { + logger.warn(`Failed to read contents of directory: ${targetPath}\n${error}`); + } + } } /** From c2c3acb6d763ba50428d6cf7fb0cf10bf0452ffc Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sun, 22 Feb 2026 02:32:39 -0500 Subject: [PATCH 41/56] Revert "silly unity, they didn't update hub prod endpoint to latest hub version" This reverts commit 9570af37935b82cb39623eb6b0b67a609ae2b686. --- src/unity-hub.ts | 7 +------ src/utilities.ts | 11 +---------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/src/unity-hub.ts b/src/unity-hub.ts index 4474d1ea..cae87609 100644 --- a/src/unity-hub.ts +++ b/src/unity-hub.ts @@ -343,12 +343,7 @@ export class UnityHub { `Start-Process -FilePath '${uninstaller}' -ArgumentList '/S' -Verb RunAs -Wait` ], { silent: true, showCommand: true }); await DeleteDirectory(this.rootDirectory); - - if (fs.existsSync(this.rootDirectory)) { - this.logger.warn(`Unity Hub root directory still exists after uninstall: ${this.rootDirectory}`); - } - - await this.installHub(versionToInstall.version); + await this.installHub(version); } else if (process.platform === 'linux') { await Exec('sudo', ['sh', '-c', `#!/bin/bash set -e diff --git a/src/utilities.ts b/src/utilities.ts index 87d65a7a..7b910b77 100644 --- a/src/utilities.ts +++ b/src/utilities.ts @@ -232,22 +232,13 @@ export async function DownloadFile(url: string, downloadPath: string): Promise { if (targetPath && targetPath.length > 0 && fs.existsSync(targetPath)) { - logger.info(`Attempting to delete directory: ${targetPath}...`); + logger.debug(`Attempting to delete directory: ${targetPath}...`); try { await fs.promises.rm(targetPath, { recursive: true, force: true, maxRetries: 2, retryDelay: 100 }); } catch (error) { logger.warn(`Failed to delete directory: ${targetPath}\n${error}`); } } - if (targetPath && targetPath.length > 0 && fs.existsSync(targetPath)) { - logger.warn(`Directory still exists after deletion attempt: ${targetPath}`); - try { - const files = await fs.promises.readdir(targetPath); - logger.warn(`Contents of ${targetPath}: ${files.join(', ')}`); - } catch (error) { - logger.warn(`Failed to read contents of directory: ${targetPath}\n${error}`); - } - } } /** From c31cfafea4432a57bcc41f413e9fd8d1727db35f Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sun, 22 Feb 2026 04:10:00 -0500 Subject: [PATCH 42/56] Change output summary format --- .github/actions/scripts/run-utp-tests.sh | 3 +- src/logging.ts | 38 +++++++++-------------- src/unity-hub.ts | 1 + unity-tests/PlaymodeTestsErrors.cs | 2 ++ unity-tests/UnityCliTests.PlayMode.asmdef | 23 ++++++++++++++ 5 files changed, 43 insertions(+), 24 deletions(-) create mode 100644 unity-tests/UnityCliTests.PlayMode.asmdef diff --git a/.github/actions/scripts/run-utp-tests.sh b/.github/actions/scripts/run-utp-tests.sh index 360f97d0..b4b4499f 100755 --- a/.github/actions/scripts/run-utp-tests.sh +++ b/.github/actions/scripts/run-utp-tests.sh @@ -90,6 +90,7 @@ for raw_test in "${tests[@]}"; do ;; PlaymodeTestsErrors) dest="$UNITY_PROJECT_PATH/Assets/Tests/PlayMode/UnityCliTests" + asmdef_src="$GITHUB_WORKSPACE/unity-tests/UnityCliTests.PlayMode.asmdef" ;; EditmodeTestsErrors) dest="$UNITY_PROJECT_PATH/Assets/Tests/EditMode/UnityCliTests" @@ -105,7 +106,7 @@ for raw_test in "${tests[@]}"; do 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" + echo "::error::Assembly definition for tests not found at $asmdef_src" failures=$((failures+1)) continue fi diff --git a/src/logging.ts b/src/logging.ts index 739760a5..4c4ae5da 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -266,46 +266,38 @@ export class Logger { const githubSummary = process.env.GITHUB_STEP_SUMMARY; if (githubSummary) { - // for now lets just log the number of items we get per type - const typeCounts: Record = {}; + // Only show LogEntry, Compiler, and Action types in the summary table + const showTypes = new Set(['LogEntry', 'Compiler', 'Action']); + let foldout = `## ${name} Summary\n\n
\nShow Action, Compiler, and LogEntry details\n\n`; + foldout += `- List of entries as JSON:\n`; + for (const entry of telemetry) { const type = entry.type || 'unknown'; - - if (!typeCounts[type]) { - typeCounts[type] = 0; + if (!showTypes.has(type)) { + continue; } - - typeCounts[type]++; + foldout += ` - \`${JSON.stringify(entry)}\`\n`; } - let table = `## ${name} Summary\n\n| Type | Count |\n| --- | ---: |\n`; - for (const [type, count] of Object.entries(typeCounts)) { - table += `| ${type} | ${count} |\n`; - } + foldout += `\n
\n`; - // guard against very large summaries over 1MB. Trim at a row boundary to avoid mangled tables. + // Truncate foldout if over 1MB const byteLimit = 1024 * 1024; - if (Buffer.byteLength(table, 'utf8') > byteLimit) { - const footer = `\n| ... | ... |\n\n***Summary truncated due to size limits.***\n`; + if (Buffer.byteLength(foldout, 'utf8') > byteLimit) { + const footer = `\n- ...\n\n***Summary truncated due to size limits.***\n\n`; const footerSize = Buffer.byteLength(footer, 'utf8'); - - const lines = table.split('\n'); + const lines = foldout.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; + foldout = rebuilt + footer; } - - fs.appendFileSync(githubSummary, table, { encoding: 'utf8' }); + fs.appendFileSync(githubSummary, foldout, { encoding: 'utf8' }); } break; } diff --git a/src/unity-hub.ts b/src/unity-hub.ts index cae87609..3c132865 100644 --- a/src/unity-hub.ts +++ b/src/unity-hub.ts @@ -351,6 +351,7 @@ wget -qO - https://hub.unity3d.com/linux/keys/public | gpg --dearmor | sudo tee sudo sh -c 'echo "deb [signed-by=/usr/share/keyrings/Unity_Technologies_ApS.gpg] https://hub.unity3d.com/linux/repos/deb stable main" > /etc/apt/sources.list.d/unityhub.list' sudo apt-get update --allow-releaseinfo-change sudo apt-get install -y --no-install-recommends --only-upgrade unityhub${version ? '=' + version : ''}`]); + this.logger.info(`Unity Hub updated successfully.`); } else { throw new Error(`Unsupported platform: ${process.platform}`); } diff --git a/unity-tests/PlaymodeTestsErrors.cs b/unity-tests/PlaymodeTestsErrors.cs index 729b1664..4c08ce9e 100644 --- a/unity-tests/PlaymodeTestsErrors.cs +++ b/unity-tests/PlaymodeTestsErrors.cs @@ -1,4 +1,6 @@ using System.Collections; +using UnityEngine; +using UnityEditor; using NUnit.Framework; using UnityEngine.TestTools; diff --git a/unity-tests/UnityCliTests.PlayMode.asmdef b/unity-tests/UnityCliTests.PlayMode.asmdef new file mode 100644 index 00000000..ab7fb34e --- /dev/null +++ b/unity-tests/UnityCliTests.PlayMode.asmdef @@ -0,0 +1,23 @@ +{ + "name": "UnityCli.PlayMode.Tests", + "references": [], + "optionalUnityReferences": [ + "TestAssemblies" + ], + "includePlatforms": [ + "Editor", + "WindowsStandalone64", + "LinuxStandaloneUniversal", + "macOSStandalone", + "Android", + "iOS" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file From b220e44c2cf811ca65d97efd733743dbff9c55ca Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sun, 22 Feb 2026 13:03:55 -0500 Subject: [PATCH 43/56] update summary output format --- .gitignore | 1 + src/logging.ts | 225 +++++++++++++++++++++++++++++++++++++------ src/unity-logging.ts | 43 +++++++++ 3 files changed, 238 insertions(+), 31 deletions(-) diff --git a/.gitignore b/.gitignore index b34eee66..cdace119 100644 --- a/.gitignore +++ b/.gitignore @@ -139,3 +139,4 @@ vite.config.js.timestamp-* vite.config.ts.timestamp-* .artifacts/ +_temp diff --git a/src/logging.ts b/src/logging.ts index 4c4ae5da..8fae3652 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -259,48 +259,211 @@ export class Logger { } } + private static readonly SUMMARY_BYTE_LIMIT = 1024 * 1024; + + private static formatDurationMs(ms: number | undefined): string { + if (ms === undefined || !Number.isFinite(ms)) { return '—'; } + if (ms < 1000) { return `${Math.round(ms)}ms`; } + return `${(ms / 1000).toFixed(1)}s`; + } + + private static truncateStr(s: string, max: number): string { + return s.length <= max ? s : s.slice(0, max) + '…'; + } + + private static truncateSummaryToByteLimit(summary: string, byteLimit: number): string { + const footer = `\n***Summary truncated due to size limits.***\n`; + const footerSize = Buffer.byteLength(footer, 'utf8'); + const lines = summary.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`; + } + return rebuilt + footer; + } + 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) { - // Only show LogEntry, Compiler, and Action types in the summary table - const showTypes = new Set(['LogEntry', 'Compiler', 'Action']); - let foldout = `## ${name} Summary\n\n
\nShow Action, Compiler, and LogEntry details\n\n`; - foldout += `- List of entries as JSON:\n`; - - for (const entry of telemetry) { - const type = entry.type || 'unknown'; - if (!showTypes.has(type)) { - continue; - } - foldout += ` - \`${JSON.stringify(entry)}\`\n`; - } + const excludedTypes = new Set(['MemoryLeaks', 'MemoryLeak']); + const filtered = telemetry.filter(entry => !excludedTypes.has(entry.type || '')); + if (filtered.length === 0) { return; } - foldout += `\n
\n`; - - // Truncate foldout if over 1MB - const byteLimit = 1024 * 1024; - if (Buffer.byteLength(foldout, 'utf8') > byteLimit) { - const footer = `\n- ...\n\n***Summary truncated due to size limits.***\n\n`; - const footerSize = Buffer.byteLength(footer, 'utf8'); - const lines = foldout.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`; - } - foldout = rebuilt + footer; + const severityError = (s: string | undefined): boolean => + s === 'Error' || s === 'Exception' || s === 'Assert'; + const errorEntries = filtered.filter( + e => (e.type === 'LogEntry' || e.type === 'Compiler') && severityError(e.severity) + ); + const completedActions = filtered.filter( + e => e.type === 'Action' && e.phase === 'End' + ); + const logEntries = filtered.filter(e => e.type === 'LogEntry'); + const compilerEntries = filtered.filter(e => e.type === 'Compiler'); + + const limit = Logger.SUMMARY_BYTE_LIMIT; + const builders: (() => string)[] = [ + () => this.buildSummaryCollapsible(name, errorEntries, completedActions, logEntries, compilerEntries), + () => this.buildSummaryCountsOnly(name, errorEntries, logEntries.length, compilerEntries.length, completedActions.length), + () => this.buildSummaryErrorsAndTimeline(name, errorEntries, completedActions), + ]; + let summary = ''; + for (const build of builders) { + summary = build(); + if (Buffer.byteLength(summary, 'utf8') <= limit) { break; } + } + if (Buffer.byteLength(summary, 'utf8') > limit) { + summary = Logger.truncateSummaryToByteLimit(summary, limit); } - fs.appendFileSync(githubSummary, foldout, { encoding: 'utf8' }); + fs.appendFileSync(githubSummary, summary, { encoding: 'utf8' }); } break; } } } + + /** + * Builds summary with collapsible sections per type + * (Errors, Build timeline, LogEntry, Compiler), one line per entry. + */ + private buildSummaryCollapsible( + name: string, + errorEntries: UTP[], + completedActions: UTP[], + logEntries: UTP[], + compilerEntries: UTP[] + ): string { + const MAX_ERROR = 20; + const MAX_ACTION = 50; + const MAX_LOG = 50; + const MAX_COMPILER = 50; + const TRUNCATE_MSG = 120; + + let out = `## ${name} Summary\n\n`; + + if (errorEntries.length > 0) { + out += `
Errors (${errorEntries.length})\n\n`; + const shown = errorEntries.slice(0, MAX_ERROR); + for (const e of shown) { + out += `- ${Logger.truncateStr((e.message || '').trim(), TRUNCATE_MSG)}\n`; + if (e.file && e.line !== undefined && e.line > 0) { + const file = (e.file || '').replace(/\\/g, '/'); + out += ` \`${file}:${e.line}\`\n`; + } + } + if (errorEntries.length > MAX_ERROR) { + out += `- ... and ${errorEntries.length - MAX_ERROR} more (see annotations).\n`; + } + out += `\n
\n\n`; + } + + if (completedActions.length > 0) { + out += `
Build timeline (${completedActions.length} actions)\n\n`; + const shown = completedActions.slice(0, MAX_ACTION); + for (const a of shown) { + const durationMs = a.duration ?? (a.durationMicroseconds != null ? a.durationMicroseconds / 1000 : undefined); + const errCount = Array.isArray(a.errors) ? a.errors.length : 0; + const status = errCount > 0 ? '❌' : '✅'; + const desc = Logger.truncateStr((a.description || a.name || '—').trim(), 60); + out += `${status} ${desc} ${Logger.formatDurationMs(durationMs)} (${errCount} errors)\n`; + } + out += `\n
\n\n`; + } + + if (logEntries.length > 0) { + out += `
LogEntry (${logEntries.length})\n\n`; + const shown = logEntries.slice(0, MAX_LOG); + for (const e of shown) { + out += `- ${e.severity ?? 'Info'}: ${Logger.truncateStr((e.message || '').trim(), TRUNCATE_MSG)}\n`; + } + if (logEntries.length > MAX_LOG) { + out += `- ... and ${logEntries.length - MAX_LOG} more.\n`; + } + out += `\n
\n\n`; + } + + if (compilerEntries.length > 0) { + out += `
Compiler (${compilerEntries.length})\n\n`; + const shown = compilerEntries.slice(0, MAX_COMPILER); + for (const e of shown) { + out += `- ${e.severity ?? 'Info'}: ${Logger.truncateStr((e.message || '').trim(), TRUNCATE_MSG)}\n`; + } + if (compilerEntries.length > MAX_COMPILER) { + out += `- ... and ${compilerEntries.length - MAX_COMPILER} more.\n`; + } + out += `\n
\n\n`; + } + + return out; + } + + /** + * Builds summary with type counts in a markdown table. + * When there are errors, adds a line pointing to annotations. + */ + private buildSummaryCountsOnly( + name: string, + errorEntries: UTP[], + logEntryCount: number, + compilerCount: number, + actionCount: number + ): string { + const errorCount = errorEntries.length; + + let out = `## ${name} Summary\n\n`; + out += `| Type | Count |\n`; + out += `|------|-------|\n`; + out += `| Errors | ${errorCount} |\n`; + out += `| LogEntry | ${logEntryCount} |\n`; + out += `| Compiler | ${compilerCount} |\n`; + out += `| Actions | ${actionCount} |\n\n`; + if (errorCount > 0) { + out += `See annotations for details.\n`; + } + return out; + } + + /** + * Builds minimal summary: errors list with optional file:line, + * then one line per completed action (no LogEntry/Compiler). + */ + private buildSummaryErrorsAndTimeline( + name: string, + errorEntries: UTP[], + completedActions: UTP[] + ): string { + const MAX_ERROR = 20; + const TRUNCATE_MSG = 120; + + let out = `## ${name} Summary\n\n`; + + if (errorEntries.length > 0) { + const shown = errorEntries.slice(0, MAX_ERROR); + for (const e of shown) { + out += `- ${Logger.truncateStr((e.message || '').trim(), TRUNCATE_MSG)}\n`; + if (e.file && e.line !== undefined && e.line > 0) { + const file = (e.file || '').replace(/\\/g, '/'); + out += ` \`${file}:${e.line}\`\n`; + } + } + if (errorEntries.length > MAX_ERROR) { + out += `- ... and ${errorEntries.length - MAX_ERROR} more (see annotations).\n`; + } + out += `\n`; + } + + for (const a of completedActions) { + const durationMs = a.duration ?? (a.durationMicroseconds != null ? a.durationMicroseconds / 1000 : undefined); + const errCount = Array.isArray(a.errors) ? a.errors.length : 0; + const status = errCount > 0 ? '❌' : '✅'; + const desc = Logger.truncateStr((a.description || a.name || '—').trim(), 60); + out += `${status} ${desc} ${Logger.formatDurationMs(durationMs)} (${errCount} errors)\n`; + } + + return out; + } } \ No newline at end of file diff --git a/src/unity-logging.ts b/src/unity-logging.ts index d149bb5a..0331e837 100644 --- a/src/unity-logging.ts +++ b/src/unity-logging.ts @@ -92,6 +92,41 @@ function sanitizeStackTrace(raw: string | undefined): string | undefined { return sanitized; } +interface StackFrame { + file: string; + line: number; + title: string; +} + +const MAX_STACK_FRAME_ANNOTATIONS = 5; + +function parseStackFrames(stackTrace: string, projectPath: string | undefined): StackFrame[] { + const frames: StackFrame[] = []; + const lines = stackTrace.split(/\r?\n/).map(l => l.trim()).filter(Boolean); + for (const stackLine of lines) { + const inMatch = stackLine.match(/\s+in\s+([^\s]+):(\d+)\s*$/); + const parenMatch = stackLine.match(/\(([^)]+):(\d+)\)\s*$/); + const plainMatch = stackLine.match(/^(.+):(\d+)\s*$/); + let file: string | undefined; + let lineNum: number | undefined; + if (inMatch) { + file = inMatch[1].replace(/\\/g, '/'); + lineNum = parseInt(inMatch[2], 10); + } else if (parenMatch) { + file = parenMatch[1].replace(/\\/g, '/'); + lineNum = parseInt(parenMatch[2], 10); + } else if (plainMatch) { + file = plainMatch[1].replace(/\\/g, '/'); + lineNum = parseInt(plainMatch[2], 10); + } + if (file && Number.isFinite(lineNum) && lineNum > 0 && + projectPath && file.startsWith(projectPath)) { + frames.push({ file, line: lineNum, title: stackLine }); + } + } + return frames; +} + const MIN_DESCRIPTION_COLUMN_WIDTH = 16; const DEFAULT_TERMINAL_WIDTH = 120; const TERMINAL_WIDTH_SAFETY_MARGIN = 2; @@ -1017,6 +1052,14 @@ export function TailLogFile(logPath: string, projectPath: string | undefined): L // only annotate if the file is within the current project if (projectPath && file && file.startsWith(projectPath)) { logger.annotate(LogLevel.ERROR, message, file, utp.line); + // Link stack trace to annotations: emit one annotation per frame (capped) for clickable stack in Checks + if (stacktrace && projectPath) { + const frames = parseStackFrames(stacktrace, projectPath); + const toEmit = frames.slice(0, MAX_STACK_FRAME_ANNOTATIONS); + for (const frame of toEmit) { + logger.annotate(LogLevel.ERROR, frame.title, frame.file, frame.line, undefined, undefined, undefined, 'Stack frame'); + } + } } else { switch (messageLevel) { case LogLevel.WARN: From efa5562f188c94151841765cc56ef1703412f7d0 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sun, 22 Feb 2026 13:13:31 -0500 Subject: [PATCH 44/56] fix failing ci/cd checks --- src/unity-logging.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/unity-logging.ts b/src/unity-logging.ts index 0331e837..f59f9955 100644 --- a/src/unity-logging.ts +++ b/src/unity-logging.ts @@ -109,19 +109,19 @@ function parseStackFrames(stackTrace: string, projectPath: string | undefined): const plainMatch = stackLine.match(/^(.+):(\d+)\s*$/); let file: string | undefined; let lineNum: number | undefined; - if (inMatch) { + if (inMatch && inMatch[1] != null && inMatch[2] != null) { file = inMatch[1].replace(/\\/g, '/'); lineNum = parseInt(inMatch[2], 10); - } else if (parenMatch) { + } else if (parenMatch && parenMatch[1] != null && parenMatch[2] != null) { file = parenMatch[1].replace(/\\/g, '/'); lineNum = parseInt(parenMatch[2], 10); - } else if (plainMatch) { + } else if (plainMatch && plainMatch[1] != null && plainMatch[2] != null) { file = plainMatch[1].replace(/\\/g, '/'); lineNum = parseInt(plainMatch[2], 10); } - if (file && Number.isFinite(lineNum) && lineNum > 0 && - projectPath && file.startsWith(projectPath)) { - frames.push({ file, line: lineNum, title: stackLine }); + const line = lineNum !== undefined && Number.isFinite(lineNum) ? lineNum : undefined; + if (file != null && line != null && line > 0 && projectPath != null && file.startsWith(projectPath)) { + frames.push({ file, line, title: stackLine }); } } return frames; From f27ccf2786b99cf54f09e97facf5fb2bec1bc42f Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Fri, 27 Feb 2026 09:43:27 -0500 Subject: [PATCH 45/56] update github actions summary layout --- src/logging.ts | 324 +++++++++++++++++++++++++++++++------------------ 1 file changed, 208 insertions(+), 116 deletions(-) diff --git a/src/logging.ts b/src/logging.ts index 8fae3652..28ee22de 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -1,5 +1,85 @@ import * as fs from 'fs'; -import { UTP } from './utp/utp'; +import { UTP, Severity } from './utp/utp'; + +const TRUNCATE_MSG = 120; +const SUMMARY_BYTE_LIMIT = 1024 * 1024; + +/** Severity order for display: Error first, then Warning, then Info */ +function severityRank(s: string | undefined): number { + if (s === Severity.Error || s === Severity.Exception || s === Severity.Assert) return 0; + if (s === Severity.Warning) return 1; + return 2; // Info or unknown +} + +function dedupeKey(e: UTP): string { + const msg = (e.message || '').trim(); + const file = (e.file || (e as { fileName?: string }).fileName || '').replace(/\\/g, '/'); + const line = e.line ?? (e as { lineNumber?: number }).lineNumber ?? 0; + return `${msg}\n${file}\n${line}`; +} + +/** + * Builds one merged list from LogEntry, Compiler, and error-severity entries. + * Deduplicated by message+file+line, sorted by severity (Error, Warning, Info). + */ +function buildMergedLogList(filtered: UTP[]): UTP[] { + const logEntries = filtered.filter(e => e.type === 'LogEntry'); + const compilerEntries = filtered.filter(e => e.type === 'Compiler'); + const isErrorSeverity = (s: string | undefined) => + s === Severity.Error || s === Severity.Exception || s === Severity.Assert; + const errorSeverityEntries = filtered.filter( + e => (e.type === 'LogEntry' || e.type === 'Compiler') && isErrorSeverity(e.severity) + ); + + const seen = new Set(); + const merged: UTP[] = []; + + const add = (e: UTP) => { + const key = dedupeKey(e); + if (seen.has(key)) return; + seen.add(key); + merged.push(e); + }; + + for (const e of logEntries) add(e); + for (const e of compilerEntries) add(e); + for (const e of errorSeverityEntries) add(e); + + merged.sort((a, b) => severityRank(a.severity) - severityRank(b.severity)); + return merged; +} + +/** Groups merged log by severity for foldouts (Error, Warning, Info). */ +function groupBySeverity(merged: UTP[]): { errorCritical: UTP[]; warning: UTP[]; info: UTP[] } { + const errorCritical: UTP[] = []; + const warning: UTP[] = []; + const info: UTP[] = []; + for (const e of merged) { + if (e.severity === Severity.Error || e.severity === Severity.Exception || e.severity === Severity.Assert) { + errorCritical.push(e); + } else if (e.severity === Severity.Warning) { + warning.push(e); + } else { + info.push(e); + } + } + return { errorCritical, warning, info }; +} + +function truncateStr(s: string, max: number): string { + return s.length <= max ? s : s.slice(0, max) + '…'; +} + +function formatLogEntryLine(e: UTP, maxMsgLen: number = TRUNCATE_MSG): string { + const msg = truncateStr((e.message || '').trim(), maxMsgLen); + let line = `- ${msg}\n`; + const file = (e.file || (e as { fileName?: string }).fileName || '').replace(/\\/g, '/'); + if (file && (e.line !== undefined && e.line > 0 || (e as { lineNumber?: number }).lineNumber)) { + const ln = e.line ?? (e as { lineNumber?: number }).lineNumber ?? ''; + line += ` \`${file}${ln ? `(${ln})` : ''}\`\n`; + } + return line; +} export enum LogLevel { DEBUG = 'debug', @@ -259,8 +339,6 @@ export class Logger { } } - private static readonly SUMMARY_BYTE_LIMIT = 1024 * 1024; - private static formatDurationMs(ms: number | undefined): string { if (ms === undefined || !Number.isFinite(ms)) { return '—'; } if (ms < 1000) { return `${Math.round(ms)}ms`; } @@ -294,22 +372,18 @@ export class Logger { const filtered = telemetry.filter(entry => !excludedTypes.has(entry.type || '')); if (filtered.length === 0) { return; } - const severityError = (s: string | undefined): boolean => - s === 'Error' || s === 'Exception' || s === 'Assert'; - const errorEntries = filtered.filter( - e => (e.type === 'LogEntry' || e.type === 'Compiler') && severityError(e.severity) - ); const completedActions = filtered.filter( e => e.type === 'Action' && e.phase === 'End' ); - const logEntries = filtered.filter(e => e.type === 'LogEntry'); - const compilerEntries = filtered.filter(e => e.type === 'Compiler'); + const merged = buildMergedLogList(filtered); + const bySeverity = groupBySeverity(merged); + const errorCount = bySeverity.errorCritical.length; + const limit = SUMMARY_BYTE_LIMIT; - const limit = Logger.SUMMARY_BYTE_LIMIT; const builders: (() => string)[] = [ - () => this.buildSummaryCollapsible(name, errorEntries, completedActions, logEntries, compilerEntries), - () => this.buildSummaryCountsOnly(name, errorEntries, logEntries.length, compilerEntries.length, completedActions.length), - () => this.buildSummaryErrorsAndTimeline(name, errorEntries, completedActions), + () => this.buildSummaryTimelineAndMergedLog(name, completedActions, bySeverity, limit), + () => this.buildSummaryCollapsibleWithMergedLog(name, completedActions, bySeverity, limit), + () => this.buildSummaryTimelineAndCounts(name, completedActions, merged.length, limit), ]; let summary = ''; for (const build of builders) { @@ -327,143 +401,161 @@ export class Logger { } /** - * Builds summary with collapsible sections per type - * (Errors, Build timeline, LogEntry, Compiler), one line per entry. + * Builds summary: optional stats, build timeline table (always first), then one
per + * severity that has messages (Error, Warning, Info). Truncates log content only when + * needed to stay under byteLimit. */ - private buildSummaryCollapsible( + private buildSummaryTimelineAndMergedLog( name: string, - errorEntries: UTP[], completedActions: UTP[], - logEntries: UTP[], - compilerEntries: UTP[] + bySeverity: { errorCritical: UTP[]; warning: UTP[]; info: UTP[] }, + byteLimit: number ): string { - const MAX_ERROR = 20; - const MAX_ACTION = 50; - const MAX_LOG = 50; - const MAX_COMPILER = 50; - const TRUNCATE_MSG = 120; - let out = `## ${name} Summary\n\n`; - if (errorEntries.length > 0) { - out += `
Errors (${errorEntries.length})\n\n`; - const shown = errorEntries.slice(0, MAX_ERROR); - for (const e of shown) { - out += `- ${Logger.truncateStr((e.message || '').trim(), TRUNCATE_MSG)}\n`; - if (e.file && e.line !== undefined && e.line > 0) { - const file = (e.file || '').replace(/\\/g, '/'); - out += ` \`${file}:${e.line}\`\n`; - } - } - if (errorEntries.length > MAX_ERROR) { - out += `- ... and ${errorEntries.length - MAX_ERROR} more (see annotations).\n`; - } - out += `\n
\n\n`; + const totalDurationMs = completedActions.reduce( + (sum, a) => sum + (a.duration ?? (a.durationMicroseconds != null ? a.durationMicroseconds / 1000 : 0)), + 0 + ); + const totalSec = totalDurationMs / 1000; + const totalStr = totalSec >= 60 ? `${Math.round(totalSec / 60)}m ${Math.round(totalSec % 60)}s` : `${totalSec.toFixed(1)}s`; + out += `${bySeverity.errorCritical.length} errors, ${completedActions.length} actions, total ${totalStr}\n\n`; + + out += `| Status | Duration | Errors | Step |\n`; + out += `|--------|----------|--------|------|\n`; + let timelineShown = 0; + for (const a of completedActions) { + const durationMs = a.duration ?? (a.durationMicroseconds != null ? a.durationMicroseconds / 1000 : undefined); + const errCount = Array.isArray(a.errors) ? a.errors.length : 0; + const status = errCount > 0 ? '❌' : '✅'; + const desc = (a.description || a.name || '—').trim(); + const durationStr = Logger.formatDurationMs(durationMs); + const row = `| ${status} | ${durationStr} | ${errCount} | ${desc} |\n`; + if (Buffer.byteLength(out + row, 'utf8') > byteLimit) break; + out += row; + timelineShown++; } - - if (completedActions.length > 0) { - out += `
Build timeline (${completedActions.length} actions)\n\n`; - const shown = completedActions.slice(0, MAX_ACTION); - for (const a of shown) { - const durationMs = a.duration ?? (a.durationMicroseconds != null ? a.durationMicroseconds / 1000 : undefined); - const errCount = Array.isArray(a.errors) ? a.errors.length : 0; - const status = errCount > 0 ? '❌' : '✅'; - const desc = Logger.truncateStr((a.description || a.name || '—').trim(), 60); - out += `${status} ${desc} ${Logger.formatDurationMs(durationMs)} (${errCount} errors)\n`; - } - out += `\n
\n\n`; + if (timelineShown < completedActions.length) { + out += `| … | … | … | … and ${completedActions.length - timelineShown} more |\n`; } - - if (logEntries.length > 0) { - out += `
LogEntry (${logEntries.length})\n\n`; - const shown = logEntries.slice(0, MAX_LOG); - for (const e of shown) { - out += `- ${e.severity ?? 'Info'}: ${Logger.truncateStr((e.message || '').trim(), TRUNCATE_MSG)}\n`; + out += `\n`; + + const limit = byteLimit; + const appendFoldout = (title: string, entries: UTP[], dropSuffix: string): void => { + if (entries.length === 0) return; + out += `
${title} (${entries.length})\n\n`; + let shown = 0; + let omitted = 0; + for (const e of entries) { + const line = formatLogEntryLine(e); + if (Buffer.byteLength(out + line, 'utf8') > limit) { + omitted = entries.length - shown; + break; + } + out += line; + shown++; } - if (logEntries.length > MAX_LOG) { - out += `- ... and ${logEntries.length - MAX_LOG} more.\n`; + if (omitted > 0) { + out += `- ... and ${omitted} more ${dropSuffix}\n`; } out += `\n
\n\n`; - } + }; - if (compilerEntries.length > 0) { - out += `
Compiler (${compilerEntries.length})\n\n`; - const shown = compilerEntries.slice(0, MAX_COMPILER); - for (const e of shown) { - out += `- ${e.severity ?? 'Info'}: ${Logger.truncateStr((e.message || '').trim(), TRUNCATE_MSG)}\n`; - } - if (compilerEntries.length > MAX_COMPILER) { - out += `- ... and ${compilerEntries.length - MAX_COMPILER} more.\n`; - } - out += `\n
\n\n`; - } + appendFoldout('Error', bySeverity.errorCritical, '(see annotations).'); + appendFoldout('Warning', bySeverity.warning, '(truncated; see full log).'); + appendFoldout('Info', bySeverity.info, '(truncated; see full log).'); return out; } /** - * Builds summary with type counts in a markdown table. - * When there are errors, adds a line pointing to annotations. + * Builds summary with timeline in a
and merged log foldouts by severity. + * Used when primary builder would exceed size limit. */ - private buildSummaryCountsOnly( + private buildSummaryCollapsibleWithMergedLog( name: string, - errorEntries: UTP[], - logEntryCount: number, - compilerCount: number, - actionCount: number + completedActions: UTP[], + bySeverity: { errorCritical: UTP[]; warning: UTP[]; info: UTP[] }, + byteLimit: number ): string { - const errorCount = errorEntries.length; - let out = `## ${name} Summary\n\n`; - out += `| Type | Count |\n`; - out += `|------|-------|\n`; - out += `| Errors | ${errorCount} |\n`; - out += `| LogEntry | ${logEntryCount} |\n`; - out += `| Compiler | ${compilerCount} |\n`; - out += `| Actions | ${actionCount} |\n\n`; - if (errorCount > 0) { - out += `See annotations for details.\n`; + + if (completedActions.length > 0) { + out += `
Build timeline (${completedActions.length} actions)\n\n`; + out += `| Status | Duration | Errors | Step |\n`; + out += `|--------|----------|--------|------|\n`; + let timelineShown = 0; + for (const a of completedActions) { + const errCount = Array.isArray(a.errors) ? a.errors.length : 0; + const row = `| ${errCount > 0 ? '❌' : '✅'} | ${Logger.formatDurationMs(a.duration ?? (a.durationMicroseconds != null ? a.durationMicroseconds / 1000 : undefined))} | ${errCount} | ${(a.description || a.name || '—').trim()} |\n`; + if (Buffer.byteLength(out + row, 'utf8') > byteLimit) break; + out += row; + timelineShown++; + } + if (timelineShown < completedActions.length) { + out += `| … | … | … | … and ${completedActions.length - timelineShown} more |\n`; + } + out += `\n
\n\n`; } + + const limit = byteLimit; + const appendFoldout = (title: string, entries: UTP[], dropSuffix: string): void => { + if (entries.length === 0) return; + out += `
${title} (${entries.length})\n\n`; + let shown = 0; + let omitted = 0; + for (const e of entries) { + const line = formatLogEntryLine(e); + if (Buffer.byteLength(out + line, 'utf8') > limit) { + omitted = entries.length - shown; + break; + } + out += line; + shown++; + } + if (omitted > 0) out += `- ... and ${omitted} more ${dropSuffix}\n`; + out += `\n
\n\n`; + }; + appendFoldout('Error', bySeverity.errorCritical, '(see annotations).'); + appendFoldout('Warning', bySeverity.warning, '(truncated; see full log).'); + appendFoldout('Info', bySeverity.info, '(truncated; see full log).'); + return out; } /** - * Builds minimal summary: errors list with optional file:line, - * then one line per completed action (no LogEntry/Compiler). + * Fallback: build timeline table + counts table only (no log foldouts). + * Used when even collapsible summary would exceed 1 MB. */ - private buildSummaryErrorsAndTimeline( + private buildSummaryTimelineAndCounts( name: string, - errorEntries: UTP[], - completedActions: UTP[] + completedActions: UTP[], + logCount: number, + byteLimit: number ): string { - const MAX_ERROR = 20; - const TRUNCATE_MSG = 120; - let out = `## ${name} Summary\n\n`; - - if (errorEntries.length > 0) { - const shown = errorEntries.slice(0, MAX_ERROR); - for (const e of shown) { - out += `- ${Logger.truncateStr((e.message || '').trim(), TRUNCATE_MSG)}\n`; - if (e.file && e.line !== undefined && e.line > 0) { - const file = (e.file || '').replace(/\\/g, '/'); - out += ` \`${file}:${e.line}\`\n`; - } - } - if (errorEntries.length > MAX_ERROR) { - out += `- ... and ${errorEntries.length - MAX_ERROR} more (see annotations).\n`; - } - out += `\n`; - } - + out += `| Status | Duration | Errors | Step |\n`; + out += `|--------|----------|--------|------|\n`; + let timelineShown = 0; for (const a of completedActions) { const durationMs = a.duration ?? (a.durationMicroseconds != null ? a.durationMicroseconds / 1000 : undefined); const errCount = Array.isArray(a.errors) ? a.errors.length : 0; const status = errCount > 0 ? '❌' : '✅'; - const desc = Logger.truncateStr((a.description || a.name || '—').trim(), 60); - out += `${status} ${desc} ${Logger.formatDurationMs(durationMs)} (${errCount} errors)\n`; + const desc = (a.description || a.name || '—').trim(); + const row = `| ${status} | ${Logger.formatDurationMs(durationMs)} | ${errCount} | ${desc} |\n`; + if (Buffer.byteLength(out + row, 'utf8') > byteLimit) break; + out += row; + timelineShown++; } - + if (timelineShown < completedActions.length) { + out += `| … | … | … | … and ${completedActions.length - timelineShown} more |\n`; + } + out += `\n`; + out += `| Type | Count |\n`; + out += `|------|-------|\n`; + out += `| Log | ${logCount} |\n`; + out += `| Actions | ${completedActions.length} |\n\n`; + out += `See annotations for details.\n`; return out; } } \ No newline at end of file From 75ab05299d42be407b3a5ed3cdb3694decc8fc15 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Fri, 27 Feb 2026 11:04:06 -0500 Subject: [PATCH 46/56] update summary log format --- src/logging.ts | 129 ++++++++++++++++++++++++++++++++++++++++--- src/unity-logging.ts | 2 +- 2 files changed, 121 insertions(+), 10 deletions(-) diff --git a/src/logging.ts b/src/logging.ts index 28ee22de..6fd69d0d 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -18,6 +18,31 @@ function dedupeKey(e: UTP): string { return `${msg}\n${file}\n${line}`; } +/** + * Returns true if the entry's file is under the project path (or entry has no file). + * projectPath is normalized to forward slashes; comparison is path-prefix. + */ +function isEntryUnderProjectPath(e: UTP, projectPath: string): boolean { + const file = (e.file || (e as { fileName?: string }).fileName || '').trim(); + if (!file) return true; + const normFile = file.replace(/\\/g, '/'); + const normProject = projectPath.replace(/\\/g, '/'); + const base = normProject.endsWith('/') ? normProject : normProject + '/'; + return normFile === normProject || normFile.startsWith(base); +} + +/** + * Returns true if the entry's file looks like a Unity engine path (should be omitted when not using projectPath). + */ +function isUnityEnginePath(file: string): boolean { + const norm = file.replace(/\\/g, '/'); + if (UNITY_ENGINE_PATH_PREFIXES.some(p => norm.startsWith(p))) return true; + if (norm.includes('/Runtime/') || norm.includes('\\Runtime\\')) return true; + if (!norm.endsWith('.cpp')) return false; + const underProject = norm.includes('/Assets/') || norm.includes('/Packages/') || norm.includes('/Library/PackageCache/'); + return !underProject; +} + /** * Builds one merged list from LogEntry, Compiler, and error-severity entries. * Deduplicated by message+file+line, sorted by severity (Error, Warning, Info). @@ -49,6 +74,22 @@ function buildMergedLogList(filtered: UTP[]): UTP[] { return merged; } +/** + * Filters merged list to project-relevant entries only. + * When projectPath is set: keep entries with no file or file under projectPath. + * When projectPath is not set: exclude Unity engine paths only (keep PackageCache and project paths). + */ +function filterMergedByPath(merged: UTP[], options: { projectPath?: string } | undefined): UTP[] { + if (options?.projectPath != null && options.projectPath !== '') { + return merged.filter(e => isEntryUnderProjectPath(e, options.projectPath!)); + } + return merged.filter(e => { + const file = (e.file || (e as { fileName?: string }).fileName || '').trim(); + if (!file) return true; + return !isUnityEnginePath(file); + }); +} + /** Groups merged log by severity for foldouts (Error, Warning, Info). */ function groupBySeverity(merged: UTP[]): { errorCritical: UTP[]; warning: UTP[]; info: UTP[] } { const errorCritical: UTP[] = []; @@ -70,15 +111,84 @@ function truncateStr(s: string, max: number): string { return s.length <= max ? s : s.slice(0, max) + '…'; } +/** Paths to treat as Unity engine (omit from summary when using heuristic filter). */ +const UNITY_ENGINE_PATH_PREFIXES = [ + 'Runtime/', + './Runtime/', + 'Modules/', + './Modules/', +]; + +/** + * Normalizes a log message for display by stripping a redundant file:line prefix + * when it matches the entry's file/line so the path appears only once. + * Returns the normalized message and optional column if present in the prefix. + */ +function normalizeMessageForDisplay( + message: string, + file: string, + line: number | undefined +): { message: string; column?: number } { + const trimmed = message.trim(); + const normFile = file.replace(/\\/g, '/'); + if (!normFile && line === undefined) return { message: trimmed }; + + // path(line,col): e.g. Assets/File.cs(2,8): error ... + const parenColon = trimmed.match(/^(.+?)\((\d+),(\d+)\):\s*/); + if (parenColon) { + const msgPath = parenColon[1].replace(/\\/g, '/'); + const msgLine = parseInt(parenColon[2], 10); + const msgCol = parseInt(parenColon[3], 10); + const pathMatches = msgPath === normFile || normFile.endsWith(msgPath) || msgPath.endsWith(normFile); + if (pathMatches && (line === undefined || line === msgLine)) { + return { message: trimmed.slice(parenColon[0].length).trim(), column: msgCol }; + } + } + + // path(line): e.g. Assets/File.cs(2): ... + const parenOnly = trimmed.match(/^(.+?)\((\d+)\):\s*/); + if (parenOnly) { + const msgPath = parenOnly[1].replace(/\\/g, '/'); + const msgLine = parseInt(parenOnly[2], 10); + const pathMatches = msgPath === normFile || normFile.endsWith(msgPath) || msgPath.endsWith(normFile); + if (pathMatches && (line === undefined || line === msgLine)) { + return { message: trimmed.slice(parenOnly[0].length).trim() }; + } + } + + // path:line: e.g. path/to/file.cs:10: + const pathLineColon = trimmed.match(/^(.+?):(\d+):\s*/); + if (pathLineColon) { + const msgPath = pathLineColon[1].replace(/\\/g, '/'); + const msgLine = parseInt(pathLineColon[2], 10); + const pathMatches = msgPath === normFile || normFile.endsWith(msgPath) || msgPath.endsWith(normFile); + if (pathMatches && (line === undefined || line === msgLine)) { + return { message: trimmed.slice(pathLineColon[0].length).trim() }; + } + } + + return { message: trimmed }; +} + +/** + * One line per entry: path(line,col): <message> or path(line): <message> when column is missing. + * When file/line are missing, outputs: - <message>. + */ function formatLogEntryLine(e: UTP, maxMsgLen: number = TRUNCATE_MSG): string { - const msg = truncateStr((e.message || '').trim(), maxMsgLen); - let line = `- ${msg}\n`; const file = (e.file || (e as { fileName?: string }).fileName || '').replace(/\\/g, '/'); - if (file && (e.line !== undefined && e.line > 0 || (e as { lineNumber?: number }).lineNumber)) { - const ln = e.line ?? (e as { lineNumber?: number }).lineNumber ?? ''; - line += ` \`${file}${ln ? `(${ln})` : ''}\`\n`; + const line = e.line ?? (e as { lineNumber?: number }).lineNumber; + const hasLocation = file && (line !== undefined && line > 0); + const rawMsg = (e.message || '').trim(); + const { message: normalizedMsg, column } = hasLocation + ? normalizeMessageForDisplay(rawMsg, file, line) + : { message: rawMsg, column: undefined as number | undefined }; + const msg = truncateStr(normalizedMsg, maxMsgLen); + + if (hasLocation) { + const loc = column !== undefined ? `${file}(${line},${column})` : `${file}(${line})`; + return `- ${loc}: ${msg}\n`; } - return line; + return `- ${msg}\n`; } export enum LogLevel { @@ -362,7 +472,7 @@ export class Logger { return rebuilt + footer; } - public CI_appendWorkflowSummary(name: string, telemetry: UTP[]) { + public CI_appendWorkflowSummary(name: string, telemetry: UTP[], options?: { projectPath?: string }) { if (telemetry.length === 0) { return; } switch (this._ci) { case 'GITHUB_ACTIONS': { @@ -376,14 +486,15 @@ export class Logger { e => e.type === 'Action' && e.phase === 'End' ); const merged = buildMergedLogList(filtered); - const bySeverity = groupBySeverity(merged); + const pathFiltered = filterMergedByPath(merged, options); + const bySeverity = groupBySeverity(pathFiltered); const errorCount = bySeverity.errorCritical.length; const limit = SUMMARY_BYTE_LIMIT; const builders: (() => string)[] = [ () => this.buildSummaryTimelineAndMergedLog(name, completedActions, bySeverity, limit), () => this.buildSummaryCollapsibleWithMergedLog(name, completedActions, bySeverity, limit), - () => this.buildSummaryTimelineAndCounts(name, completedActions, merged.length, limit), + () => this.buildSummaryTimelineAndCounts(name, completedActions, pathFiltered.length, limit), ]; let summary = ''; for (const build of builders) { diff --git a/src/unity-logging.ts b/src/unity-logging.ts index f59f9955..f3748d0e 100644 --- a/src/unity-logging.ts +++ b/src/unity-logging.ts @@ -1009,7 +1009,7 @@ export function TailLogFile(logPath: string, projectPath: string | undefined): L telemetryFlushed = true; await writeUtpTelemetryLog(utpLogPath, telemetry, logger); const parsed = path.parse(logPath); - Logger.instance.CI_appendWorkflowSummary(parsed.name, telemetry); + Logger.instance.CI_appendWorkflowSummary(parsed.name, telemetry, { projectPath: projectPath ?? undefined }); }; const writeStdoutThenTableContent = (content: string, restoreTable: boolean = true): void => { From 19f46b46360d7083206e1d8248e8e4c99457b6d7 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Fri, 27 Feb 2026 11:23:44 -0500 Subject: [PATCH 47/56] fix build errors --- src/logging.ts | 15 +++++++++------ src/unity-logging.ts | 6 +++++- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/logging.ts b/src/logging.ts index 6fd69d0d..085ae9b4 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -135,35 +135,38 @@ function normalizeMessageForDisplay( // path(line,col): e.g. Assets/File.cs(2,8): error ... const parenColon = trimmed.match(/^(.+?)\((\d+),(\d+)\):\s*/); - if (parenColon) { + if (parenColon && parenColon[1] != null && parenColon[2] != null && parenColon[3] != null) { + const fullMatch = parenColon[0]; const msgPath = parenColon[1].replace(/\\/g, '/'); const msgLine = parseInt(parenColon[2], 10); const msgCol = parseInt(parenColon[3], 10); const pathMatches = msgPath === normFile || normFile.endsWith(msgPath) || msgPath.endsWith(normFile); if (pathMatches && (line === undefined || line === msgLine)) { - return { message: trimmed.slice(parenColon[0].length).trim(), column: msgCol }; + return { message: trimmed.slice(fullMatch.length).trim(), column: msgCol }; } } // path(line): e.g. Assets/File.cs(2): ... const parenOnly = trimmed.match(/^(.+?)\((\d+)\):\s*/); - if (parenOnly) { + if (parenOnly && parenOnly[1] != null && parenOnly[2] != null) { + const fullMatch = parenOnly[0]; const msgPath = parenOnly[1].replace(/\\/g, '/'); const msgLine = parseInt(parenOnly[2], 10); const pathMatches = msgPath === normFile || normFile.endsWith(msgPath) || msgPath.endsWith(normFile); if (pathMatches && (line === undefined || line === msgLine)) { - return { message: trimmed.slice(parenOnly[0].length).trim() }; + return { message: trimmed.slice(fullMatch.length).trim() }; } } // path:line: e.g. path/to/file.cs:10: const pathLineColon = trimmed.match(/^(.+?):(\d+):\s*/); - if (pathLineColon) { + if (pathLineColon && pathLineColon[1] != null && pathLineColon[2] != null) { + const fullMatch = pathLineColon[0]; const msgPath = pathLineColon[1].replace(/\\/g, '/'); const msgLine = parseInt(pathLineColon[2], 10); const pathMatches = msgPath === normFile || normFile.endsWith(msgPath) || msgPath.endsWith(normFile); if (pathMatches && (line === undefined || line === msgLine)) { - return { message: trimmed.slice(pathLineColon[0].length).trim() }; + return { message: trimmed.slice(fullMatch.length).trim() }; } } diff --git a/src/unity-logging.ts b/src/unity-logging.ts index f3748d0e..9bef5348 100644 --- a/src/unity-logging.ts +++ b/src/unity-logging.ts @@ -1009,7 +1009,11 @@ export function TailLogFile(logPath: string, projectPath: string | undefined): L telemetryFlushed = true; await writeUtpTelemetryLog(utpLogPath, telemetry, logger); const parsed = path.parse(logPath); - Logger.instance.CI_appendWorkflowSummary(parsed.name, telemetry, { projectPath: projectPath ?? undefined }); + Logger.instance.CI_appendWorkflowSummary( + parsed.name, + telemetry, + projectPath != null && projectPath !== '' ? { projectPath } : undefined + ); }; const writeStdoutThenTableContent = (content: string, restoreTable: boolean = true): void => { From f2c2482e76d5ea706e17a4111389ad8ca935ad53 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Fri, 27 Feb 2026 11:24:42 -0500 Subject: [PATCH 48/56] bump deps --- package-lock.json | 165 ++++++++++++++++++++++++++++++---------------- package.json | 4 +- 2 files changed, 110 insertions(+), 59 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0e27a083..c746989a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ }, "devDependencies": { "@types/jest": "^30.0.0", - "@types/node": "^24.10.13", + "@types/node": "^24.10.15", "@types/semver": "^7.7.1", "@types/update-notifier": "^6.0.8", "jest": "^30.2.0", @@ -1024,6 +1024,23 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/@jest/reporters/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jest/reporters/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/@jest/reporters/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1087,13 +1104,13 @@ "license": "ISC" }, "node_modules/@jest/reporters/node_modules/minimatch": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", - "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -1120,13 +1137,13 @@ } }, "node_modules/@jest/reporters/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -1650,9 +1667,9 @@ } }, "node_modules/@types/node": { - "version": "24.10.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.13.tgz", - "integrity": "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg==", + "version": "24.10.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.15.tgz", + "integrity": "sha512-BgjLoRuSr0MTI5wA6gMw9Xy0sFudAaUuvrnjgGx9wZ522fYYLA5SYJ+1Y30vTcJEG+DRCyDHx/gzQVfofYzSdg==", "dev": true, "license": "MIT", "dependencies": { @@ -2257,12 +2274,12 @@ } }, "node_modules/balanced-match": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.3.tgz", - "integrity": "sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "license": "MIT", "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" } }, "node_modules/baseline-browser-mapping": { @@ -2302,15 +2319,15 @@ } }, "node_modules/brace-expansion": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz", - "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" } }, "node_modules/braces": { @@ -2413,9 +2430,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001772", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001772.tgz", - "integrity": "sha512-mIwLZICj+ntVTw4BT2zfp+yu/AqV6GMKfJVJMx3MwPxs+uk/uj2GLl2dH8LQbjiLDX66amCga5nKFyDgRR43kg==", + "version": "1.0.30001774", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001774.tgz", + "integrity": "sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==", "dev": true, "funding": [ { @@ -3691,6 +3708,23 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/jest-config/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-config/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/jest-config/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -3754,13 +3788,13 @@ "license": "ISC" }, "node_modules/jest-config/node_modules/minimatch": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", - "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -3787,13 +3821,13 @@ } }, "node_modules/jest-config/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -4344,6 +4378,23 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/jest-runtime/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-runtime/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/jest-runtime/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -4407,13 +4458,13 @@ "license": "ISC" }, "node_modules/jest-runtime/node_modules/minimatch": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", - "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -4440,13 +4491,13 @@ } }, "node_modules/jest-runtime/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -4937,9 +4988,9 @@ } }, "node_modules/minimatch": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", - "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "license": "BlueOak-1.0.0", "dependencies": { "brace-expansion": "^5.0.2" @@ -5589,13 +5640,13 @@ } }, "node_modules/string-width/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -5788,9 +5839,9 @@ } }, "node_modules/test-exclude/node_modules/minimatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", - "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -6168,12 +6219,12 @@ } }, "node_modules/update-notifier/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -6404,13 +6455,13 @@ } }, "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" diff --git a/package.json b/package.json index 5a0ec8c9..5916d25f 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ }, "devDependencies": { "@types/jest": "^30.0.0", - "@types/node": "^24.10.13", + "@types/node": "^24.10.15", "@types/semver": "^7.7.1", "@types/update-notifier": "^6.0.8", "jest": "^30.2.0", @@ -68,4 +68,4 @@ "ts-node": "^10.9.2", "typescript": "^5.9.3" } -} \ No newline at end of file +} From 4eaafc69d7360ff3f0254749ab5b4a1d23e42796 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 11:28:15 -0500 Subject: [PATCH 49/56] Bump tar from 7.5.2 to 7.5.8 in the npm_and_yarn group across 1 directory (#70) Bumps the npm_and_yarn group with 1 update in the / directory: [tar](https://github.com/isaacs/node-tar). Updates `tar` from 7.5.2 to 7.5.8
Commits
  • 6b8eba0 7.5.8
  • 2cb1120 fix(unpack): improve UnpackSync symlink error "into" path accuracy
  • d18e4e1 fix: do not write linkpaths through symlinks
  • 4a37eb9 7.5.7
  • f4a7aa9 fix: properly sanitize hard links containing ..
  • 394ece6 7.5.6
  • 7d4cc17 fix race puting a Link ahead of its target File
  • 26ab904 7.5.5
  • e9a1ddb fix: do not prevent valid linkpaths within archive
  • 911c886 7.5.4
  • Additional commits viewable in compare view
Maintainer changes

This version was pushed to npm by isaacs, a new releaser for tar since your current version.

Install script changes

This version adds prepare script that runs during installation. Review the package contents before updating.


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=tar&package-manager=npm_and_yarn&previous-version=7.5.2&new-version=7.5.8)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/RageAgainstThePixel/unity-cli/network/alerts).
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Stephen Hodgson From 38b655ab4760d9269d686b5559aea364b283860d Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Fri, 27 Feb 2026 12:12:49 -0500 Subject: [PATCH 50/56] fix some regressions. add unit test tables --- src/logging.ts | 184 +++++++++++++++++++++++++++++++++---------- src/unity-logging.ts | 10 ++- 2 files changed, 151 insertions(+), 43 deletions(-) diff --git a/src/logging.ts b/src/logging.ts index 085ae9b4..f3304c40 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -18,14 +18,25 @@ function dedupeKey(e: UTP): string { return `${msg}\n${file}\n${line}`; } +/** + * Returns true if the path looks absolute (Unix / or Windows X:/). + */ +function isAbsolutePath(file: string): boolean { + const norm = file.replace(/\\/g, '/'); + if (norm.startsWith('/')) return true; + return /^[a-zA-Z]:\//.test(norm); +} + /** * Returns true if the entry's file is under the project path (or entry has no file). - * projectPath is normalized to forward slashes; comparison is path-prefix. + * Relative paths (e.g. Assets/..., Packages/...) are always kept so Unity UTP log/compiler + * entries with relative file paths still appear in the summary. */ function isEntryUnderProjectPath(e: UTP, projectPath: string): boolean { const file = (e.file || (e as { fileName?: string }).fileName || '').trim(); if (!file) return true; const normFile = file.replace(/\\/g, '/'); + if (!isAbsolutePath(normFile)) return true; const normProject = projectPath.replace(/\\/g, '/'); const base = normProject.endsWith('/') ? normProject : normProject + '/'; return normFile === normProject || normFile.startsWith(base); @@ -107,6 +118,72 @@ function groupBySeverity(merged: UTP[]): { errorCritical: UTP[]; warning: UTP[]; return { errorCritical, warning, info }; } +/** Single test result row for summary and CLI table. */ +export interface TestResultSummary { + status: string; + durationMs: number; + description: string; + message?: string; +} + +/** Maps UTPTestStatus.state to display status (Unity/NUnit-style: 0 Inconclusive, 1 Passed, 2 Failed, 3 Skipped). */ +export function testStatusFromState(state: number | undefined): string { + switch (state) { + case 1: return '✅'; + case 2: return '❌'; + case 3: return '⏭️'; + case 0: + default: return '◯'; + } +} + +/** Converts a single TestStatus UTP to TestResultSummary. Exported for CLI use. */ +export function utpToTestResultSummary(e: UTP): TestResultSummary { + const state = (e as { state?: number }).state; + const durationMs = e.duration ?? (e.durationMicroseconds != null ? e.durationMicroseconds / 1000 : 0); + const description = (e.name || e.description || '—').trim(); + const msg = (e.message || '').trim(); + const summary: TestResultSummary = { + status: testStatusFromState(state), + durationMs, + description, + }; + if (msg !== '') { + summary.message = msg; + } + return summary; +} + +/** Collects TestStatus entries from telemetry into TestResultSummary rows. */ +function collectTestResults(filtered: UTP[]): TestResultSummary[] { + return filtered.filter(e => e.type === 'TestStatus').map(utpToTestResultSummary); +} + +/** Builds a markdown table string for test results (Status | Duration | Test). Exported for CLI use. */ +export function buildTestResultsTableMarkdown(testResults: TestResultSummary[], byteLimit: number, prefix?: string): string { + if (testResults.length === 0) return ''; + const p = prefix ?? ''; + let out = p + `### Test results\n\n`; + out += `| Status | Duration | Test |\n`; + out += `|--------|----------|------|\n`; + let shown = 0; + for (const row of testResults) { + const durationStr = row.durationMs >= 1000 + ? `${(row.durationMs / 1000).toFixed(1)}s` + : `${Math.round(row.durationMs)} ms`; + const desc = row.description.length > 80 ? row.description.slice(0, 77) + '…' : row.description; + const line = `| ${row.status} | ${durationStr} | ${desc} |\n`; + if (Buffer.byteLength(out + line, 'utf8') > byteLimit) break; + out += line; + shown++; + } + if (shown < testResults.length) { + out += `| … | … | … and ${testResults.length - shown} more |\n`; + } + out += `\n`; + return out; +} + function truncateStr(s: string, max: number): string { return s.length <= max ? s : s.slice(0, max) + '…'; } @@ -488,16 +565,16 @@ export class Logger { const completedActions = filtered.filter( e => e.type === 'Action' && e.phase === 'End' ); + const testResults = collectTestResults(filtered); const merged = buildMergedLogList(filtered); const pathFiltered = filterMergedByPath(merged, options); const bySeverity = groupBySeverity(pathFiltered); - const errorCount = bySeverity.errorCritical.length; const limit = SUMMARY_BYTE_LIMIT; const builders: (() => string)[] = [ - () => this.buildSummaryTimelineAndMergedLog(name, completedActions, bySeverity, limit), - () => this.buildSummaryCollapsibleWithMergedLog(name, completedActions, bySeverity, limit), - () => this.buildSummaryTimelineAndCounts(name, completedActions, pathFiltered.length, limit), + () => this.buildSummaryTimelineAndMergedLog(name, completedActions, bySeverity, testResults, limit), + () => this.buildSummaryCollapsibleWithMergedLog(name, completedActions, bySeverity, testResults, limit), + () => this.buildSummaryTimelineAndCounts(name, completedActions, pathFiltered.length, testResults, limit), ]; let summary = ''; for (const build of builders) { @@ -515,14 +592,14 @@ export class Logger { } /** - * Builds summary: optional stats, build timeline table (always first), then one
per - * severity that has messages (Error, Warning, Info). Truncates log content only when - * needed to stay under byteLimit. + * Builds summary: optional stats, build timeline table (only when actions exist), test results + * table (when present), then one
per severity (Error, Warning, Info). */ private buildSummaryTimelineAndMergedLog( name: string, completedActions: UTP[], bySeverity: { errorCritical: UTP[]; warning: UTP[]; info: UTP[] }, + testResults: TestResultSummary[], byteLimit: number ): string { let out = `## ${name} Summary\n\n`; @@ -535,24 +612,31 @@ export class Logger { const totalStr = totalSec >= 60 ? `${Math.round(totalSec / 60)}m ${Math.round(totalSec % 60)}s` : `${totalSec.toFixed(1)}s`; out += `${bySeverity.errorCritical.length} errors, ${completedActions.length} actions, total ${totalStr}\n\n`; - out += `| Status | Duration | Errors | Step |\n`; - out += `|--------|----------|--------|------|\n`; - let timelineShown = 0; - for (const a of completedActions) { - const durationMs = a.duration ?? (a.durationMicroseconds != null ? a.durationMicroseconds / 1000 : undefined); - const errCount = Array.isArray(a.errors) ? a.errors.length : 0; - const status = errCount > 0 ? '❌' : '✅'; - const desc = (a.description || a.name || '—').trim(); - const durationStr = Logger.formatDurationMs(durationMs); - const row = `| ${status} | ${durationStr} | ${errCount} | ${desc} |\n`; - if (Buffer.byteLength(out + row, 'utf8') > byteLimit) break; - out += row; - timelineShown++; + if (completedActions.length > 0) { + out += `| Status | Duration | Errors | Step |\n`; + out += `|--------|----------|--------|------|\n`; + let timelineShown = 0; + for (const a of completedActions) { + const durationMs = a.duration ?? (a.durationMicroseconds != null ? a.durationMicroseconds / 1000 : undefined); + const errCount = Array.isArray(a.errors) ? a.errors.length : 0; + const status = errCount > 0 ? '❌' : '✅'; + const desc = (a.description || a.name || '—').trim(); + const durationStr = Logger.formatDurationMs(durationMs); + const row = `| ${status} | ${durationStr} | ${errCount} | ${desc} |\n`; + if (Buffer.byteLength(out + row, 'utf8') > byteLimit) break; + out += row; + timelineShown++; + } + if (timelineShown < completedActions.length) { + out += `| … | … | … | … and ${completedActions.length - timelineShown} more |\n`; + } + out += `\n`; } - if (timelineShown < completedActions.length) { - out += `| … | … | … | … and ${completedActions.length - timelineShown} more |\n`; + + if (testResults.length > 0) { + const remaining = byteLimit - Buffer.byteLength(out, 'utf8'); + out += buildTestResultsTableMarkdown(testResults, remaining, ''); } - out += `\n`; const limit = byteLimit; const appendFoldout = (title: string, entries: UTP[], dropSuffix: string): void => { @@ -590,6 +674,7 @@ export class Logger { name: string, completedActions: UTP[], bySeverity: { errorCritical: UTP[]; warning: UTP[]; info: UTP[] }, + testResults: TestResultSummary[], byteLimit: number ): string { let out = `## ${name} Summary\n\n`; @@ -612,6 +697,11 @@ export class Logger { out += `\n
\n\n`; } + if (testResults.length > 0) { + const remaining = byteLimit - Buffer.byteLength(out, 'utf8'); + out += buildTestResultsTableMarkdown(testResults, remaining, ''); + } + const limit = byteLimit; const appendFoldout = (title: string, entries: UTP[], dropSuffix: string): void => { if (entries.length === 0) return; @@ -638,38 +728,48 @@ export class Logger { } /** - * Fallback: build timeline table + counts table only (no log foldouts). + * Fallback: build timeline table (when actions exist) + test results table (when present) + counts table. * Used when even collapsible summary would exceed 1 MB. */ private buildSummaryTimelineAndCounts( name: string, completedActions: UTP[], logCount: number, + testResults: TestResultSummary[], byteLimit: number ): string { let out = `## ${name} Summary\n\n`; - out += `| Status | Duration | Errors | Step |\n`; - out += `|--------|----------|--------|------|\n`; - let timelineShown = 0; - for (const a of completedActions) { - const durationMs = a.duration ?? (a.durationMicroseconds != null ? a.durationMicroseconds / 1000 : undefined); - const errCount = Array.isArray(a.errors) ? a.errors.length : 0; - const status = errCount > 0 ? '❌' : '✅'; - const desc = (a.description || a.name || '—').trim(); - const row = `| ${status} | ${Logger.formatDurationMs(durationMs)} | ${errCount} | ${desc} |\n`; - if (Buffer.byteLength(out + row, 'utf8') > byteLimit) break; - out += row; - timelineShown++; + if (completedActions.length > 0) { + out += `| Status | Duration | Errors | Step |\n`; + out += `|--------|----------|--------|------|\n`; + let timelineShown = 0; + for (const a of completedActions) { + const durationMs = a.duration ?? (a.durationMicroseconds != null ? a.durationMicroseconds / 1000 : undefined); + const errCount = Array.isArray(a.errors) ? a.errors.length : 0; + const status = errCount > 0 ? '❌' : '✅'; + const desc = (a.description || a.name || '—').trim(); + const row = `| ${status} | ${Logger.formatDurationMs(durationMs)} | ${errCount} | ${desc} |\n`; + if (Buffer.byteLength(out + row, 'utf8') > byteLimit) break; + out += row; + timelineShown++; + } + if (timelineShown < completedActions.length) { + out += `| … | … | … | … and ${completedActions.length - timelineShown} more |\n`; + } + out += `\n`; } - if (timelineShown < completedActions.length) { - out += `| … | … | … | … and ${completedActions.length - timelineShown} more |\n`; + if (testResults.length > 0) { + const remaining = byteLimit - Buffer.byteLength(out, 'utf8'); + out += buildTestResultsTableMarkdown(testResults, remaining, ''); } - out += `\n`; out += `| Type | Count |\n`; out += `|------|-------|\n`; out += `| Log | ${logCount} |\n`; - out += `| Actions | ${completedActions.length} |\n\n`; - out += `See annotations for details.\n`; + out += `| Actions | ${completedActions.length} |\n`; + if (testResults.length > 0) { + out += `| Tests | ${testResults.length} |\n`; + } + out += `\nSee annotations for details.\n`; return out; } } \ No newline at end of file diff --git a/src/unity-logging.ts b/src/unity-logging.ts index 9bef5348..044f3568 100644 --- a/src/unity-logging.ts +++ b/src/unity-logging.ts @@ -1,6 +1,6 @@ import * as fs from 'fs'; import * as path from 'path'; -import { LogLevel, Logger } from './logging'; +import { LogLevel, Logger, buildTestResultsTableMarkdown, TestResultSummary, utpToTestResultSummary } from './logging'; import { Delay, WaitForFileToBeUnlocked } from './utilities'; import { Phase, @@ -991,6 +991,7 @@ export function TailLogFile(logPath: string, projectPath: string | undefined): L const logPollingInterval = 250; let pendingPartialLine = ''; const telemetry: UTP[] = []; + const testResults: TestResultSummary[] = []; const logger = Logger.instance; const actionAccumulator = new ActionTelemetryAccumulator(); const actionTableRenderer = new ActionTableRenderer(process.stdout.isTTY === true && process.env.CI !== 'true'); @@ -1014,6 +1015,10 @@ export function TailLogFile(logPath: string, projectPath: string | undefined): L telemetry, projectPath != null && projectPath !== '' ? { projectPath } : undefined ); + if (testResults.length > 0) { + const table = buildTestResultsTableMarkdown(testResults, 1024 * 1024, '\n'); + process.stdout.write(table); + } }; const writeStdoutThenTableContent = (content: string, restoreTable: boolean = true): void => { @@ -1038,6 +1043,9 @@ export function TailLogFile(logPath: string, projectPath: string | undefined): L const utpJson = JSON.parse(sanitizedJson); const utp = normalizeTelemetryEntry(utpJson); telemetry.push(utp); + if (utp.type === 'TestStatus') { + testResults.push(utpToTestResultSummary(utp)); + } if (utp.message && 'severity' in utp && (utp.severity === Severity.Error || utp.severity === Severity.Exception || utp.severity === Severity.Assert)) { From dfc3e1db42f81f285e4b72ce2099ef0b62a91223 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Fri, 27 Feb 2026 13:36:10 -0500 Subject: [PATCH 51/56] add additional unit tests to see table format --- .../actions/run-unity-test-batch/action.yml | 5 +- .github/actions/scripts/run-utp-tests.sh | 70 +++++++++++++++---- src/logging.ts | 14 ++-- unity-tests/EditmodeTestsPassing.cs | 16 +++++ unity-tests/EditmodeTestsSkipped.cs | 17 +++++ unity-tests/PlaymodeTestsPassing.cs | 20 ++++++ unity-tests/PlaymodeTestsSkipped.cs | 21 ++++++ 7 files changed, 141 insertions(+), 22 deletions(-) create mode 100644 unity-tests/EditmodeTestsPassing.cs create mode 100644 unity-tests/EditmodeTestsSkipped.cs create mode 100644 unity-tests/PlaymodeTestsPassing.cs create mode 100644 unity-tests/PlaymodeTestsSkipped.cs diff --git a/.github/actions/run-unity-test-batch/action.yml b/.github/actions/run-unity-test-batch/action.yml index 237c73ff..b2ab0555 100644 --- a/.github/actions/run-unity-test-batch/action.yml +++ b/.github/actions/run-unity-test-batch/action.yml @@ -23,11 +23,11 @@ runs: working-directory: ${{ inputs.unity-project-path }} run: | set -euo pipefail - tests_input="CompilerWarnings,CompilerErrors,BuildWarnings,BuildErrors,PlaymodeTestsErrors,EditmodeTestsErrors" + tests_input="CompilerWarnings,CompilerErrors,BuildWarnings,BuildErrors,PlaymodeTestsErrors,EditmodeTestsErrors,EditmodeTestsPassing,EditmodeTestsSkipped,PlaymodeTestsPassing,PlaymodeTestsSkipped,EditmodeSuite,PlaymodeSuite" echo "TESTS_INPUT=$tests_input" >> $GITHUB_ENV needs_test_framework=false - if [[ "$tests_input" == *"PlaymodeTestsErrors"* || "$tests_input" == *"EditmodeTestsErrors"* ]]; then + if [[ "$tests_input" == *"PlaymodeTests"* || "$tests_input" == *"EditmodeTests"* || "$tests_input" == *"EditmodeSuite"* || "$tests_input" == *"PlaymodeSuite"* ]]; then needs_test_framework=true fi @@ -35,6 +35,7 @@ runs: openupm add com.utilities.buildpipeline if [ "$needs_test_framework" = true ]; then openupm add com.unity.test-framework + openupm add com.unity.test-framework.utp-reporter || true fi - name: Run tests diff --git a/.github/actions/scripts/run-utp-tests.sh b/.github/actions/scripts/run-utp-tests.sh index b4b4499f..fc57d949 100755 --- a/.github/actions/scripts/run-utp-tests.sh +++ b/.github/actions/scripts/run-utp-tests.sh @@ -38,12 +38,11 @@ clean_build_outputs() { # 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 ;; + CompilerWarnings|BuildWarnings) echo 0 ;; + CompilerErrors|BuildErrors) echo 1 ;; + PlaymodeTestsErrors|EditmodeTestsErrors) echo 1 ;; + EditmodeSuite|PlaymodeSuite) echo 1 ;; + EditmodeTestsPassing|EditmodeTestsSkipped|PlaymodeTestsPassing|PlaymodeTestsSkipped) echo 0 ;; *) echo 0 ;; esac } @@ -52,8 +51,8 @@ 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" ;; + PlaymodeTestsErrors|PlaymodeSuite) echo "Intentional playmode failure" ;; + EditmodeTestsErrors|EditmodeSuite) echo "Intentional editmode failure" ;; CompilerWarnings) echo "Intentional warning" ;; BuildWarnings) echo "Intentional build warning" ;; *) echo "" ;; @@ -70,7 +69,11 @@ for raw_test in "${tests[@]}"; do fi src="$GITHUB_WORKSPACE/unity-tests/${test_name}.cs" - if [ ! -f "$src" ]; then + is_suite=0 + case "$test_name" in + EditmodeSuite|PlaymodeSuite) is_suite=1 ;; + esac + if [ "$is_suite" -eq 0 ] && [ ! -f "$src" ]; then echo "::error::Requested test '$test_name' not found at $src" failures=$((failures+1)) continue @@ -88,13 +91,23 @@ for raw_test in "${tests[@]}"; do BuildWarnings|BuildErrors) dest="$UNITY_PROJECT_PATH/Assets/Editor/UnityCliTests" ;; - PlaymodeTestsErrors) + PlaymodeTestsErrors|PlaymodeTestsPassing|PlaymodeTestsSkipped) dest="$UNITY_PROJECT_PATH/Assets/Tests/PlayMode/UnityCliTests" asmdef_src="$GITHUB_WORKSPACE/unity-tests/UnityCliTests.PlayMode.asmdef" ;; - EditmodeTestsErrors) + EditmodeTestsErrors|EditmodeTestsPassing|EditmodeTestsSkipped) + dest="$UNITY_PROJECT_PATH/Assets/Tests/EditMode/UnityCliTests" + asmdef_src="$GITHUB_WORKSPACE/unity-tests/UnityCliTests.EditMode.Editor.asmdef" + ;; + EditmodeSuite) dest="$UNITY_PROJECT_PATH/Assets/Tests/EditMode/UnityCliTests" asmdef_src="$GITHUB_WORKSPACE/unity-tests/UnityCliTests.EditMode.Editor.asmdef" + suite_sources="EditmodeTestsErrors,EditmodeTestsPassing,EditmodeTestsSkipped" + ;; + PlaymodeSuite) + dest="$UNITY_PROJECT_PATH/Assets/Tests/PlayMode/UnityCliTests" + asmdef_src="$GITHUB_WORKSPACE/unity-tests/UnityCliTests.PlayMode.asmdef" + suite_sources="PlaymodeTestsErrors,PlaymodeTestsPassing,PlaymodeTestsSkipped" ;; *) echo "::error::Unknown test selection '$test_name'" @@ -112,15 +125,33 @@ for raw_test in "${tests[@]}"; do fi cp "$asmdef_src" "$dest/" fi - cp "$src" "$dest/" - echo "Running test: $test_name (copied to $dest)" + + if [ -n "${suite_sources:-}" ]; then + IFS=',' read -ra suite_files <<< "$suite_sources" + for f in "${suite_files[@]}"; do + f="${f// /}" + suite_src="$GITHUB_WORKSPACE/unity-tests/${f}.cs" + if [ -f "$suite_src" ]; then + cp "$suite_src" "$dest/" + fi + done + unset suite_sources + echo "Running suite: $test_name (copied ${#suite_files[@]} test files to $dest)" + elif [ -f "$src" ]; then + cp "$src" "$dest/" + echo "Running test: $test_name (copied to $dest)" + else + echo "::error::Requested test '$test_name' not found at $src" + failures=$((failures+1)) + continue + fi validate_rc=0 build_rc=0 ran_custom_flow=0 - if [ "$test_name" = "EditmodeTestsErrors" ]; then + if [ "$test_name" = "EditmodeTestsErrors" ] || [ "$test_name" = "EditmodeTestsPassing" ] || [ "$test_name" = "EditmodeTestsSkipped" ] || [ "$test_name" = "EditmodeSuite" ]; 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" @@ -131,6 +162,17 @@ for raw_test in "${tests[@]}"; do ran_custom_flow=1 fi + if [ "$test_name" = "PlaymodeTestsErrors" ] || [ "$test_name" = "PlaymodeTestsPassing" ] || [ "$test_name" = "PlaymodeTestsSkipped" ] || [ "$test_name" = "PlaymodeSuite" ]; then + unity-cli run --log-name "${test_name}-PlayMode" -runTests -testPlatform playmode -assemblyNames "UnityCli.PlayMode.Tests" -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=$? diff --git a/src/logging.ts b/src/logging.ts index f3304c40..7750699c 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -639,9 +639,10 @@ export class Logger { } const limit = byteLimit; - const appendFoldout = (title: string, entries: UTP[], dropSuffix: string): void => { + const appendFoldout = (title: string, entries: UTP[], dropSuffix: string, openByDefault?: boolean): void => { if (entries.length === 0) return; - out += `
${title} (${entries.length})\n\n`; + const openAttr = openByDefault ? ' open' : ''; + out += `${title} (${entries.length})\n\n`; let shown = 0; let omitted = 0; for (const e of entries) { @@ -659,7 +660,7 @@ export class Logger { out += `\n
\n\n`; }; - appendFoldout('Error', bySeverity.errorCritical, '(see annotations).'); + appendFoldout('Error', bySeverity.errorCritical, '(see annotations).', true); appendFoldout('Warning', bySeverity.warning, '(truncated; see full log).'); appendFoldout('Info', bySeverity.info, '(truncated; see full log).'); @@ -703,9 +704,10 @@ export class Logger { } const limit = byteLimit; - const appendFoldout = (title: string, entries: UTP[], dropSuffix: string): void => { + const appendFoldout = (title: string, entries: UTP[], dropSuffix: string, openByDefault?: boolean): void => { if (entries.length === 0) return; - out += `
${title} (${entries.length})\n\n`; + const openAttr = openByDefault ? ' open' : ''; + out += `${title} (${entries.length})\n\n`; let shown = 0; let omitted = 0; for (const e of entries) { @@ -720,7 +722,7 @@ export class Logger { if (omitted > 0) out += `- ... and ${omitted} more ${dropSuffix}\n`; out += `\n
\n\n`; }; - appendFoldout('Error', bySeverity.errorCritical, '(see annotations).'); + appendFoldout('Error', bySeverity.errorCritical, '(see annotations).', true); appendFoldout('Warning', bySeverity.warning, '(truncated; see full log).'); appendFoldout('Info', bySeverity.info, '(truncated; see full log).'); diff --git a/unity-tests/EditmodeTestsPassing.cs b/unity-tests/EditmodeTestsPassing.cs new file mode 100644 index 00000000..48c093e4 --- /dev/null +++ b/unity-tests/EditmodeTestsPassing.cs @@ -0,0 +1,16 @@ +using NUnit.Framework; + +namespace UnityCli.UtpSamples +{ + /// + /// Editmode test that passes for test matrix and summary table coverage. + /// + public class EditmodeTestsPassing + { + [Test] + public void PassesEditmodeSuite() + { + Assert.Pass("Intentional editmode pass"); + } + } +} diff --git a/unity-tests/EditmodeTestsSkipped.cs b/unity-tests/EditmodeTestsSkipped.cs new file mode 100644 index 00000000..4d7e6d88 --- /dev/null +++ b/unity-tests/EditmodeTestsSkipped.cs @@ -0,0 +1,17 @@ +using NUnit.Framework; + +namespace UnityCli.UtpSamples +{ + /// + /// Editmode test that is skipped for test matrix and summary table coverage. + /// + public class EditmodeTestsSkipped + { + [Test] + [Ignore("Intentional editmode skip")] + public void SkippedEditmodeSuite() + { + Assert.Fail("Should not run"); + } + } +} diff --git a/unity-tests/PlaymodeTestsPassing.cs b/unity-tests/PlaymodeTestsPassing.cs new file mode 100644 index 00000000..144fcf34 --- /dev/null +++ b/unity-tests/PlaymodeTestsPassing.cs @@ -0,0 +1,20 @@ +using System.Collections; +using UnityEngine; +using NUnit.Framework; +using UnityEngine.TestTools; + +namespace UnityCli.UtpSamples +{ + /// + /// Playmode test that passes for test matrix and summary table coverage. + /// + public class PlaymodeTestsPassing + { + [UnityTest] + public IEnumerator PassesPlaymodeSuite() + { + yield return null; + Assert.Pass("Intentional playmode pass"); + } + } +} diff --git a/unity-tests/PlaymodeTestsSkipped.cs b/unity-tests/PlaymodeTestsSkipped.cs new file mode 100644 index 00000000..a60dbedb --- /dev/null +++ b/unity-tests/PlaymodeTestsSkipped.cs @@ -0,0 +1,21 @@ +using System.Collections; +using UnityEngine; +using NUnit.Framework; +using UnityEngine.TestTools; + +namespace UnityCli.UtpSamples +{ + /// + /// Playmode test that is skipped for test matrix and summary table coverage. + /// + public class PlaymodeTestsSkipped + { + [UnityTest] + [Ignore("Intentional playmode skip")] + public IEnumerator SkippedPlaymodeSuite() + { + yield return null; + Assert.Fail("Should not run"); + } + } +} From d08413a5ea39fc633193fb1fb0f6f19352845630 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Fri, 27 Feb 2026 20:50:40 -0500 Subject: [PATCH 52/56] give me all the logs --- .github/actions/run-unity-test-batch/action.yml | 6 +++--- .github/actions/scripts/run-utp-tests.sh | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/actions/run-unity-test-batch/action.yml b/.github/actions/run-unity-test-batch/action.yml index b2ab0555..77523fad 100644 --- a/.github/actions/run-unity-test-batch/action.yml +++ b/.github/actions/run-unity-test-batch/action.yml @@ -12,7 +12,7 @@ inputs: required: false default: "" artifact-name: - description: Artifact name for uploaded UTP logs (must be unique per matrix job). + description: Artifact name for uploaded test artifacts (UTP logs, Unity Editor/Player logs, and test results XML; must be unique per matrix job). required: false default: unity-tests-batch-utp-logs runs: @@ -48,10 +48,10 @@ runs: run: | bash "${GITHUB_WORKSPACE}/.github/actions/scripts/run-utp-tests.sh" - - name: Upload UTP logs + - name: Upload test artifacts if: always() uses: actions/upload-artifact@v6 with: name: ${{ inputs.artifact-name }} - path: utp-artifacts/**/*-utp-json.log + path: utp-artifacts/ if-no-files-found: ignore diff --git a/.github/actions/scripts/run-utp-tests.sh b/.github/actions/scripts/run-utp-tests.sh index fc57d949..c3709619 100755 --- a/.github/actions/scripts/run-utp-tests.sh +++ b/.github/actions/scripts/run-utp-tests.sh @@ -265,6 +265,12 @@ for raw_test in "${tests[@]}"; do cp "$utp_src" "$dest_file" || true fi done || true + # Copy test results XML when present (Edit/Play mode) for later analysis + if [ -f "$UNITY_PROJECT_PATH/Builds/Logs/${test_name}-results.xml" ]; then + cp "$UNITY_PROJECT_PATH/Builds/Logs/${test_name}-results.xml" "$test_artifacts/" || true + fi + # Copy all Unity Editor/Player logs for this scenario + find "$UNITY_PROJECT_PATH/Builds/Logs" -maxdepth 1 -type f -name "*${test_name}*.log" -exec cp {} "$test_artifacts/" \; 2>/dev/null || true done From 479ddd453ff3d563151562ec6db7421504a6e44e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 28 Feb 2026 11:19:06 -0500 Subject: [PATCH 53/56] Bump the npm_and_yarn group across 1 directory with 2 updates (#71) Bumps the npm_and_yarn group with 2 updates in the / directory: [tar](https://github.com/isaacs/node-tar) and [minimatch](https://github.com/isaacs/minimatch). Updates `tar` from 7.5.2 to 7.5.8
Commits
  • 6b8eba0 7.5.8
  • 2cb1120 fix(unpack): improve UnpackSync symlink error "into" path accuracy
  • d18e4e1 fix: do not write linkpaths through symlinks
  • 4a37eb9 7.5.7
  • f4a7aa9 fix: properly sanitize hard links containing ..
  • 394ece6 7.5.6
  • 7d4cc17 fix race puting a Link ahead of its target File
  • 26ab904 7.5.5
  • e9a1ddb fix: do not prevent valid linkpaths within archive
  • 911c886 7.5.4
  • Additional commits viewable in compare view
Maintainer changes

This version was pushed to npm by isaacs, a new releaser for tar since your current version.

Install script changes

This version adds prepare script that runs during installation. Review the package contents before updating.


Updates `minimatch` from 10.1.1 to 10.2.4
Changelog

Sourced from minimatch's changelog.

change log

10.2

  • Add braceExpandMax option

10.1

  • Add magicalBraces option for escape
  • Fix makeRe when partial: true is set.
  • Fix makeRe when pattern ends in a final ** path part.

10.0

  • Require node 20 or 22 and higher

9.0

  • No default export, only named exports.

8.0

  • Recursive descent parser for extglob, allowing correct support for arbitrarily nested extglob expressions
  • Bump required Node.js version

7.4

  • Add escape() method
  • Add unescape() method
  • Add Minimatch.hasMagic() method

7.3

  • Add support for posix character classes in a unicode-aware way.

7.2

  • Add windowsNoMagicRoot option

7.1

  • Add optimizationLevel configuration option, and revert the default back to the 6.2 style minimal optimizations, making the advanced transforms introduced in 7.0 opt-in. Also, process provided file paths in the same way in optimizationLevel:2 mode, so most things that matched with optimizationLevel 1 or 0 should match with level 2 as well. However, level 1 is the default, out of an abundance of caution.

... (truncated)

Commits
  • c36addb 10.2.4
  • 26b9002 docs: add warning about ReDoS
  • 3a0d83b fix partial matching of globstar patterns
  • ea94840 10.2.3
  • 0873fba update deps
  • cecaad1 more extglob coalescing for performance
  • 11d0df6 limit nested extglob recursion, flatten extglobs
  • c3448c4 update assertValidPattern param type to unknown from any
  • 0bf499a limit recursion for **, improve perf considerably
  • 9f15c58 update deps
  • Additional commits viewable in compare view

Updates `minimatch` from 3.1.2 to 3.1.5
Changelog

Sourced from minimatch's changelog.

change log

10.2

  • Add braceExpandMax option

10.1

  • Add magicalBraces option for escape
  • Fix makeRe when partial: true is set.
  • Fix makeRe when pattern ends in a final ** path part.

10.0

  • Require node 20 or 22 and higher

9.0

  • No default export, only named exports.

8.0

  • Recursive descent parser for extglob, allowing correct support for arbitrarily nested extglob expressions
  • Bump required Node.js version

7.4

  • Add escape() method
  • Add unescape() method
  • Add Minimatch.hasMagic() method

7.3

  • Add support for posix character classes in a unicode-aware way.

7.2

  • Add windowsNoMagicRoot option

7.1

  • Add optimizationLevel configuration option, and revert the default back to the 6.2 style minimal optimizations, making the advanced transforms introduced in 7.0 opt-in. Also, process provided file paths in the same way in optimizationLevel:2 mode, so most things that matched with optimizationLevel 1 or 0 should match with level 2 as well. However, level 1 is the default, out of an abundance of caution.

... (truncated)

Commits
  • c36addb 10.2.4
  • 26b9002 docs: add warning about ReDoS
  • 3a0d83b fix partial matching of globstar patterns
  • ea94840 10.2.3
  • 0873fba update deps
  • cecaad1 more extglob coalescing for performance
  • 11d0df6 limit nested extglob recursion, flatten extglobs
  • c3448c4 update assertValidPattern param type to unknown from any
  • 0bf499a limit recursion for **, improve perf considerably
  • 9f15c58 update deps
  • Additional commits viewable in compare view

Updates `minimatch` from 9.0.5 to 9.0.9
Changelog

Sourced from minimatch's changelog.

change log

10.2

  • Add braceExpandMax option

10.1

  • Add magicalBraces option for escape
  • Fix makeRe when partial: true is set.
  • Fix makeRe when pattern ends in a final ** path part.

10.0

  • Require node 20 or 22 and higher

9.0

  • No default export, only named exports.

8.0

  • Recursive descent parser for extglob, allowing correct support for arbitrarily nested extglob expressions
  • Bump required Node.js version

7.4

  • Add escape() method
  • Add unescape() method
  • Add Minimatch.hasMagic() method

7.3

  • Add support for posix character classes in a unicode-aware way.

7.2

  • Add windowsNoMagicRoot option

7.1

  • Add optimizationLevel configuration option, and revert the default back to the 6.2 style minimal optimizations, making the advanced transforms introduced in 7.0 opt-in. Also, process provided file paths in the same way in optimizationLevel:2 mode, so most things that matched with optimizationLevel 1 or 0 should match with level 2 as well. However, level 1 is the default, out of an abundance of caution.

... (truncated)

Commits
  • c36addb 10.2.4
  • 26b9002 docs: add warning about ReDoS
  • 3a0d83b fix partial matching of globstar patterns
  • ea94840 10.2.3
  • 0873fba update deps
  • cecaad1 more extglob coalescing for performance
  • 11d0df6 limit nested extglob recursion, flatten extglobs
  • c3448c4 update assertValidPattern param type to unknown from any
  • 0bf499a limit recursion for **, improve perf considerably
  • 9f15c58 update deps
  • Additional commits viewable in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/RageAgainstThePixel/unity-cli/network/alerts).
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Stephen Hodgson --- package-lock.json | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index c746989a..a8ac695a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5002,6 +5002,27 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/minimatch/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/minimatch/node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", @@ -5759,9 +5780,9 @@ } }, "node_modules/tar": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.9.tgz", - "integrity": "sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg==", + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.8.tgz", + "integrity": "sha512-SYkBtK99u0yXa+IWL0JRzzcl7RxNpvX/U08Z+8DKnysfno7M+uExnTZH8K+VGgShf2qFPKtbNr9QBl8n7WBP6Q==", "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/fs-minipass": "^4.0.0", From 3bd4dabcedd561e2620265896722725c239493db Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sat, 28 Feb 2026 12:24:09 -0500 Subject: [PATCH 54/56] move utp.ts back into root src/ --- src/logging.ts | 2 +- src/unity-logging.ts | 10 ++++++++-- src/{utp => }/utp.ts | 0 3 files changed, 9 insertions(+), 3 deletions(-) rename src/{utp => }/utp.ts (100%) diff --git a/src/logging.ts b/src/logging.ts index 7750699c..080f0b8b 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -1,5 +1,5 @@ import * as fs from 'fs'; -import { UTP, Severity } from './utp/utp'; +import { UTP, Severity } from './utp'; const TRUNCATE_MSG = 120; const SUMMARY_BYTE_LIMIT = 1024 * 1024; diff --git a/src/unity-logging.ts b/src/unity-logging.ts index 044f3568..ebdb0be8 100644 --- a/src/unity-logging.ts +++ b/src/unity-logging.ts @@ -1,6 +1,12 @@ import * as fs from 'fs'; import * as path from 'path'; -import { LogLevel, Logger, buildTestResultsTableMarkdown, TestResultSummary, utpToTestResultSummary } from './logging'; +import { + LogLevel, + Logger, + buildTestResultsTableMarkdown, + TestResultSummary, + utpToTestResultSummary +} from './logging'; import { Delay, WaitForFileToBeUnlocked } from './utilities'; import { Phase, @@ -10,7 +16,7 @@ import { UTPMemoryLeak, UTPPlayerBuildInfo, normalizeTelemetryEntry -} from './utp/utp'; +} from './utp'; /** * Result of the tailLogFile function containing cleanup resources. diff --git a/src/utp/utp.ts b/src/utp.ts similarity index 100% rename from src/utp/utp.ts rename to src/utp.ts From b7021b0f4c7e15ef4c080171ad499d3bcdf25602 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sat, 28 Feb 2026 12:24:36 -0500 Subject: [PATCH 55/56] bump deps --- package-lock.json | 59 +++++++++++++++++++++++++---------------------- package.json | 2 +- 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/package-lock.json b/package-lock.json index a8ac695a..e4f1d43d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ }, "devDependencies": { "@types/jest": "^30.0.0", - "@types/node": "^24.10.15", + "@types/node": "^24.11.0", "@types/semver": "^7.7.1", "@types/update-notifier": "^6.0.8", "jest": "^30.2.0", @@ -1667,9 +1667,9 @@ } }, "node_modules/@types/node": { - "version": "24.10.15", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.15.tgz", - "integrity": "sha512-BgjLoRuSr0MTI5wA6gMw9Xy0sFudAaUuvrnjgGx9wZ522fYYLA5SYJ+1Y30vTcJEG+DRCyDHx/gzQVfofYzSdg==", + "version": "24.11.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.11.0.tgz", + "integrity": "sha512-fPxQqz4VTgPI/IQ+lj9r0h+fDR66bzoeMGHp8ASee+32OSGIkeASsoZuJixsQoVef1QJbeubcPBxKk22QVoWdw==", "dev": true, "license": "MIT", "dependencies": { @@ -1831,6 +1831,9 @@ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1845,6 +1848,9 @@ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1859,6 +1865,9 @@ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1873,6 +1882,9 @@ "riscv64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1887,6 +1899,9 @@ "riscv64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1901,6 +1916,9 @@ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1915,6 +1933,9 @@ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1929,6 +1950,9 @@ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -5002,27 +5026,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/minimatch/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/minimatch/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", @@ -5780,9 +5783,9 @@ } }, "node_modules/tar": { - "version": "7.5.8", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.8.tgz", - "integrity": "sha512-SYkBtK99u0yXa+IWL0JRzzcl7RxNpvX/U08Z+8DKnysfno7M+uExnTZH8K+VGgShf2qFPKtbNr9QBl8n7WBP6Q==", + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.9.tgz", + "integrity": "sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg==", "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/fs-minipass": "^4.0.0", diff --git a/package.json b/package.json index 5916d25f..7bd3d7ee 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ }, "devDependencies": { "@types/jest": "^30.0.0", - "@types/node": "^24.10.15", + "@types/node": "^24.11.0", "@types/semver": "^7.7.1", "@types/update-notifier": "^6.0.8", "jest": "^30.2.0", From c470a2d41f377677335e0ba84d659ef556bba842 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sat, 28 Feb 2026 12:24:58 -0500 Subject: [PATCH 56/56] fix import --- src/utp.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utp.ts b/src/utp.ts index 446f2c8c..6252814d 100644 --- a/src/utp.ts +++ b/src/utp.ts @@ -1,4 +1,4 @@ -import { Logger } from "../logging"; +import { Logger } from "./logging"; export class UTPBase { type?: string;