Skip to content
Merged
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
58 changes: 37 additions & 21 deletions src/Aspire.Cli/Agents/CopilotCli/CopilotCliRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,28 +52,8 @@ internal sealed class CopilotCliRunner(ILogger<CopilotCliRunner> logger) : ICopi
}

var output = await outputTask.ConfigureAwait(false);
var versionString = output.Trim();

if (string.IsNullOrEmpty(versionString))
{
logger.LogDebug("GitHub Copilot CLI returned empty version output");
return null;
}

// Version output may be on the first line if multi-line
var lines = versionString.Split(['\n', '\r'], StringSplitOptions.RemoveEmptyEntries);
if (lines.Length > 0)
{
versionString = lines[0].Trim();
}

// Try to parse the version string (may have a 'v' prefix like "v1.2.3")
if (versionString.StartsWith('v') || versionString.StartsWith('V'))
{
versionString = versionString[1..];
}

if (SemVersion.TryParse(versionString, SemVersionStyles.Any, out var version))
if (TryParseVersionOutput(output, out var version))
{
logger.LogDebug("Found GitHub Copilot CLI version: {Version}", version);
return version;
Expand All @@ -88,4 +68,40 @@ internal sealed class CopilotCliRunner(ILogger<CopilotCliRunner> logger) : ICopi
return null;
}
}

internal static bool TryParseVersionOutput(string output, out SemVersion? version)
{
version = null;
var versionString = output.Trim();

if (string.IsNullOrEmpty(versionString))
{
return false;
}

// Version output may be on the first line if multi-line
var lines = versionString.Split(['\n', '\r'], StringSplitOptions.RemoveEmptyEntries);
if (lines.Length > 0)
{
versionString = lines[0].Trim();
}

// Try to extract the version from known formats like "GitHub Copilot CLI 0.0.397"
var lastSpaceIndex = versionString.LastIndexOf(' ');
if (lastSpaceIndex >= 0)
{
versionString = versionString[(lastSpaceIndex + 1)..];
}

// Trim common trailing punctuation that may follow the version (for example, "0.0.397.")
versionString = versionString.TrimEnd('.', ',', ')');

// Try to parse the version string (may have a 'v' prefix like "v1.2.3")
if (versionString.StartsWith('v') || versionString.StartsWith('V'))
{
versionString = versionString[1..];
}

return SemVersion.TryParse(versionString, SemVersionStyles.Any, out version);
}
}
33 changes: 33 additions & 0 deletions tests/Aspire.Cli.Tests/Agents/CopilotCliRunnerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,37 @@ public async Task GetVersionAsync_ChecksForCopilot()
// we just verify the method completes without throwing
// Version can be null (not installed) or a real version
}

[Theory]
[InlineData("GitHub Copilot CLI 0.0.397", 0, 0, 397)]
[InlineData("GitHub Copilot CLI 1.2.3", 1, 2, 3)]
[InlineData("0.0.397", 0, 0, 397)]
[InlineData("1.2.3", 1, 2, 3)]
[InlineData("v1.2.3", 1, 2, 3)]
[InlineData("V1.2.3", 1, 2, 3)]
[InlineData("GitHub Copilot CLI 0.0.397\nsome other output", 0, 0, 397)]
[InlineData(" GitHub Copilot CLI 0.0.397 ", 0, 0, 397)]
[InlineData("GitHub Copilot CLI 0.0.397.", 0, 0, 397)]
public void TryParseVersionOutput_ValidVersionStrings_ReturnsTrue(string input, int major, int minor, int patch)
{
var result = CopilotCliRunner.TryParseVersionOutput(input, out var version);

Assert.True(result);
Assert.NotNull(version);
Assert.Equal(major, version.Major);
Assert.Equal(minor, version.Minor);
Assert.Equal(patch, version.Patch);
}

[Theory]
[InlineData("")]
[InlineData(" ")]
[InlineData("not a version")]
public void TryParseVersionOutput_InvalidVersionStrings_ReturnsFalse(string input)
{
var result = CopilotCliRunner.TryParseVersionOutput(input, out var version);

Assert.False(result);
Assert.Null(version);
}
}
Loading