diff --git a/Assets/Tests/Editor/CliInstallProgressFormattingTests.cs b/Assets/Tests/Editor/CliInstallProgressFormattingTests.cs new file mode 100644 index 000000000..ba0216758 --- /dev/null +++ b/Assets/Tests/Editor/CliInstallProgressFormattingTests.cs @@ -0,0 +1,94 @@ +using System; + +using NUnit.Framework; + +using io.github.hatayama.UnityCliLoop.Presentation; + +namespace io.github.hatayama.UnityCliLoop.Tests.Editor +{ + /// + /// Test fixture that verifies CLI install progress text formatting. + /// + public class CliInstallProgressFormattingTests + { + [Test] + public void FormatStatusLine_WhenAnimationStepZero_UsesOneVisibleDotWithTransparentPadding() + { + // Verifies one visible dot keeps three-dot width via transparent rich-text padding. + string result = CliInstallProgressFormatting.FormatStatusLine(TimeSpan.Zero, 0); + + Assert.That(result, Is.EqualTo("Installing... (0s)")); + } + + [Test] + public void FormatStatusLine_WhenAnimationStepOne_UsesTwoVisibleDotsWithTransparentPadding() + { + // Verifies two visible dots keep three-dot width via one transparent padding dot. + string result = CliInstallProgressFormatting.FormatStatusLine(TimeSpan.Zero, 1); + + Assert.That(result, Is.EqualTo("Installing... (0s)")); + } + + [Test] + public void FormatStatusLine_WhenAnimationStepTwo_UsesThreeVisibleDotsWithoutPadding() + { + // Verifies three visible dots need no transparent padding and match the full status form. + string result = CliInstallProgressFormatting.FormatStatusLine(TimeSpan.Zero, 2); + + Assert.That(result, Is.EqualTo("Installing... (0s)")); + } + + [Test] + public void FormatStatusLine_WhenUnderOneMinute_ReturnsSecondsOnly() + { + // Verifies sub-minute elapsed time stays in the seconds-only format. + string result = CliInstallProgressFormatting.FormatStatusLine(TimeSpan.FromSeconds(59), 2); + + Assert.That(result, Is.EqualTo("Installing... (59s)")); + } + + [Test] + public void FormatStatusLine_WhenOverOneMinute_ReturnsMinutesAndSeconds() + { + // Verifies minute formatting pads seconds to two digits with fixed three-dot width. + string result = CliInstallProgressFormatting.FormatStatusLine(TimeSpan.FromSeconds(65), 2); + + Assert.That(result, Is.EqualTo("Installing... (1m 05s)")); + } + + [Test] + public void FormatStatusLine_WhenExactTensOfMinutes_PadsSeconds() + { + // Verifies exact minute boundaries still show zero-padded seconds. + string result = CliInstallProgressFormatting.FormatStatusLine(TimeSpan.FromSeconds(600), 2); + + Assert.That(result, Is.EqualTo("Installing... (10m 00s)")); + } + + [Test] + public void FormatStatusLine_WhenAnimationStepThree_LoopsBackToOneVisibleDot() + { + // Verifies animationStep wraps every three steps so the visible-dot cycle repeats. + string result = CliInstallProgressFormatting.FormatStatusLine(TimeSpan.Zero, 3); + + Assert.That(result, Is.EqualTo("Installing... (0s)")); + } + + [Test] + public void FormatDetailLine_WhenNullOrWhitespace_ReturnsEmpty() + { + // Verifies blank installer lines do not update the detail label. + Assert.That(CliInstallProgressFormatting.FormatDetailLine(null), Is.EqualTo(string.Empty)); + Assert.That(CliInstallProgressFormatting.FormatDetailLine(" "), Is.EqualTo(string.Empty)); + } + + [Test] + public void FormatDetailLine_WhenPadded_TrimsWhitespace() + { + // Verifies surrounding whitespace is stripped from streamed installer output. + string result = CliInstallProgressFormatting.FormatDetailLine(" Downloading uloop "); + + Assert.That(result, Is.EqualTo("Downloading uloop")); + } + } +} diff --git a/Assets/Tests/Editor/CliInstallProgressFormattingTests.cs.meta b/Assets/Tests/Editor/CliInstallProgressFormattingTests.cs.meta new file mode 100644 index 000000000..8ff1a36f4 --- /dev/null +++ b/Assets/Tests/Editor/CliInstallProgressFormattingTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a031c32898d87449ea444797c52a1f11 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Tests/Editor/CliPathSetupFlowTests.cs b/Assets/Tests/Editor/CliPathSetupFlowTests.cs index f7a38ec29..8bccfeeac 100644 --- a/Assets/Tests/Editor/CliPathSetupFlowTests.cs +++ b/Assets/Tests/Editor/CliPathSetupFlowTests.cs @@ -1,3 +1,4 @@ +using System; using System.Threading; using System.Threading.Tasks; @@ -182,6 +183,7 @@ public Task InstallGlobalCliAsync( RuntimePlatform platform, string dispatcherReleaseTag, string dispatcherArchiveManifest, + IProgress installProgress, CancellationToken ct) { return Task.FromResult(new CliInstallResult(true, "")); diff --git a/Assets/Tests/Editor/CliSetupApplicationServiceTests.cs b/Assets/Tests/Editor/CliSetupApplicationServiceTests.cs index b73509028..5b2daaee1 100644 --- a/Assets/Tests/Editor/CliSetupApplicationServiceTests.cs +++ b/Assets/Tests/Editor/CliSetupApplicationServiceTests.cs @@ -1,3 +1,4 @@ +using System; using System.Threading; using System.Threading.Tasks; @@ -25,11 +26,14 @@ public async Task InstallGlobalCliAsync_UsesPinnedDispatcherReleaseTag() nativeCliInstaller, new CliPinReaderService()); - await service.InstallGlobalCliAsync(RuntimePlatform.OSXEditor, CancellationToken.None); + await service.InstallGlobalCliAsync( + RuntimePlatform.OSXEditor, + new Progress(), + CancellationToken.None); Assert.That( nativeCliInstaller.InstalledVersion, - Is.EqualTo("dispatcher-v3.0.1-beta.6")); + Is.EqualTo(ExpectedDispatcherReleaseTag())); } [Test] @@ -61,7 +65,7 @@ public void GetGlobalCliInstallCommand_UsesPinnedDispatcherReleaseTag() Assert.That(commandResult.Success, Is.True, commandResult.ErrorOutput); Assert.That( commandResult.Command.ManualCommand, - Is.EqualTo("install dispatcher-v3.0.1-beta.6")); + Is.EqualTo($"install {ExpectedDispatcherReleaseTag()}")); } [Test] @@ -74,7 +78,10 @@ public async Task InstallGlobalCliAsync_WhenBootstrapPinIsUnavailableFailsWithou nativeCliInstaller, new FailingBootstrapPinReader()); - CliInstallResult result = await service.InstallGlobalCliAsync(RuntimePlatform.OSXEditor, CancellationToken.None); + CliInstallResult result = await service.InstallGlobalCliAsync( + RuntimePlatform.OSXEditor, + new Progress(), + CancellationToken.None); Assert.That(result.Success, Is.False); Assert.That(result.ErrorOutput, Does.Contain("bootstrap pin")); @@ -88,6 +95,13 @@ private static string ExpectedMinimumDispatcherVersion() return result.Pin.MinimumDispatcherVersion; } + private static string ExpectedDispatcherReleaseTag() + { + DispatcherBootstrapPinLoadResult result = new CliPinReaderService().LoadDispatcherBootstrapPin(); + Assert.That(result.Success, Is.True, result.ErrorMessage); + return result.DispatcherReleaseTag; + } + private sealed class FakeCliInstallationDetector : ICliInstallationDetector { private readonly string[] _versions; @@ -158,6 +172,7 @@ public Task InstallGlobalCliAsync( RuntimePlatform platform, string dispatcherReleaseTag, string dispatcherArchiveManifest, + IProgress installProgress, CancellationToken ct) { InstalledVersion = dispatcherReleaseTag; diff --git a/Assets/Tests/Editor/CliSetupSectionTests.cs b/Assets/Tests/Editor/CliSetupSectionTests.cs index d5e39584c..0bb74f313 100644 --- a/Assets/Tests/Editor/CliSetupSectionTests.cs +++ b/Assets/Tests/Editor/CliSetupSectionTests.cs @@ -170,6 +170,9 @@ private static VisualElement CreateRootElement() root.Add(new Label { name = "cli-status-label" }); root.Add(new Button { name = "refresh-cli-version-button" }); root.Add(new Button { name = "install-cli-button" }); + VisualElement installProgress = new() { name = "cli-install-progress" }; + installProgress.Add(new Label { name = "cli-install-progress-label" }); + root.Add(installProgress); root.Add(new EnumField { name = "skills-target-field" }); root.Add(new Button { name = "refresh-skills-state-button" }); root.Add(new VisualElement { name = "group-skills-row" }); diff --git a/Assets/Tests/Editor/NativeCliInstallerTests.cs b/Assets/Tests/Editor/NativeCliInstallerTests.cs index c92884b9e..60aacbb0a 100644 --- a/Assets/Tests/Editor/NativeCliInstallerTests.cs +++ b/Assets/Tests/Editor/NativeCliInstallerTests.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -301,7 +302,8 @@ public void RunInstallCommand_WhenInstallerExecutableIsMissingReturnsFailure() CliInstallResult result = NativeCliSetupCommandRunner.RunInstallCommand( command, CancellationToken.None, - 1000); + 1000, + static _ => { }); Assert.That(result.Success, Is.False); Assert.That(result.ErrorOutput, Does.Contain("Failed to start release CLI installer")); @@ -316,12 +318,41 @@ public void RunInstallCommand_WhenInstallerDoesNotExitReturnsFailure() CliInstallResult result = NativeCliSetupCommandRunner.RunInstallCommand( command, CancellationToken.None, - 50); + 50, + static _ => { }); Assert.That(result.Success, Is.False); Assert.That(result.ErrorOutput, Does.Contain("timed out")); } + [Test] + public void RunInstallCommand_StreamsOutputLinesToCallback() + { + // Verifies that installer stdout and stderr lines reach the progress callback so the editor UI can stream them. + NativeCliInstallCommand command = BuildEchoInstallCommand(); + List receivedLines = new(); + + CliInstallResult result = NativeCliSetupCommandRunner.RunInstallCommand( + command, + CancellationToken.None, + 5000, + line => + { + lock (receivedLines) + { + receivedLines.Add(line); + } + }); + + Assert.That(result.Success, Is.True, result.ErrorOutput); + lock (receivedLines) + { + Assert.That(receivedLines, Does.Contain("line1")); + Assert.That(receivedLines, Does.Contain("line2")); + Assert.That(receivedLines, Does.Contain("err1")); + } + } + [Test] public void RunUninstallCommand_WhenCanceledReportsUninstallCommand() { @@ -445,6 +476,22 @@ private static NativeCliInstallCommand BuildLongRunningInstallCommand() "sleep 5"); } + private static NativeCliInstallCommand BuildEchoInstallCommand() + { + if (UnityEngine.Application.platform == RuntimePlatform.WindowsEditor) + { + return new NativeCliInstallCommand( + "powershell", + "-NoProfile -ExecutionPolicy Bypass -Command \"Write-Output 'line1'; Write-Output 'line2'; [Console]::Error.WriteLine('err1')\"", + "Write-Output line1; Write-Output line2; [Console]::Error.WriteLine('err1')"); + } + + return new NativeCliInstallCommand( + "/bin/sh", + "-c \"echo line1; echo line2; echo err1 1>&2\"", + "echo line1; echo line2; echo err1 1>&2"); + } + [Test] public void BuildPathWithInstallDirectory_OnWindowsMovesExistingNativeInstallDirToFront() { diff --git a/Packages/src/Editor/Application/CliSetupApplicationService.cs b/Packages/src/Editor/Application/CliSetupApplicationService.cs index 80b5cfc17..a7ed8b9d6 100644 --- a/Packages/src/Editor/Application/CliSetupApplicationService.cs +++ b/Packages/src/Editor/Application/CliSetupApplicationService.cs @@ -80,6 +80,7 @@ Task InstallGlobalCliAsync( RuntimePlatform platform, string dispatcherReleaseTag, string dispatcherArchiveManifest, + IProgress installProgress, CancellationToken ct); Task UninstallGlobalCliAsync(RuntimePlatform platform, CancellationToken ct); Task GetGlobalCliPathSetupPlanAsync(RuntimePlatform platform, CancellationToken ct); @@ -198,8 +199,12 @@ public bool IsCliVersionEqual(string leftVersion, string rightVersion) return CliVersionComparer.IsVersionEqual(leftVersion, rightVersion); } - public async Task InstallGlobalCliAsync(RuntimePlatform platform, CancellationToken ct) + public async Task InstallGlobalCliAsync( + RuntimePlatform platform, + IProgress installProgress, + CancellationToken ct) { + Debug.Assert(installProgress != null, "installProgress must not be null"); ct.ThrowIfCancellationRequested(); DispatcherBootstrapPinLoadResult bootstrapPin = _cliPinReader.LoadDispatcherBootstrapPin(); @@ -212,6 +217,7 @@ public async Task InstallGlobalCliAsync(RuntimePlatform platfo platform, bootstrapPin.DispatcherReleaseTag, bootstrapPin.ArchiveManifest, + installProgress, ct); _cliInstallationDetector.InvalidateCache(); return result; diff --git a/Packages/src/Editor/Infrastructure/CLI/NativeCliInstaller.cs b/Packages/src/Editor/Infrastructure/CLI/NativeCliInstaller.cs index 55ffcba7b..1842ed38a 100644 --- a/Packages/src/Editor/Infrastructure/CLI/NativeCliInstaller.cs +++ b/Packages/src/Editor/Infrastructure/CLI/NativeCliInstaller.cs @@ -36,10 +36,12 @@ public static async Task InstallAsync( RuntimePlatform platform, string dispatcherReleaseTag, string dispatcherArchiveManifest, + IProgress installProgress, CancellationToken ct) { UnityEngine.Debug.Assert(!string.IsNullOrWhiteSpace(dispatcherReleaseTag), "dispatcherReleaseTag must not be null or empty"); UnityEngine.Debug.Assert(!string.IsNullOrWhiteSpace(dispatcherArchiveManifest), "dispatcherArchiveManifest must not be null or empty"); + UnityEngine.Debug.Assert(installProgress != null, "installProgress must not be null"); ct.ThrowIfCancellationRequested(); string installDirectory = NativeCliInstallPathResolver.GetInstallDirectoryForCurrentUser(platform); @@ -56,7 +58,11 @@ public static async Task InstallAsync( dispatcherArchiveManifest, true); CliInstallResult result = await Task.Run( - () => NativeCliSetupCommandRunner.RunInstallCommand(command, ct, INSTALL_PROCESS_TIMEOUT_MS), + () => NativeCliSetupCommandRunner.RunInstallCommand( + command, + ct, + INSTALL_PROCESS_TIMEOUT_MS, + line => installProgress.Report(line)), ct); if (result.Success) @@ -184,10 +190,16 @@ public Task InstallGlobalCliAsync( RuntimePlatform platform, string dispatcherReleaseTag, string dispatcherArchiveManifest, + IProgress installProgress, CancellationToken ct) { ct.ThrowIfCancellationRequested(); - return NativeCliInstaller.InstallAsync(platform, dispatcherReleaseTag, dispatcherArchiveManifest, ct); + return NativeCliInstaller.InstallAsync( + platform, + dispatcherReleaseTag, + dispatcherArchiveManifest, + installProgress, + ct); } public Task UninstallGlobalCliAsync(RuntimePlatform platform, CancellationToken ct) diff --git a/Packages/src/Editor/Infrastructure/CLI/NativeCliSetupCommandRunner.cs b/Packages/src/Editor/Infrastructure/CLI/NativeCliSetupCommandRunner.cs index cd3b6017c..04cbcf715 100644 --- a/Packages/src/Editor/Infrastructure/CLI/NativeCliSetupCommandRunner.cs +++ b/Packages/src/Editor/Infrastructure/CLI/NativeCliSetupCommandRunner.cs @@ -21,11 +21,13 @@ internal static class NativeCliSetupCommandRunner internal static CliInstallResult RunInstallCommand( NativeCliInstallCommand command, CancellationToken ct, - int timeoutMs) + int timeoutMs, + Action onOutputLine) { UnityEngine.Debug.Assert(!string.IsNullOrEmpty(command.FileName), "command.FileName must not be null or empty"); UnityEngine.Debug.Assert(!string.IsNullOrEmpty(command.Arguments), "command.Arguments must not be null or empty"); UnityEngine.Debug.Assert(timeoutMs > 0, "timeoutMs must be greater than zero"); + UnityEngine.Debug.Assert(onOutputLine != null, "onOutputLine must not be null"); ct.ThrowIfCancellationRequested(); return RunCliSetupCommand( @@ -33,6 +35,7 @@ internal static CliInstallResult RunInstallCommand( ct, timeoutMs, "release CLI installer", + onOutputLine, startInfo => { }); } @@ -49,6 +52,7 @@ internal static CliInstallResult RunUninstallCommand( ct, timeoutMs, "global CLI uninstall command", + static _ => { }, startInfo => { startInfo.EnvironmentVariables[CliConstants.INSTALL_DIR_ENVIRONMENT_VARIABLE] = installDirectory; @@ -60,12 +64,14 @@ private static CliInstallResult RunCliSetupCommand( CancellationToken ct, int timeoutMs, string commandDescription, + Action onOutputLine, Action configureStartInfo) { UnityEngine.Debug.Assert(!string.IsNullOrEmpty(command.FileName), "command.FileName must not be null or empty"); UnityEngine.Debug.Assert(!string.IsNullOrEmpty(command.Arguments), "command.Arguments must not be null or empty"); UnityEngine.Debug.Assert(timeoutMs > 0, "timeoutMs must be greater than zero"); UnityEngine.Debug.Assert(!string.IsNullOrWhiteSpace(commandDescription), "commandDescription must not be null or empty"); + UnityEngine.Debug.Assert(onOutputLine != null, "onOutputLine must not be null"); UnityEngine.Debug.Assert(configureStartInfo != null, "configureStartInfo must not be null"); ct.ThrowIfCancellationRequested(); @@ -95,6 +101,7 @@ private static CliInstallResult RunCliSetupCommand( if (e.Data != null) { standardOutputBuilder.AppendLine(e.Data); + onOutputLine(e.Data); } }; process.ErrorDataReceived += (sender, e) => @@ -102,6 +109,7 @@ private static CliInstallResult RunCliSetupCommand( if (e.Data != null) { errorOutputBuilder.AppendLine(e.Data); + onOutputLine(e.Data); } }; process.BeginOutputReadLine(); diff --git a/Packages/src/Editor/Presentation/CliInstallProgressFormatting.cs b/Packages/src/Editor/Presentation/CliInstallProgressFormatting.cs new file mode 100644 index 000000000..a96ee6dcf --- /dev/null +++ b/Packages/src/Editor/Presentation/CliInstallProgressFormatting.cs @@ -0,0 +1,62 @@ +using System; +using UnityEngine; + +namespace io.github.hatayama.UnityCliLoop.Presentation +{ + /// + /// Formats progress status text shown while the global CLI installer is running. + /// + internal static class CliInstallProgressFormatting + { + // Why transparent rich-text dots: keep "Installing... (Ns)" word order while + // reserving three-dot width so the elapsed-time suffix does not shift left/right. + private const string HIDDEN_DOT_OPEN = ""; + private const string HIDDEN_DOT_CLOSE = ""; + + internal static string FormatStatusLine(TimeSpan elapsed, int animationStep) + { + Debug.Assert(elapsed >= TimeSpan.Zero, "elapsed must not be negative"); + Debug.Assert(animationStep >= 0, "animationStep must not be negative"); + + int visibleDotCount = (animationStep % 3) + 1; + int hiddenDotCount = 3 - visibleDotCount; + string dots = BuildPaddedDots(visibleDotCount, hiddenDotCount); + + int totalSeconds = (int)elapsed.TotalSeconds; + if (totalSeconds < 60) + { + return $"Installing{dots} ({totalSeconds}s)"; + } + + int minutes = totalSeconds / 60; + int seconds = totalSeconds % 60; + return $"Installing{dots} ({minutes}m {seconds:00}s)"; + } + + internal static string FormatDetailLine(string rawLine) + { + if (string.IsNullOrWhiteSpace(rawLine)) + { + return string.Empty; + } + + return rawLine.Trim(); + } + + private static string BuildPaddedDots(int visibleDotCount, int hiddenDotCount) + { + Debug.Assert(visibleDotCount >= 1 && visibleDotCount <= 3, "visibleDotCount must be 1..3"); + Debug.Assert(hiddenDotCount >= 0 && hiddenDotCount <= 2, "hiddenDotCount must be 0..2"); + Debug.Assert(visibleDotCount + hiddenDotCount == 3, "visible and hidden dots must total 3"); + + string visibleDots = new string('.', visibleDotCount); + if (hiddenDotCount == 0) + { + return visibleDots; + } + + string hiddenDots = new string('.', hiddenDotCount); + return visibleDots + HIDDEN_DOT_OPEN + hiddenDots + HIDDEN_DOT_CLOSE; + } + } +} diff --git a/Packages/src/Editor/Presentation/CliInstallProgressFormatting.cs.meta b/Packages/src/Editor/Presentation/CliInstallProgressFormatting.cs.meta new file mode 100644 index 000000000..903a1c0da --- /dev/null +++ b/Packages/src/Editor/Presentation/CliInstallProgressFormatting.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bc1a68caf5a014ed0bb3a3d851d70991 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/src/Editor/Presentation/Setup/SetupWizardCliWorkflowController.cs b/Packages/src/Editor/Presentation/Setup/SetupWizardCliWorkflowController.cs index b2d2caba3..bf55d0ea2 100644 --- a/Packages/src/Editor/Presentation/Setup/SetupWizardCliWorkflowController.cs +++ b/Packages/src/Editor/Presentation/Setup/SetupWizardCliWorkflowController.cs @@ -18,6 +18,7 @@ namespace io.github.hatayama.UnityCliLoop.Presentation internal sealed class SetupWizardCliWorkflowController { private readonly SetupWizardCliStepPresenter _cliStepPresenter; + private readonly CliInstallProgressView _installProgressView; private readonly CliSetupApplicationService _cliSetupApplicationService; private readonly Action _refreshUi; @@ -28,6 +29,8 @@ internal SetupWizardCliWorkflowController( VisualElement cliStatusIcon, Label cliStatusLabel, Button installCliButton, + VisualElement installProgressContainer, + Label installProgressLabel, CliSetupApplicationService cliSetupApplicationService, Action refreshUi) { @@ -38,6 +41,10 @@ internal SetupWizardCliWorkflowController( ?? throw new ArgumentNullException(nameof(cliSetupApplicationService)); _refreshUi = refreshUi ?? throw new ArgumentNullException(nameof(refreshUi)); + _installProgressView = new CliInstallProgressView( + installProgressContainer, + installCliButton, + installProgressLabel); _cliStepPresenter = new SetupWizardCliStepPresenter( cliStatusIcon, @@ -110,11 +117,14 @@ private async Task HandleInstallCliAsync(CancellationToken ct) requiredCliVersion: GetMinimumRequiredCliVersion(), isInstallingCli: _isInstallingCli, needsCliPathSetup: _needsCliPathSetup); + _installProgressView.Show(); + Progress installProgress = new(_installProgressView.SetDetailLine); try { CliInstallResult result = await _cliSetupApplicationService.InstallGlobalCliAsync( UnityEngine.Application.platform, + installProgress, ct); if (!result.Success) @@ -141,6 +151,7 @@ await CliPathSetupPrompt.EnsureVisibleAndShowResultAsync( } finally { + _installProgressView.Hide(); _isInstallingCli = false; _refreshUi(CliInstallRefreshPolicy.ShouldRefreshSkillsAfterCliInstall( wasCliInstalledBeforeInstall)); diff --git a/Packages/src/Editor/Presentation/Setup/SetupWizardWindow.cs b/Packages/src/Editor/Presentation/Setup/SetupWizardWindow.cs index 35b7fcb37..a5e9cc186 100644 --- a/Packages/src/Editor/Presentation/Setup/SetupWizardWindow.cs +++ b/Packages/src/Editor/Presentation/Setup/SetupWizardWindow.cs @@ -287,6 +287,8 @@ private void BindElements() VisualElement cliStatusIcon = rootVisualElement.Q("cli-status-icon"); Label cliStatusLabel = rootVisualElement.Q