Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions Documentation/getting-started/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
104 changes: 104 additions & 0 deletions Source/Cli.Specs/for_CliUpdate/when_detecting_strategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand All @@ -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()
{
Expand All @@ -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");
}
}
67 changes: 67 additions & 0 deletions Source/Cli/CliUpdate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ public enum CliUpdateStrategy
/// </summary>
Homebrew,

/// <summary>
/// Update by running <c>winget upgrade --id Cratis.Cli</c>.
/// </summary>
Winget,

/// <summary>
/// Update by running <c>choco upgrade cratis</c>.
/// </summary>
Chocolatey,

/// <summary>
/// Update manually by downloading and replacing the Linux native binary.
/// </summary>
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down
4 changes: 3 additions & 1 deletion Source/Cli/Commands/Version/SelfUpdateCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace Cratis.Cli.Commands.Version;
/// <summary>
/// Updates the Cratis CLI to the latest version using the detected installation method.
/// </summary>
[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")]
Expand Down Expand Up @@ -103,6 +103,8 @@ protected override async Task<int> 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);
Expand Down