diff --git a/.github/workflows/desktop-release.yml b/.github/workflows/desktop-release.yml index 838bd23..46c7598 100644 --- a/.github/workflows/desktop-release.yml +++ b/.github/workflows/desktop-release.yml @@ -49,6 +49,30 @@ jobs: "name=$name" >> $env:GITHUB_OUTPUT Write-Host "Building $name" + # The web app's download page has the version written into a constant, because GitHub's + # /releases/latest points at whichever release is newest overall and this repository publishes + # two independent tag lines — half the time "latest" is a NuGet release with no zip on it. + # + # Writing it down means it can go stale, so it is checked here rather than left to memory: a + # release that forgets to update the page does not ship, and the page can never offer a + # version that was never published. + - name: The download page must offer this version + if: github.event_name == 'release' + shell: pwsh + run: | + $file = 'src/SignsOfAI.UI/Services/DesktopRelease.cs' + $declared = (Select-String -Path $file -Pattern 'Version\s*=\s*"([^"]+)"').Matches[0].Groups[1].Value + $tagged = "${{ steps.version.outputs.version }}" + if ($declared -ne $tagged) { + Write-Error @" + The tag says $tagged but $file says $declared. + Update DesktopRelease.Version, merge it, then re-run this release — otherwise the download + button on the site points at a release that does not exist. + "@ + exit 1 + } + Write-Host "Download page offers $declared, which is what this tag builds." + # A release that crashes is worse than a release that never shipped, and a GitHub Release # cannot be un-downloaded once people have it. - name: Test diff --git a/src/SignsOfAI.Desktop/DesktopVersion.cs b/src/SignsOfAI.Desktop/DesktopVersion.cs new file mode 100644 index 0000000..41202b6 --- /dev/null +++ b/src/SignsOfAI.Desktop/DesktopVersion.cs @@ -0,0 +1,42 @@ +using System.Reflection; + +namespace SignsOfAI.Desktop; + +/// +/// Which build this is. +/// +/// Small enough to look unnecessary, and it exists because of a support message: somebody reported +/// that "desktop 0.4.0 is not published" when what they actually meant was that they could not tell +/// which build they had. The app said its name in the title bar and nothing else, so neither could +/// anyone trying to help them. +/// +/// Kept out of so it can be tested without standing up a window — same +/// reason DesktopFolderBatch lives on its own. +/// +public static class DesktopVersion +{ + /// + /// The version this executable was built with, or null if it somehow carries none. + /// + /// The release workflow passes -p:Version= taken from the tag and the SDK turns that into + /// . A developer build with no version set + /// reports the SDK's own 1.0.0, which is the honest answer for something never released. + /// + public static string? Running() => Of(typeof(DesktopVersion).Assembly); + + /// Same, for a given assembly, so a test can hand it one. + public static string? Of(Assembly assembly) + { + var informational = assembly + .GetCustomAttribute() + ?.InformationalVersion; + + // The SDK appends "+" when the repository is available at build time. Accurate, + // and not what somebody reading a footer is trying to find out. + return Trim(informational); + } + + /// Drops the source-control metadata the SDK appends after a +. + public static string? Trim(string? informational) => + string.IsNullOrWhiteSpace(informational) ? null : informational.Split('+')[0]; +} diff --git a/src/SignsOfAI.Desktop/MainWindow.xaml.cs b/src/SignsOfAI.Desktop/MainWindow.xaml.cs index 8ce858d..96415f2 100644 --- a/src/SignsOfAI.Desktop/MainWindow.xaml.cs +++ b/src/SignsOfAI.Desktop/MainWindow.xaml.cs @@ -36,7 +36,9 @@ public MainWindow() services.AddSingleton(); // Native HTTP: Ollama on localhost is simply reachable, with no CORS workaround to explain. - services.AddSingleton(HostCapabilities.Desktop); + // The build number travels with it, because a downloaded app is the kind that can be out of + // date and this one used to have no way of saying which it was. + services.AddSingleton(HostCapabilities.Desktop(DesktopVersion.Running())); // The XAML binds Services="{DynamicResource services}", so the provider has to be in // Resources before InitializeComponent builds the visual tree. diff --git a/src/SignsOfAI.UI/Layout/MainLayout.razor b/src/SignsOfAI.UI/Layout/MainLayout.razor index 8c72654..986c59e 100644 --- a/src/SignsOfAI.UI/Layout/MainLayout.razor +++ b/src/SignsOfAI.UI/Layout/MainLayout.razor @@ -2,11 +2,13 @@ @implements IDisposable @inject Loc L @inject IFolderBatch Folders +@inject HostCapabilities Host