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