From d6191a83933bf8d61169834d732f470a56d3e9fc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 16 May 2026 07:38:52 +0000 Subject: [PATCH 1/2] Initial plan From 03c4a18bd37c222bddfda3934ade012ebd4a4052 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 16 May 2026 07:44:41 +0000 Subject: [PATCH 2/2] feat: add Winget and Chocolatey support to cratis update command - Add Winget and Chocolatey values to CliUpdateStrategy enum - Detect winget install path (\WinGet\Packages\) and chocolatey paths (\chocolatey\bin\, \chocolatey\lib\) - Run `winget upgrade --id Cratis.Cli [--version x]` for winget installs - Run `choco upgrade cratis --yes [--version x]` for chocolatey installs - Update SelfUpdateCommand LlmDescription and process-start failure hints - Add specs covering detection and update process start info for both package managers - Update getting-started docs with Windows Winget and Chocolatey install instructions Agent-Logs-Url: https://github.com/Cratis/cli/sessions/f35097ca-c130-4964-86ce-89ff10c7010e Co-authored-by: einari <134365+einari@users.noreply.github.com> --- Documentation/getting-started/index.md | 24 ++++ .../for_CliUpdate/when_detecting_strategy.cs | 104 ++++++++++++++++++ Source/Cli/CliUpdate.cs | 67 +++++++++++ .../Cli/Commands/Version/SelfUpdateCommand.cs | 4 +- 4 files changed, 198 insertions(+), 1 deletion(-) diff --git a/Documentation/getting-started/index.md b/Documentation/getting-started/index.md index 3b0898a..cd9c3ea 100644 --- a/Documentation/getting-started/index.md +++ b/Documentation/getting-started/index.md @@ -15,6 +15,30 @@ To upgrade: brew upgrade cratis ``` +### Windows (Winget) + +```powershell +winget install Cratis.Cli +``` + +To upgrade: + +```powershell +winget upgrade Cratis.Cli +``` + +### Windows (Chocolatey) + +```powershell +choco install cratis +``` + +To upgrade: + +```powershell +choco upgrade cratis +``` + ### Linux Download and install the pre-built native binary from the [latest release](https://github.com/Cratis/cli/releases/latest): diff --git a/Source/Cli.Specs/for_CliUpdate/when_detecting_strategy.cs b/Source/Cli.Specs/for_CliUpdate/when_detecting_strategy.cs index a25967b..6d7a29e 100644 --- a/Source/Cli.Specs/for_CliUpdate/when_detecting_strategy.cs +++ b/Source/Cli.Specs/for_CliUpdate/when_detecting_strategy.cs @@ -44,6 +44,58 @@ void should_detect_manual_linux_when_native_on_linux() strategy.ShouldEqual(CliUpdateStrategy.ManualLinux); } + [Fact] + void should_detect_winget_from_path() + { + var strategy = CliUpdate.DetectStrategy( + @"C:\Users\user\AppData\Local\Microsoft\WinGet\Packages\Cratis.Cli_abc123\cratis.exe", + @"C:\Users\user\AppData\Local\Microsoft\WinGet\Packages\Cratis.Cli_abc123\", + isNativeBuild: true, + isMacOS: false, + isLinux: false); + + strategy.ShouldEqual(CliUpdateStrategy.Winget); + } + + [Fact] + void should_detect_winget_from_machine_scoped_path() + { + var strategy = CliUpdate.DetectStrategy( + @"C:\Program Files\WinGet\Packages\Cratis.Cli_abc123\cratis.exe", + @"C:\Program Files\WinGet\Packages\Cratis.Cli_abc123\", + isNativeBuild: true, + isMacOS: false, + isLinux: false); + + strategy.ShouldEqual(CliUpdateStrategy.Winget); + } + + [Fact] + void should_detect_chocolatey_from_bin_path() + { + var strategy = CliUpdate.DetectStrategy( + @"C:\ProgramData\chocolatey\bin\cratis.exe", + @"C:\ProgramData\chocolatey\bin\", + isNativeBuild: true, + isMacOS: false, + isLinux: false); + + strategy.ShouldEqual(CliUpdateStrategy.Chocolatey); + } + + [Fact] + void should_detect_chocolatey_from_lib_path() + { + var strategy = CliUpdate.DetectStrategy( + @"C:\ProgramData\chocolatey\lib\cratis\tools\cratis.exe", + @"C:\ProgramData\chocolatey\lib\cratis\tools\", + isNativeBuild: true, + isMacOS: false, + isLinux: false); + + strategy.ShouldEqual(CliUpdateStrategy.Chocolatey); + } + [Fact] void should_use_cratis_update_hint_for_auto_update_strategies() { @@ -52,6 +104,22 @@ void should_use_cratis_update_hint_for_auto_update_strategies() hint.ShouldContain("1.0.0 -> 1.1.0"); } + [Fact] + void should_use_cratis_update_hint_for_winget() + { + var hint = CliUpdate.GetUpdateHint(CliUpdateStrategy.Winget, "1.0.0", "1.1.0"); + hint.ShouldContain("run 'cratis update'"); + hint.ShouldContain("1.0.0 -> 1.1.0"); + } + + [Fact] + void should_use_cratis_update_hint_for_chocolatey() + { + var hint = CliUpdate.GetUpdateHint(CliUpdateStrategy.Chocolatey, "1.0.0", "1.1.0"); + hint.ShouldContain("run 'cratis update'"); + hint.ShouldContain("1.0.0 -> 1.1.0"); + } + [Fact] void should_prepare_brew_update_before_upgrade_for_homebrew() { @@ -67,4 +135,40 @@ void should_not_prepare_brew_update_when_target_version_is_set() var startInfo = CliUpdate.CreatePreUpdateProcessStartInfo(CliUpdateStrategy.Homebrew, "1.2.3"); startInfo.ShouldBeNull(); } + + [Fact] + void should_create_winget_upgrade_process_start_info() + { + var startInfo = CliUpdate.CreateUpdateProcessStartInfo(CliUpdateStrategy.Winget); + startInfo.ShouldNotBeNull(); + startInfo!.FileName.ShouldEqual("winget"); + startInfo.Arguments.ShouldEqual("upgrade --id Cratis.Cli"); + } + + [Fact] + void should_create_winget_upgrade_process_start_info_with_version() + { + var startInfo = CliUpdate.CreateUpdateProcessStartInfo(CliUpdateStrategy.Winget, "1.2.3"); + startInfo.ShouldNotBeNull(); + startInfo!.FileName.ShouldEqual("winget"); + startInfo.Arguments.ShouldEqual("upgrade --id Cratis.Cli --version 1.2.3"); + } + + [Fact] + void should_create_chocolatey_upgrade_process_start_info() + { + var startInfo = CliUpdate.CreateUpdateProcessStartInfo(CliUpdateStrategy.Chocolatey); + startInfo.ShouldNotBeNull(); + startInfo!.FileName.ShouldEqual("choco"); + startInfo.Arguments.ShouldEqual("upgrade cratis --yes"); + } + + [Fact] + void should_create_chocolatey_upgrade_process_start_info_with_version() + { + var startInfo = CliUpdate.CreateUpdateProcessStartInfo(CliUpdateStrategy.Chocolatey, "1.2.3"); + startInfo.ShouldNotBeNull(); + startInfo!.FileName.ShouldEqual("choco"); + startInfo.Arguments.ShouldEqual("upgrade cratis --yes --version 1.2.3"); + } } diff --git a/Source/Cli/CliUpdate.cs b/Source/Cli/CliUpdate.cs index faf37c4..f4f4a11 100644 --- a/Source/Cli/CliUpdate.cs +++ b/Source/Cli/CliUpdate.cs @@ -20,6 +20,16 @@ public enum CliUpdateStrategy /// Homebrew, + /// + /// Update by running winget upgrade --id Cratis.Cli. + /// + Winget, + + /// + /// Update by running choco upgrade cratis. + /// + Chocolatey, + /// /// Update manually by downloading and replacing the Linux native binary. /// @@ -129,6 +139,44 @@ public static CliUpdateStrategy DetectStrategy() }; } + if (strategy == CliUpdateStrategy.Winget) + { + var arguments = "upgrade --id Cratis.Cli"; + if (!string.IsNullOrWhiteSpace(targetVersion)) + { + arguments += $" --version {targetVersion}"; + } + + return new ProcessStartInfo + { + FileName = "winget", + Arguments = arguments, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + } + + if (strategy == CliUpdateStrategy.Chocolatey) + { + var arguments = "upgrade cratis --yes"; + if (!string.IsNullOrWhiteSpace(targetVersion)) + { + arguments += $" --version {targetVersion}"; + } + + return new ProcessStartInfo + { + FileName = "choco", + Arguments = arguments, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + } + return null; } @@ -190,6 +238,16 @@ internal static CliUpdateStrategy DetectStrategy( return CliUpdateStrategy.Homebrew; } + if (IsWingetPath(processPath)) + { + return CliUpdateStrategy.Winget; + } + + if (IsChocolateyPath(processPath)) + { + return CliUpdateStrategy.Chocolatey; + } + if (isLinux) { return CliUpdateStrategy.ManualLinux; @@ -208,6 +266,15 @@ static bool IsHomebrewPath(string? path) => (path.Contains("/Cellar/cratis/", StringComparison.OrdinalIgnoreCase) || path.Contains("/Homebrew/Cellar/cratis/", StringComparison.OrdinalIgnoreCase)); + static bool IsWingetPath(string? path) => + !string.IsNullOrWhiteSpace(path) && + path.Contains(@"\WinGet\Packages\", StringComparison.OrdinalIgnoreCase); + + static bool IsChocolateyPath(string? path) => + !string.IsNullOrWhiteSpace(path) && + (path.Contains(@"\chocolatey\bin\", StringComparison.OrdinalIgnoreCase) || + path.Contains(@"\chocolatey\lib\", StringComparison.OrdinalIgnoreCase)); + static string? GetEffectiveProcessPath() { var path = Environment.ProcessPath; diff --git a/Source/Cli/Commands/Version/SelfUpdateCommand.cs b/Source/Cli/Commands/Version/SelfUpdateCommand.cs index c6543df..59e4b1c 100644 --- a/Source/Cli/Commands/Version/SelfUpdateCommand.cs +++ b/Source/Cli/Commands/Version/SelfUpdateCommand.cs @@ -8,7 +8,7 @@ namespace Cratis.Cli.Commands.Version; /// /// Updates the Cratis CLI to the latest version using the detected installation method. /// -[LlmDescription("Updates the cratis CLI to the latest version using the appropriate installation method (dotnet tool or Homebrew).")] +[LlmDescription("Updates the cratis CLI to the latest version using the appropriate installation method (dotnet tool, Homebrew, Winget, or Chocolatey).")] [CliCommand("update", "Update the Cratis CLI to the latest version")] [CliExample("update")] [CliExample("update", "--version", "1.2.3")] @@ -103,6 +103,8 @@ protected override async Task ExecuteAsync(CommandContext context, SelfUpda { CliUpdateStrategy.DotNetTool => "Ensure the .NET SDK is installed and 'dotnet' is on your PATH", CliUpdateStrategy.Homebrew => "Ensure Homebrew is installed and 'brew' is on your PATH", + CliUpdateStrategy.Winget => "Ensure winget is installed and 'winget' is on your PATH", + CliUpdateStrategy.Chocolatey => "Ensure Chocolatey is installed and 'choco' is on your PATH", _ => null }; OutputFormatter.WriteError(format, "Failed to start update process", hint, ExitCodes.ServerErrorCode);