From d238d3d0016cd2d6192f4a37088e26abdebaaa85 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Mar 2026 21:33:09 +0000 Subject: [PATCH 1/2] Bump the nuget group with 1 update Bumps Scriban from 5.7.0 to 6.6.0 --- updated-dependencies: - dependency-name: Scriban dependency-version: 6.6.0 dependency-type: direct:production dependency-group: nuget ... Signed-off-by: dependabot[bot] --- src/CodeQLToolkit.Shared/CodeQLToolkit.Shared.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CodeQLToolkit.Shared/CodeQLToolkit.Shared.csproj b/src/CodeQLToolkit.Shared/CodeQLToolkit.Shared.csproj index d75dfc5..898068d 100644 --- a/src/CodeQLToolkit.Shared/CodeQLToolkit.Shared.csproj +++ b/src/CodeQLToolkit.Shared/CodeQLToolkit.Shared.csproj @@ -11,7 +11,7 @@ - + From 8618c2a5f8d900cb46e177298e70e61b34ac97d6 Mon Sep 17 00:00:00 2001 From: trganda Date: Wed, 1 Apr 2026 13:45:27 +0800 Subject: [PATCH 2/2] feat: auto-resolve latest CodeQL versions from GitHub API in set-version command Add GitHubReleaseResolver that queries the GitHub Releases API to fetch the latest tag from github/codeql-cli-binaries and github/codeql-action, using them as default values for --cli-version, --standard-library-version, and --bundle-version on 'qlt codeql set version'. Falls back to hardcoded values (2.25.1) if the network request fails. Add InternalsVisibleTo for the test project and 14 unit tests covering successful resolution, network errors, malformed JSON, missing tag_name, and fallback value format sanity checks. Co-Authored-By: Claude Sonnet 4.6 --- .../Lifecycle/CodeQLLifecycleFeature.cs | 7 +- .../CodeQL/GitHubReleaseResolver.cs | 96 +++++++++++ .../CodeQLToolkit.Shared.csproj | 6 + .../CodeQL/GitHubReleaseResolverTests.cs | 155 ++++++++++++++++++ 4 files changed, 261 insertions(+), 3 deletions(-) create mode 100644 src/CodeQLToolkit.Shared/CodeQL/GitHubReleaseResolver.cs create mode 100644 test/CodeQLToolkit.Shared.Tests/CodeQL/GitHubReleaseResolverTests.cs diff --git a/src/CodeQLToolkit.Features/CodeQL/Lifecycle/CodeQLLifecycleFeature.cs b/src/CodeQLToolkit.Features/CodeQL/Lifecycle/CodeQLLifecycleFeature.cs index 73e388f..fef374f 100644 --- a/src/CodeQLToolkit.Features/CodeQL/Lifecycle/CodeQLLifecycleFeature.cs +++ b/src/CodeQLToolkit.Features/CodeQL/Lifecycle/CodeQLLifecycleFeature.cs @@ -1,4 +1,5 @@ using CodeQLToolkit.Features.CodeQL.Lifecycle.Targets; +using CodeQLToolkit.Shared.CodeQL; using System.CommandLine; namespace CodeQLToolkit.Features.CodeQL.Lifecycle @@ -19,9 +20,9 @@ public void Register(Command parentCommand) var setVersionCommand = new Command("version", "Sets the version of CodeQL used."); - var cliVersionOption = new Option("--cli-version", () => "2.11.6", "The version of the cli to use. Example: `2.11.6`.") { IsRequired = true }; - var standardLibraryVersionOption = new Option("--standard-library-version", () => "codeql-cli/v2.11.6", "The version of the standard library to use. Example: `codeql-cli/v2.11.6`.") { IsRequired = true }; - var bundleVersionOption = new Option("--bundle-version", () => "codeql-bundle-20221211", "The bundle version to use. Example: `codeql-bundle-20221211`.") { IsRequired = true }; + var cliVersionOption = new Option("--cli-version", GitHubReleaseResolver.GetLatestCLIVersion, "The version of the cli to use. Example: `2.25.1`.") { IsRequired = true }; + var standardLibraryVersionOption = new Option("--standard-library-version", GitHubReleaseResolver.GetLatestStandardLibraryVersion, "The version of the standard library to use. Example: `codeql-cli/v2.25.1`.") { IsRequired = true }; + var bundleVersionOption = new Option("--bundle-version", GitHubReleaseResolver.GetLatestBundleVersion, "The bundle version to use. Example: `codeql-bundle-v2.25.1`.") { IsRequired = true }; setVersionCommand.Add(cliVersionOption); setVersionCommand.Add(standardLibraryVersionOption); diff --git a/src/CodeQLToolkit.Shared/CodeQL/GitHubReleaseResolver.cs b/src/CodeQLToolkit.Shared/CodeQL/GitHubReleaseResolver.cs new file mode 100644 index 0000000..dbfdf42 --- /dev/null +++ b/src/CodeQLToolkit.Shared/CodeQL/GitHubReleaseResolver.cs @@ -0,0 +1,96 @@ +using Newtonsoft.Json.Linq; +using System.Net.Http.Headers; + +namespace CodeQLToolkit.Shared.CodeQL +{ + /// + /// Resolves the latest release versions for CodeQL CLI, standard library, and bundle + /// from GitHub's public API. Falls back to hardcoded values if the request fails. + /// + public static class GitHubReleaseResolver + { + public const string FallbackCLIVersion = "2.25.1"; + public const string FallbackStandardLibraryVersion = "codeql-cli/v2.25.1"; + public const string FallbackBundleVersion = "codeql-bundle-v2.25.1"; + + private static readonly HttpClient _client = CreateClient(null); + + private static HttpClient CreateClient(HttpMessageHandler? handler) + { + var client = handler != null ? new HttpClient(handler) : new HttpClient(); + client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("qlt", "1.0")); + client.Timeout = TimeSpan.FromSeconds(5); + return client; + } + + /// + /// Fetches the latest CodeQL CLI version string (e.g. "2.25.1"). + /// Falls back to on any error. + /// + public static string GetLatestCLIVersion() => GetLatestCLIVersion(_client); + + /// + /// Fetches the latest standard library version string (e.g. "codeql-cli/v2.25.1"). + /// Falls back to on any error. + /// + public static string GetLatestStandardLibraryVersion() => GetLatestStandardLibraryVersion(_client); + + /// + /// Fetches the latest bundle version string (e.g. "codeql-bundle-v2.25.1"). + /// Falls back to on any error. + /// + public static string GetLatestBundleVersion() => GetLatestBundleVersion(_client); + + // Internal overloads for testing — accept a custom HttpClient. + + internal static string GetLatestCLIVersion(HttpClient client) + { + try + { + var tag = GetLatestTagName(client, "https://api.github.com/repos/github/codeql-cli-binaries/releases/latest"); + return tag.TrimStart('v'); + } + catch + { + return FallbackCLIVersion; + } + } + + internal static string GetLatestStandardLibraryVersion(HttpClient client) + { + try + { + var tag = GetLatestTagName(client, "https://api.github.com/repos/github/codeql-cli-binaries/releases/latest"); + return $"codeql-cli/v{tag.TrimStart('v')}"; + } + catch + { + return FallbackStandardLibraryVersion; + } + } + + internal static string GetLatestBundleVersion(HttpClient client) + { + try + { + // codeql-action release tags are already in the form "codeql-bundle-vX.Y.Z" + return GetLatestTagName(client, "https://api.github.com/repos/github/codeql-action/releases/latest"); + } + catch + { + return FallbackBundleVersion; + } + } + + internal static HttpClient CreateTestClient(HttpMessageHandler handler) => CreateClient(handler); + + private static string GetLatestTagName(HttpClient client, string url) + { + var response = client.GetAsync(url).GetAwaiter().GetResult(); + response.EnsureSuccessStatusCode(); + var json = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); + var obj = JObject.Parse(json); + return obj["tag_name"]!.Value()!; + } + } +} diff --git a/src/CodeQLToolkit.Shared/CodeQLToolkit.Shared.csproj b/src/CodeQLToolkit.Shared/CodeQLToolkit.Shared.csproj index 898068d..38a00c9 100644 --- a/src/CodeQLToolkit.Shared/CodeQLToolkit.Shared.csproj +++ b/src/CodeQLToolkit.Shared/CodeQLToolkit.Shared.csproj @@ -6,6 +6,12 @@ enable + + + <_Parameter1>CodeQLToolkit.Shared.Tests + + + diff --git a/test/CodeQLToolkit.Shared.Tests/CodeQL/GitHubReleaseResolverTests.cs b/test/CodeQLToolkit.Shared.Tests/CodeQL/GitHubReleaseResolverTests.cs new file mode 100644 index 0000000..c2de8d8 --- /dev/null +++ b/test/CodeQLToolkit.Shared.Tests/CodeQL/GitHubReleaseResolverTests.cs @@ -0,0 +1,155 @@ +using CodeQLToolkit.Shared.CodeQL; +using System.Net; +using System.Text; + +namespace CodeQLToolkit.Shared.Tests.CodeQL +{ + public class GitHubReleaseResolverTests + { + // Fake HttpMessageHandler that returns a fixed response. + private class StubHandler : HttpMessageHandler + { + private readonly HttpStatusCode _statusCode; + private readonly string _body; + + public StubHandler(HttpStatusCode statusCode, string body) + { + _statusCode = statusCode; + _body = body; + } + + protected override Task SendAsync( + HttpRequestMessage request, CancellationToken cancellationToken) + { + var response = new HttpResponseMessage(_statusCode) + { + Content = new StringContent(_body, Encoding.UTF8, "application/json") + }; + return Task.FromResult(response); + } + } + + private static HttpClient MakeClient(HttpStatusCode status, string body) + => GitHubReleaseResolver.CreateTestClient(new StubHandler(status, body)); + + private const string CliResponse = "{\"tag_name\":\"v2.99.0\",\"name\":\"v2.99.0\"}"; + private const string BundleResponse = "{\"tag_name\":\"codeql-bundle-v3.28.5\",\"name\":\"codeql-bundle-v3.28.5\"}"; + + // ── GetLatestCLIVersion ────────────────────────────────────────────── + + [Test] + public void GetLatestCLIVersion_ParsesTagAndStripsV() + { + var client = MakeClient(HttpStatusCode.OK, CliResponse); + var result = GitHubReleaseResolver.GetLatestCLIVersion(client); + Assert.That(result, Is.EqualTo("2.99.0")); + } + + [Test] + public void GetLatestCLIVersion_ReturnsFallback_OnNetworkError() + { + var client = MakeClient(HttpStatusCode.ServiceUnavailable, ""); + var result = GitHubReleaseResolver.GetLatestCLIVersion(client); + Assert.That(result, Is.EqualTo(GitHubReleaseResolver.FallbackCLIVersion)); + } + + [Test] + public void GetLatestCLIVersion_ReturnsFallback_OnMalformedJson() + { + var client = MakeClient(HttpStatusCode.OK, "not-json"); + var result = GitHubReleaseResolver.GetLatestCLIVersion(client); + Assert.That(result, Is.EqualTo(GitHubReleaseResolver.FallbackCLIVersion)); + } + + [Test] + public void GetLatestCLIVersion_ReturnsFallback_OnMissingTagName() + { + var client = MakeClient(HttpStatusCode.OK, "{\"name\":\"v2.99.0\"}"); + var result = GitHubReleaseResolver.GetLatestCLIVersion(client); + Assert.That(result, Is.EqualTo(GitHubReleaseResolver.FallbackCLIVersion)); + } + + // ── GetLatestStandardLibraryVersion ───────────────────────────────── + + [Test] + public void GetLatestStandardLibraryVersion_FormatsCorrectly() + { + var client = MakeClient(HttpStatusCode.OK, CliResponse); + var result = GitHubReleaseResolver.GetLatestStandardLibraryVersion(client); + Assert.That(result, Is.EqualTo("codeql-cli/v2.99.0")); + } + + [Test] + public void GetLatestStandardLibraryVersion_ReturnsFallback_OnNetworkError() + { + var client = MakeClient(HttpStatusCode.InternalServerError, ""); + var result = GitHubReleaseResolver.GetLatestStandardLibraryVersion(client); + Assert.That(result, Is.EqualTo(GitHubReleaseResolver.FallbackStandardLibraryVersion)); + } + + [Test] + public void GetLatestStandardLibraryVersion_ReturnsFallback_OnMalformedJson() + { + var client = MakeClient(HttpStatusCode.OK, "not-json"); + var result = GitHubReleaseResolver.GetLatestStandardLibraryVersion(client); + Assert.That(result, Is.EqualTo(GitHubReleaseResolver.FallbackStandardLibraryVersion)); + } + + // ── GetLatestBundleVersion ─────────────────────────────────────────── + + [Test] + public void GetLatestBundleVersion_FormatsCorrectly() + { + var client = MakeClient(HttpStatusCode.OK, BundleResponse); + var result = GitHubReleaseResolver.GetLatestBundleVersion(client); + Assert.That(result, Is.EqualTo("codeql-bundle-v3.28.5")); + } + + [Test] + public void GetLatestBundleVersion_ReturnsFallback_OnNetworkError() + { + var client = MakeClient(HttpStatusCode.ServiceUnavailable, ""); + var result = GitHubReleaseResolver.GetLatestBundleVersion(client); + Assert.That(result, Is.EqualTo(GitHubReleaseResolver.FallbackBundleVersion)); + } + + [Test] + public void GetLatestBundleVersion_ReturnsFallback_OnMalformedJson() + { + var client = MakeClient(HttpStatusCode.OK, "not-json"); + var result = GitHubReleaseResolver.GetLatestBundleVersion(client); + Assert.That(result, Is.EqualTo(GitHubReleaseResolver.FallbackBundleVersion)); + } + + [Test] + public void GetLatestBundleVersion_ReturnsFallback_OnMissingTagName() + { + var client = MakeClient(HttpStatusCode.OK, "{\"name\":\"codeql-bundle-v3.28.5\"}"); + var result = GitHubReleaseResolver.GetLatestBundleVersion(client); + Assert.That(result, Is.EqualTo(GitHubReleaseResolver.FallbackBundleVersion)); + } + + // ── Fallback value format sanity checks ────────────────────────────── + + [Test] + public void FallbackCLIVersion_HasCorrectFormat() + { + // Should be X.Y.Z with no leading 'v' + var parts = GitHubReleaseResolver.FallbackCLIVersion.Split('.'); + Assert.That(parts.Length, Is.EqualTo(3)); + Assert.That(GitHubReleaseResolver.FallbackCLIVersion, Does.Not.StartWith("v")); + } + + [Test] + public void FallbackStandardLibraryVersion_HasCorrectFormat() + { + Assert.That(GitHubReleaseResolver.FallbackStandardLibraryVersion, Does.StartWith("codeql-cli/v")); + } + + [Test] + public void FallbackBundleVersion_HasCorrectFormat() + { + Assert.That(GitHubReleaseResolver.FallbackBundleVersion, Does.StartWith("codeql-bundle-v")); + } + } +}