From 0271e6ce039afbb68a2830a28d819d07fd2aab1d Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Wed, 5 Nov 2025 18:01:44 -0500 Subject: [PATCH 01/10] unity-cli@v1.5.4 - Add unity telemetry log parsing for GitHub Actions - fix unit test failure due to missing asar.uncacheAll mock --- package-lock.json | 4 +-- package.json | 4 +-- src/logging.ts | 68 ++++++++++++++++++++++++++++++++++++ src/utilities.ts | 46 ++++++++++++++++++------ tests/mocks/electron-asar.js | 11 +++--- 5 files changed, 114 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index 281a4bd..4df758e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@rage-against-the-pixel/unity-cli", - "version": "1.5.3", + "version": "1.5.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@rage-against-the-pixel/unity-cli", - "version": "1.5.3", + "version": "1.5.4", "license": "MIT", "dependencies": { "@electron/asar": "^4.0.1", diff --git a/package.json b/package.json index 86dd47e..a28976b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@rage-against-the-pixel/unity-cli", - "version": "1.5.3", + "version": "1.5.4", "description": "A command line utility for the Unity Game Engine.", "author": "RageAgainstThePixel", "license": "MIT", @@ -68,4 +68,4 @@ "ts-node": "^10.9.2", "typescript": "^5.9.3" } -} +} \ No newline at end of file diff --git a/src/logging.ts b/src/logging.ts index 166143f..ab3f86f 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -136,6 +136,74 @@ export class Logger { this.log(LogLevel.ERROR, message, optionalParams); } + /** + * Annotates a file and line number in CI environments that support it. + * @param logLevel The level of the log. + * @param message The message to annotate. + * @param file The file to annotate. + * @param line The line number to annotate. + * @param endLine The end line number to annotate. + * @param column The column number to annotate. + * @param endColumn The end column number to annotate. + * @param title The title of the annotation. + */ + public annotate(logLevel: LogLevel, message: string, file?: string, line?: number, endLine?: number, column?: number, endColumn?: number, title?: string): void { + let annotation = ''; + + switch (this._ci) { + case 'GITHUB_ACTIONS': { + var level: string; + switch (logLevel) { + case LogLevel.CI: + case LogLevel.INFO: + case LogLevel.DEBUG: { + level = 'notice'; + break; + } + case LogLevel.WARN: { + level = 'warning'; + break; + } + case LogLevel.ERROR: { + level = 'error'; + break; + } + } + + let parts: string[] = []; + + if (file !== undefined && file.length > 0) { + parts.push(`file=${file}`); + } + + if (line !== undefined && line > 0) { + parts.push(`line=${line}`); + } + + if (endLine !== undefined && endLine > 0) { + parts.push(`endLine=${endLine}`); + } + + if (column !== undefined && column > 0) { + parts.push(`col=${column}`); + } + + if (endColumn !== undefined && endColumn > 0) { + parts.push(`endColumn=${endColumn}`); + } + + if (title !== undefined && title.length > 0) { + parts.push(`title=${title}`); + } + + annotation = `::${level} ${parts.join(',')}::${message}`; + break; + } + } + + process.stdout.write(`${annotation}\n`); + } + private shouldLog(level: LogLevel): boolean { if (level === LogLevel.CI) { return true; } const levelOrder = [LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARN, LogLevel.ERROR]; diff --git a/src/utilities.ts b/src/utilities.ts index 63c1c8e..5d29392 100644 --- a/src/utilities.ts +++ b/src/utilities.ts @@ -334,6 +334,8 @@ export interface LogTailResult { tailPromise: Promise; /** Function to signal that log tailing should end */ stopLogTail: () => void; + /** Collected telemetry objects parsed from lines beginning with '##utp:' */ + telemetry: any[]; } /** @@ -345,18 +347,13 @@ export function TailLogFile(logPath: string): LogTailResult { let logEnded = false; let lastSize = 0; const logPollingInterval = 250; + const telemetry: any[] = []; async function readNewLogContent(): Promise { try { - if (!fs.existsSync(logPath)) { - return; - } - + if (!fs.existsSync(logPath)) { return; } const stats = await fs.promises.stat(logPath); - - if (stats.size < lastSize) { - lastSize = 0; - } + if (stats.size < lastSize) { lastSize = 0; } if (stats.size > lastSize) { const bytesToRead = stats.size - lastSize; @@ -375,12 +372,40 @@ export function TailLogFile(logPath: string): LogTailResult { if (bytesToRead > 0) { const chunk = buffer.toString('utf8'); + // Parse telemetry lines in this chunk (lines starting with '##utp:') try { - process.stdout.write(chunk); + const lines = chunk.split(/\r?\n/); + for (const rawLine of lines) { + const line = rawLine.trim(); + if (!line) { continue; } + if (line.startsWith('##utp:')) { + const jsonPart = line.substring('##utp:'.length).trim(); + try { + const utp = JSON.parse(jsonPart); + telemetry.push(utp); + + // annotate the log with the telemetry event + // ##utp:{"type":"Compiler","version":2,"phase":"Immediate","time":1762378495689,"processId":2256,"severity":"Error","message":"Assets\\_BurnerSphere\\Content\\Common Assets\\Lighting\\older\\ReflectionProbeBaker1.cs(75,13): error CS0103: The name 'AssetDatabase' does not exist in the current context","stacktrace":"","line":75,"file":"Assets\\_BurnerSphere\\Content\\Common Assets\\Lighting\\older\\ReflectionProbeBaker1.cs"} + if (utp.severity && utp.severity.toLowerCase() === 'error') { + const file = utp.file ? utp.file.replace(/\\/g, '/') : undefined; + const lineNum = utp.line ? utp.line : undefined; + const message = utp.message; + if (!message.startsWith(`\n::error::\u001B[31m`)) { // indicates a duplicate annotation + Logger.instance.annotate(LogLevel.ERROR, message, file, lineNum); + } + } + } catch (error) { + logger.warn(`Failed to parse telemetry JSON: ${error} -- raw: ${jsonPart}`); + } + } else { + process.stdout.write(`${line}\n`); + } + } } catch (error: any) { if (error.code !== 'EPIPE') { throw error; } + logger.warn(`Error while parsing telemetry from log chunk: ${error}`); } } } @@ -402,6 +427,7 @@ export function TailLogFile(logPath: string): LogTailResult { await readNewLogContent(); try { + // write a final newline to separate log output process.stdout.write('\n'); } catch (error: any) { if (error.code !== 'EPIPE') { @@ -420,7 +446,7 @@ export function TailLogFile(logPath: string): LogTailResult { logEnded = true; } - return { tailPromise, stopLogTail }; + return { tailPromise, stopLogTail, telemetry }; } /** diff --git a/tests/mocks/electron-asar.js b/tests/mocks/electron-asar.js index 8a83d2b..ac4b15b 100644 --- a/tests/mocks/electron-asar.js +++ b/tests/mocks/electron-asar.js @@ -1,9 +1,10 @@ // Minimal mock for @electron/asar used in tests module.exports = { - extractFile: (asarPath, file) => { - if (file === 'package.json') { - return Buffer.from(JSON.stringify({ version: '1.0.0' })); - } - return Buffer.from(''); + extractFile: (asarPath, file) => { + if (file === 'package.json') { + return Buffer.from(JSON.stringify({ version: '1.0.0' })); } + return Buffer.from(''); + }, + uncacheAll: () => { }, }; From fa2baa38957df12da55c5ef572bff21f99976f78 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Wed, 5 Nov 2025 21:04:39 -0500 Subject: [PATCH 02/10] always clean space on linux --- .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 7189e69..775203b 100644 --- a/.github/workflows/unity-build.yml +++ b/.github/workflows/unity-build.yml @@ -26,7 +26,7 @@ jobs: RUN_BUILD: '' # Set to true if the build pipeline package can be installed and used steps: - name: Free Disk Space - if: ${{ matrix.os == 'ubuntu-latest' && matrix.unity-version == '6000.2' }} + if: ${{ matrix.os == 'ubuntu-latest' }} uses: endersonmenezes/free-disk-space@713d134e243b926eba4a5cce0cf608bfd1efb89a # v2.1.1 with: remove_android: true From 339a5b7de46898d1ebdc563975a67bdd00253e93 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Wed, 5 Nov 2025 21:09:38 -0500 Subject: [PATCH 03/10] add stack trace to the message --- src/utilities.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utilities.ts b/src/utilities.ts index 5d29392..5267d89 100644 --- a/src/utilities.ts +++ b/src/utilities.ts @@ -390,8 +390,9 @@ export function TailLogFile(logPath: string): LogTailResult { const file = utp.file ? utp.file.replace(/\\/g, '/') : undefined; const lineNum = utp.line ? utp.line : undefined; const message = utp.message; + const stacktrace = utp.stacktrace ? `${utp.stacktrace}` : undefined; if (!message.startsWith(`\n::error::\u001B[31m`)) { // indicates a duplicate annotation - Logger.instance.annotate(LogLevel.ERROR, message, file, lineNum); + Logger.instance.annotate(LogLevel.ERROR, stacktrace == undefined ? message : `${message}\n${stacktrace}`, file, lineNum); } } } catch (error) { From b422a69543fe97c6d74c7f43344265504ebec196 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Wed, 5 Nov 2025 21:14:27 -0500 Subject: [PATCH 04/10] bump action versions --- .github/workflows/unity-build.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/unity-build.yml b/.github/workflows/unity-build.yml index 775203b..51ab935 100644 --- a/.github/workflows/unity-build.yml +++ b/.github/workflows/unity-build.yml @@ -33,7 +33,7 @@ jobs: remove_dotnet: false remove_tool_cache: false - uses: actions/checkout@v5 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: node-version: 24.x - name: Setup unity-cli @@ -140,9 +140,10 @@ jobs: unity-cli return-license --license personal - name: Upload Logs if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: ${{ github.run_id }}.${{ github.run_attempt }} ${{ matrix.os }} ${{ matrix.unity-version }} ${{ matrix.build-target }} logs retention-days: 1 + if-no-files-found: ignore path: | ${{ github.workspace }}/**/*.log From e8d0363a016cdbd79dd1f63d35e24d2a7dd2c74a Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Wed, 5 Nov 2025 21:14:42 -0500 Subject: [PATCH 05/10] bump action versions --- .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 e5d89cb..924d58d 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -10,7 +10,7 @@ jobs: contents: read steps: - uses: actions/checkout@v5 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: node-version: 24.x registry-url: "https://registry.npmjs.org" From 9c7b7ccf2fb329e8ce6e7d5f824d15ae2e70d6a4 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Wed, 5 Nov 2025 21:23:33 -0500 Subject: [PATCH 06/10] try to init dummy fmod audio drivers to reduce error logs --- src/unity-editor.ts | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/unity-editor.ts b/src/unity-editor.ts index 6aa6d12..435fe66 100644 --- a/src/unity-editor.ts +++ b/src/unity-editor.ts @@ -272,15 +272,26 @@ export class UnityEditor { if (process.platform === 'linux' && !command.args.includes('-nographics') ) { + // On Linux, force Unity to run under Xvfb and provide a dummy audio driver + // to prevent FMOD from failing to initialize the output device when no + // actual audio device is present (common in CI/container environments). + const linuxEnv = { + ...process.env, + DISPLAY: ':99', + UNITY_THISISABUILDMACHINE: '1', + // Tell various audio systems to use a dummy/out-of-process driver + SDL_AUDIODRIVER: process.env.SDL_AUDIODRIVER || 'dummy', + AUDIODRIVER: process.env.AUDIODRIVER || 'dummy', + AUDIODEV: process.env.AUDIODEV || 'null', + // For PulseAudio: point to an invalid socket to avoid connecting + PULSE_SERVER: process.env.PULSE_SERVER || '/tmp/invalid-pulse-socket' + }; + unityProcess = spawn( 'xvfb-run', [this.editorPath, ...command.args], { stdio: ['ignore', 'ignore', 'ignore'], - env: { - ...process.env, - DISPLAY: ':99', - UNITY_THISISABUILDMACHINE: '1' - } + env: linuxEnv }); } else if (process.arch === 'arm64' && process.platform === 'darwin' && From 0266d3eb838dadf2e95e8e3852137f0eabac8cfe Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Thu, 6 Nov 2025 12:21:57 -0500 Subject: [PATCH 07/10] init ALSA write telemetry to summary --- src/logging.ts | 18 ++++++++++++++++++ src/unity-editor.ts | 19 ++++++++++++++----- src/unity-hub.ts | 4 ++-- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/src/logging.ts b/src/logging.ts index ab3f86f..e011690 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -255,4 +255,22 @@ export class Logger { } } } + + + public CI_appendWorkflowSummary(telemetry: any[]) { + 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`; + }); + + fs.writeFileSync(githubSummary, table, { encoding: 'utf8' }); + } + } + } + } } \ No newline at end of file diff --git a/src/unity-editor.ts b/src/unity-editor.ts index 435fe66..233059a 100644 --- a/src/unity-editor.ts +++ b/src/unity-editor.ts @@ -269,9 +269,7 @@ export class UnityEditor { throw new Error(`Cannot execute Unity ${this.version.toString()} on Apple Silicon Macs.`); } - if (process.platform === 'linux' && - !command.args.includes('-nographics') - ) { + if (process.platform === 'linux') { // On Linux, force Unity to run under Xvfb and provide a dummy audio driver // to prevent FMOD from failing to initialize the output device when no // actual audio device is present (common in CI/container environments). @@ -287,9 +285,20 @@ export class UnityEditor { PULSE_SERVER: process.env.PULSE_SERVER || '/tmp/invalid-pulse-socket' }; + var linuxCommand = ''; + var linuxArgs = []; + + if (!command.args.includes('-nographics')) { + linuxCommand = 'xvfb-run'; + linuxArgs = [this.editorPath, ...command.args]; + } else { + linuxCommand = this.editorPath; + linuxArgs = command.args; + } + unityProcess = spawn( - 'xvfb-run', - [this.editorPath, ...command.args], { + linuxCommand, + linuxArgs, { stdio: ['ignore', 'ignore', 'ignore'], env: linuxEnv }); diff --git a/src/unity-hub.ts b/src/unity-hub.ts index 5c8510d..76826a2 100644 --- a/src/unity-hub.ts +++ b/src/unity-hub.ts @@ -337,7 +337,7 @@ set -e wget -qO - https://hub.unity3d.com/linux/keys/public | gpg --dearmor | sudo tee /usr/share/keyrings/Unity_Technologies_ApS.gpg >/dev/null 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 : ''}`]); + sudo apt-get install -y --no-install-recommends --only-upgrade unityhub${version ? '=' + version : ''} libasound2 alsa-utils`]); } else { throw new Error(`Unsupported platform: ${process.platform}`); } @@ -439,7 +439,7 @@ wget -qO - https://hub.unity3d.com/linux/keys/public | gpg --dearmor | tee /usr/ 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 echo "deb https://archive.ubuntu.com/ubuntu jammy main universe" | tee /etc/apt/sources.list.d/jammy.list apt-get update -apt-get install -y --no-install-recommends unityhub${version ? '=' + version : ''} ffmpeg libgtk2.0-0 libglu1-mesa libgconf-2-4 libncurses5 +apt-get install -y --no-install-recommends unityhub${version ? '=' + version : ''} ffmpeg libgtk2.0-0 libglu1-mesa libgconf-2-4 libncurses5 libasound2 alsa-utils apt-get clean sed -i 's/^\\(.*DISPLAY=:.*XAUTHORITY=.*\\)\\( "\\$@" \\)2>&1$/\\1\\2/' /usr/bin/xvfb-run printf '#!/bin/bash\nxvfb-run --auto-servernum /opt/unityhub/unityhub "$@" 2>/dev/null' | tee /usr/bin/unity-hub >/dev/null From 561392bee1b76a6f3d768c2fa867ee93dce24115 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Thu, 6 Nov 2025 16:50:08 -0500 Subject: [PATCH 08/10] append summary --- README.md | 2 +- src/logging.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b4d471b..7e7b391 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ npm install -g @rage-against-the-pixel/unity-cli In general, the command structure is: ```bash -unity-cli [command] [options] +unity-cli [command] {options} ``` With options always using double dashes (`--option`) and arguments passed directly to Unity or Unity Hub commands as they normally would with single dashes (`-arg`). Each option typically has a short alias using a single dash (`-o`), except for commands where we pass through arguments, as those get confused by the command parser. diff --git a/src/logging.ts b/src/logging.ts index e011690..2599cd5 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -256,7 +256,6 @@ export class Logger { } } - public CI_appendWorkflowSummary(telemetry: any[]) { switch (this._ci) { case 'GITHUB_ACTIONS': { @@ -268,7 +267,7 @@ export class Logger { table += `| ${item.key} | ${item.value} |\n`; }); - fs.writeFileSync(githubSummary, table, { encoding: 'utf8' }); + fs.appendFileSync(githubSummary, table, { encoding: 'utf8' }); } } } From 031348b100a4859f120da295650c1082bcedc52a Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Thu, 6 Nov 2025 20:07:15 -0500 Subject: [PATCH 09/10] reverts --- src/unity-editor.ts | 19 +++++-------------- src/unity-hub.ts | 4 ++-- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/src/unity-editor.ts b/src/unity-editor.ts index 233059a..435fe66 100644 --- a/src/unity-editor.ts +++ b/src/unity-editor.ts @@ -269,7 +269,9 @@ export class UnityEditor { throw new Error(`Cannot execute Unity ${this.version.toString()} on Apple Silicon Macs.`); } - if (process.platform === 'linux') { + if (process.platform === 'linux' && + !command.args.includes('-nographics') + ) { // On Linux, force Unity to run under Xvfb and provide a dummy audio driver // to prevent FMOD from failing to initialize the output device when no // actual audio device is present (common in CI/container environments). @@ -285,20 +287,9 @@ export class UnityEditor { PULSE_SERVER: process.env.PULSE_SERVER || '/tmp/invalid-pulse-socket' }; - var linuxCommand = ''; - var linuxArgs = []; - - if (!command.args.includes('-nographics')) { - linuxCommand = 'xvfb-run'; - linuxArgs = [this.editorPath, ...command.args]; - } else { - linuxCommand = this.editorPath; - linuxArgs = command.args; - } - unityProcess = spawn( - linuxCommand, - linuxArgs, { + 'xvfb-run', + [this.editorPath, ...command.args], { stdio: ['ignore', 'ignore', 'ignore'], env: linuxEnv }); diff --git a/src/unity-hub.ts b/src/unity-hub.ts index 76826a2..5c8510d 100644 --- a/src/unity-hub.ts +++ b/src/unity-hub.ts @@ -337,7 +337,7 @@ set -e wget -qO - https://hub.unity3d.com/linux/keys/public | gpg --dearmor | sudo tee /usr/share/keyrings/Unity_Technologies_ApS.gpg >/dev/null 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 : ''} libasound2 alsa-utils`]); +sudo apt-get install -y --no-install-recommends --only-upgrade unityhub${version ? '=' + version : ''}`]); } else { throw new Error(`Unsupported platform: ${process.platform}`); } @@ -439,7 +439,7 @@ wget -qO - https://hub.unity3d.com/linux/keys/public | gpg --dearmor | tee /usr/ 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 echo "deb https://archive.ubuntu.com/ubuntu jammy main universe" | tee /etc/apt/sources.list.d/jammy.list apt-get update -apt-get install -y --no-install-recommends unityhub${version ? '=' + version : ''} ffmpeg libgtk2.0-0 libglu1-mesa libgconf-2-4 libncurses5 libasound2 alsa-utils +apt-get install -y --no-install-recommends unityhub${version ? '=' + version : ''} ffmpeg libgtk2.0-0 libglu1-mesa libgconf-2-4 libncurses5 apt-get clean sed -i 's/^\\(.*DISPLAY=:.*XAUTHORITY=.*\\)\\( "\\$@" \\)2>&1$/\\1\\2/' /usr/bin/xvfb-run printf '#!/bin/bash\nxvfb-run --auto-servernum /opt/unityhub/unityhub "$@" 2>/dev/null' | tee /usr/bin/unity-hub >/dev/null From 6acc591c91dcb083b2be81b613a44b8ce517d52d Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Sat, 8 Nov 2025 21:04:03 -0500 Subject: [PATCH 10/10] add Completed with errors edge case --- src/unity-hub.ts | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/unity-hub.ts b/src/unity-hub.ts index 5c8510d..ba7cc67 100644 --- a/src/unity-hub.ts +++ b/src/unity-hub.ts @@ -108,9 +108,13 @@ export class UnityHub { try { exitCode = await new Promise((resolve, reject) => { let isSettled: boolean = false; // Has the promise been settled (resolved or rejected)? - let isHubTaskComplete: boolean = false; // Has the Unity Hub tasks completed successfully? + let isHubTaskCompleteSuccess: boolean = false; // Has the Unity Hub tasks completed successfully? + let isHubTaskCompleteFailed: boolean = false; // Has the Unity Hub tasks completed with failure? let lineBuffer = ''; // Buffer for incomplete lines - const tasksCompleteMessage = 'All Tasks Completed Successfully.'; + const tasksCompleteMessages: string[] = [ + 'All Tasks Completed Successfully.', + 'Completed with errors.' + ]; const child = spawn(executable, execArgs, { stdio: ['ignore', 'pipe', 'pipe'] }); @@ -140,8 +144,9 @@ export class UnityHub { lineBuffer = ''; } - if (lines.includes(tasksCompleteMessage)) { - isHubTaskComplete = true; + if (lines.some(line => tasksCompleteMessages.includes(line))) { + isHubTaskCompleteSuccess = lines.includes('All Tasks Completed Successfully.'); + isHubTaskCompleteFailed = lines.includes('Completed with errors.'); if (child?.pid) { try { @@ -159,7 +164,13 @@ export class UnityHub { } catch { // Ignore, process may have already exited } finally { - settle(0); + if (isHubTaskCompleteSuccess) { + settle(0); + } else if (isHubTaskCompleteFailed) { + settle(1); + } else { + settle(null); + } } } } @@ -186,8 +197,9 @@ export class UnityHub { lineBuffer = ''; const outputLines = lines.filter(line => !ignoredLines.some(ignored => line.includes(ignored))); - if (outputLines.includes(tasksCompleteMessage)) { - isHubTaskComplete = true; + if (outputLines.some(line => tasksCompleteMessages.includes(line))) { + isHubTaskCompleteSuccess = outputLines.includes('All Tasks Completed Successfully.'); + isHubTaskCompleteFailed = outputLines.includes('Completed with errors.'); } for (const line of outputLines) { @@ -210,12 +222,7 @@ export class UnityHub { isSettled = true; removeListeners(); flushOutput(); - - if (isHubTaskComplete) { - resolve(0); - } else { - resolve(code === null ? 0 : code); - } + resolve(code === null ? 0 : code); } child.stdout.on('data', processOutput);