diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 000000000..f7362d667 --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "gitversion.tool": { + "version": "6.4.0", + "commands": [ + "dotnet-gitversion" + ], + "rollForward": false + } + } +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 639011782..e418abf50 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -353,6 +353,8 @@ jobs: steps: - uses: actions/checkout@v6 + with: + fetch-depth: 0 - name: Setup .NET 10 uses: actions/setup-dotnet@v5 @@ -371,17 +373,32 @@ jobs: run: dotnet restore src/OpenClaw.Tray.WinUI -r ${{ matrix.rid }} - name: Build WinUI Tray App (Release) - run: dotnet build src/OpenClaw.Tray.WinUI --no-restore -c Release -r ${{ matrix.rid }} -p:Version=${{ needs.test.outputs.semVer }} + run: dotnet build src/OpenClaw.Tray.WinUI --no-restore -c Release -r ${{ matrix.rid }} - name: Publish WinUI Tray App - run: dotnet publish src/OpenClaw.Tray.WinUI -c Release -r ${{ matrix.rid }} --self-contained --no-restore -p:Version=${{ needs.test.outputs.semVer }} -o publish + run: dotnet publish src/OpenClaw.Tray.WinUI -c Release -r ${{ matrix.rid }} --self-contained --no-restore -o publish - name: Publish SetupEngine.UI run: | - dotnet publish src/OpenClaw.SetupEngine.UI -c Release -r ${{ matrix.rid }} --self-contained -p:Version=${{ needs.test.outputs.semVer }} -o publish-setup + dotnet publish src/OpenClaw.SetupEngine.UI -c Release -r ${{ matrix.rid }} --self-contained -o publish-setup mkdir publish\SetupEngine copy publish-setup\* publish\SetupEngine\ -Recurse + - name: Verify GitVersion assembly metadata + shell: pwsh + run: | + $expected = "${{ needs.test.outputs.semVer }}" + $assemblyPath = Resolve-Path "publish\OpenClaw.Tray.WinUI.dll" + $assembly = [System.Reflection.Assembly]::LoadFile($assemblyPath) + $attribute = $assembly.GetCustomAttributes([System.Reflection.AssemblyInformationalVersionAttribute], $false) | Select-Object -First 1 + if (-not $attribute) { + throw "OpenClaw.Tray.WinUI.dll is missing AssemblyInformationalVersionAttribute." + } + $actual = $attribute.InformationalVersion -replace '\+.*$', '' + if ($actual -ne $expected) { + throw "AssemblyInformationalVersion '$actual' did not match GitVersion SemVer '$expected'." + } + - name: Disable NuGet source mapping for signing if: startsWith(github.ref, 'refs/tags/v') && matrix.rid != 'win-arm64' shell: pwsh @@ -450,6 +467,8 @@ jobs: steps: - uses: actions/checkout@v6 + with: + fetch-depth: 0 - name: Setup .NET 10 for VS MSBuild uses: actions/setup-dotnet@v5 @@ -505,7 +524,6 @@ jobs: /p:AppxPackageSigningEnabled=false /p:AppxBundle=Never /p:UapAppxPackageBuildMode=SideloadOnly - /p:Version=${{ needs.test.outputs.majorMinorPatch }} /p:AppxPackageDir=AppPackages\ - name: Find MSIX Package @@ -661,7 +679,6 @@ jobs: - 🦞 System tray integration with gateway status - 🔄 Auto-updates from GitHub Releases - ✅ Code-signed with Azure Trusted Signing - - 📦 MSIX package available for native camera/microphone consent prompts ### Requirements - Windows 10 version 1903 or later @@ -669,6 +686,6 @@ jobs: - OpenClaw gateway running locally ### Quick Start - 1. Run the installer for your architecture (or sideload the MSIX for camera consent) + 1. Run the installer for your architecture 2. Launch from Start Menu or system tray 3. Right-click tray icon → Settings to configure diff --git a/docs/RELEASING.md b/docs/RELEASING.md index 8cc2068e9..22eb3c124 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -180,7 +180,8 @@ Only tag when `HEAD == origin/master`. ## Versioning rules - Do not manually bump project or manifest versions for routine releases. -- Treat csproj `` as a local fallback for dev builds. +- Do not add csproj `` release fallbacks; product versions come from + GitVersion/tag history. - Release versions come from the tag (`vX.Y.Z` or `vX.Y.Z-alpha.N`). -- CI computes the version from git history/tags and passes it to builds. - +- CI computes GitVersion outputs for artifact naming, while product builds use + GitVersion-backed assembly metadata. diff --git a/docs/VERSIONING.md b/docs/VERSIONING.md index de1ad8f03..87e45ca29 100644 --- a/docs/VERSIONING.md +++ b/docs/VERSIONING.md @@ -1,101 +1,73 @@ # Versioning in OpenClaw Windows Hub -## How Versioning Works +## Source of truth -This project uses GitVersion for automatic semantic versioning based on git tags and commit history. The version is used in multiple places: +OpenClaw uses GitVersion and git tags for application versioning. Product +project files must not hardcode release versions with `` elements. -### Version Properties in .csproj +Canonical release tags use: -The project file (`OpenClaw.Tray.WinUI.csproj`) defines only the `` property: +- Stable: `vX.Y.Z` +- Alpha: `vX.Y.Z-alpha.N` -```xml -0.3.0 -``` - -Other version-related properties (`FileVersion` and `AssemblyVersion`) are **not** explicitly set in the csproj files. This is intentional. +`GitVersion.yml` controls how tag history becomes SemVer. The product build +imports GitVersion through `src\Directory.Build.props`, so normal `dotnet build`, +`.\build.ps1`, `.\run-app-local.ps1`, and CI builds all derive assembly metadata +from the same tag history. -### Automatic Version Derivation +## Assembly metadata -When only `` is set in a .NET project: -- **AssemblyVersion**: Automatically set to the numeric part of `Version` (e.g., `0.3.0` → `0.3.0.0`) -- **FileVersion**: Automatically set to the numeric part of `Version` (e.g., `0.3.0` → `0.3.0.0`) -- **InformationalVersion**: Set to the full `Version` value including suffixes (e.g., `0.3.0-beta.1`) +GitVersion-derived builds set: -This ensures all version properties stay in sync automatically. +- `AssemblyVersion` and `FileVersion` to numeric versions Windows/.NET can + compare. +- `AssemblyInformationalVersion` to the SemVer identity used by user-visible + surfaces. -### CI Build Process +`OpenClaw.Shared.AppVersionInfo` reads `AssemblyInformationalVersionAttribute` +from the tray assembly and exposes: -During CI builds (`.github/workflows/ci.yml`), GitVersion determines the semantic version from git history and passes it to the build: - -```bash -dotnet build -p:Version=${{ needs.test.outputs.semVer }} -``` +- `AppVersionInfo.Version` -> bare SemVer, for example `1.2.3-alpha.4` +- `AppVersionInfo.DisplayVersion` -> `v`-prefixed SemVer, for example + `v1.2.3-alpha.4` -This `-p:Version=...` argument overrides the `` property in the csproj, and consequently also sets `FileVersion` and `AssemblyVersion` to match. +Build metadata after `+` is stripped before display, but prerelease labels are +preserved. That makes alpha builds identify themselves precisely in About, +diagnostics, support context, `device.info`, MCP handshake metadata, and update +diagnostics. -### Auto-Updater Version Detection +## CI release flow -The Updatum auto-updater determines the current application version by reading the **AssemblyVersion** from the running executable using: +The release workflow computes GitVersion in the `test` job for workflow outputs +and artifact naming. Product builds themselves also use GitVersion-backed +MSBuild metadata; CI should not pass a competing hardcoded `-p:Version=...` +value that could hide drift. -```csharp -Assembly.GetExecutingAssembly().GetName().Version -``` +Release build jobs must check out full git history (`fetch-depth: 0`) so +GitVersion can see tags. -This is why it's critical that `AssemblyVersion` (and `FileVersion`) match the semantic version - otherwise, the updater will get confused and keep offering the same update repeatedly. +## Local scripts -## Historical Issue +`scripts\Get-OpenClawVersion.ps1` uses the repository-local +`.config\dotnet-tools.json` manifest and `GitVersion.Tool` to print the same +GitVersion value local scripts need outside MSBuild. -Previously, the csproj files had hardcoded values: +For example: -```xml -0.3.0 -0.2.0 -0.2.0 +```powershell +.\scripts\Get-OpenClawVersion.ps1 -Variable SemVer +.\scripts\Get-OpenClawVersion.ps1 -Variable MajorMinorPatch ``` -This caused a version mismatch: -- The semantic version was 0.3.0 -- But the file and assembly versions were stuck at 0.2.0 -- Updatum would read 0.2.0 from the running EXE -- It would see 0.4.0 available on GitHub -- It would offer to update from "0.2.0" to "0.4.0" even though the user was already on 0.3.0 or 0.4.0 - -## Solution - -By removing the hardcoded `FileVersion` and `AssemblyVersion` properties, they now automatically derive from `Version`. When CI overrides `Version` via command-line, all three properties are set correctly and consistently. - -## Best Practices +`scripts\build-inno-local.ps1` uses that helper for Inno's `AppVersion` when +`-Version` is not explicitly supplied. -1. **Never hardcode `FileVersion` or `AssemblyVersion` in the csproj** - let them auto-derive from `Version` -2. **Let GitVersion and CI control the version** - the csproj's `` is just a fallback for local development builds -3. **Test version detection** - after building, check the EXE properties to ensure FileVersion matches expectations -4. **Use semantic versioning** - tags should follow `v{major}.{minor}.{patch}` format (e.g., `v0.4.0`) -5. **Use `OpenClaw.Shared.AppVersionInfo` for any user-visible or wire-exposed version string** - never re-roll - `typeof(...).Assembly.GetName().Version` or hardcode literals like `"v0.1.0"`. `AppVersionInfo` is the single - source of truth driven by ``, used by the About page, Update dialog, support-context dump, - `device.info` capability, MCP `serverVersion` handshake, and the update-check diagnostics. +## Guardrails -## Runtime Version Resolution (AppVersionInfo) - -`src/OpenClaw.Shared/AppVersionInfo.cs` exposes: - -- `AppVersionInfo.Version` → bare string, e.g. `"0.4.7"` -- `AppVersionInfo.DisplayVersion` → `"v"` prefix, e.g. `"v0.4.7"` - -It resolves the version by: - -1. Looking for the `OpenClaw.Tray.WinUI` assembly in the current `AppDomain` (so `dotnet test` and CLI siblings - still report the tray's version rather than the testhost / dotnet host). -2. Falling back to `Assembly.GetEntryAssembly()`, then to the Shared assembly. -3. Reading `AssemblyInformationalVersionAttribute` (preferred) or `AssemblyVersion`. -4. Stripping SourceLink build metadata (`+abc123`) **and** the SemVer pre-release suffix (`-beta.1`) so the - displayed value matches what Updatum compares (Updatum reads the numeric `AssemblyVersion` only). - -For tests that need a deterministic value regardless of host process, set the `internal` test hook: - -```csharp -AppVersionInfo.TestOverride = "9.9.9"; -``` +- Do not add `` release literals to product `.csproj` files. +- Do not hardcode user-visible version strings like `vX.Y.Z` in active code or + tests; use `AppVersionInfo`. +- Keep release tags and `GitVersion.yml` as the versioning contract. ## References diff --git a/scripts/Get-OpenClawVersion.ps1 b/scripts/Get-OpenClawVersion.ps1 new file mode 100644 index 000000000..d30796bf9 --- /dev/null +++ b/scripts/Get-OpenClawVersion.ps1 @@ -0,0 +1,46 @@ +<# +.SYNOPSIS + Prints the GitVersion-derived OpenClaw version. + +.DESCRIPTION + Uses the repository-local GitVersion.Tool manifest so local scripts and CI + derive versions from the same GitVersion.yml/tag history as release builds. +#> + +[CmdletBinding()] +param( + [ValidateSet("SemVer", "MajorMinorPatch")] + [string]$Variable = "SemVer", + + [switch]$NoRestore +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..") +Push-Location $repoRoot +try { + if (-not $NoRestore) { + dotnet tool restore | Out-Host + if ($LASTEXITCODE -ne 0) { + throw "dotnet tool restore failed." + } + } + + $gitVersionOutput = & dotnet tool run dotnet-gitversion -- /output json 2>&1 + if ($LASTEXITCODE -ne 0) { + throw "GitVersion failed: $gitVersionOutput" + } + + $gitVersion = $gitVersionOutput | ConvertFrom-Json + $value = $gitVersion.$Variable + if ([string]::IsNullOrWhiteSpace($value)) { + throw "GitVersion did not return '$Variable'." + } + + Write-Output $value +} +finally { + Pop-Location +} diff --git a/scripts/build-inno-local.ps1 b/scripts/build-inno-local.ps1 index 36c45047e..7af21e409 100644 --- a/scripts/build-inno-local.ps1 +++ b/scripts/build-inno-local.ps1 @@ -73,15 +73,6 @@ function Resolve-InnoCompiler { throw "Inno Setup compiler (ISCC.exe) was not found. Install it, or rerun with -InstallInno." } -function Get-ProjectVersion { - $projectPath = Join-Path $repoRoot "src\OpenClaw.Tray.WinUI\OpenClaw.Tray.WinUI.csproj" - [xml]$project = Get-Content -LiteralPath $projectPath -Raw - $project.Project.PropertyGroup | - ForEach-Object { $_.Version } | - Where-Object { $_ } | - Select-Object -First 1 -} - function Get-RidForArch { param([string]$Architecture) if ($Architecture -eq "arm64") { @@ -104,24 +95,36 @@ function Publish-ArchitecturePayload { Remove-Item -LiteralPath $publishDir, $setupPublishDir -Recurse -Force -ErrorAction SilentlyContinue New-Item -ItemType Directory -Path $publishDir | Out-Null - dotnet publish .\src\OpenClaw.Tray.WinUI\OpenClaw.Tray.WinUI.csproj ` - -c $Configuration ` - -r $RuntimeIdentifier ` - --self-contained ` - -p:Version=$PublishVersion ` - -o $publishDir ` - -v:minimal + $trayPublishArgs = @( + ".\src\OpenClaw.Tray.WinUI\OpenClaw.Tray.WinUI.csproj", + "-c", $Configuration, + "-r", $RuntimeIdentifier, + "--self-contained", + "-o", $publishDir, + "-v:minimal" + ) + if ($PublishVersion) { + $trayPublishArgs += "-p:Version=$PublishVersion" + } + + dotnet publish @trayPublishArgs if ($LASTEXITCODE -ne 0) { throw "Tray publish failed for $Architecture." } - dotnet publish .\src\OpenClaw.SetupEngine.UI\OpenClaw.SetupEngine.UI.csproj ` - -c $Configuration ` - -r $RuntimeIdentifier ` - --self-contained ` - -p:Version=$PublishVersion ` - -o $setupPublishDir ` - -v:minimal + $setupPublishArgs = @( + ".\src\OpenClaw.SetupEngine.UI\OpenClaw.SetupEngine.UI.csproj", + "-c", $Configuration, + "-r", $RuntimeIdentifier, + "--self-contained", + "-o", $setupPublishDir, + "-v:minimal" + ) + if ($PublishVersion) { + $setupPublishArgs += "-p:Version=$PublishVersion" + } + + dotnet publish @setupPublishArgs if ($LASTEXITCODE -ne 0) { throw "SetupEngine.UI publish failed for $Architecture." } @@ -178,8 +181,11 @@ function Invoke-InnoCompiler { } } +$versionWasProvided = $PSBoundParameters.ContainsKey("Version") + if (-not $Version) { - $Version = Get-ProjectVersion + $versionScript = Join-Path $PSScriptRoot "Get-OpenClawVersion.ps1" + $Version = & $versionScript -Variable SemVer } if (-not $Version) { @@ -198,7 +204,8 @@ Write-Host "No publish: $($NoPublish.IsPresent)" foreach ($architecture in $architectures) { $rid = Get-RidForArch $architecture if (-not $NoPublish) { - Publish-ArchitecturePayload -Architecture $architecture -RuntimeIdentifier $rid -PublishVersion $Version + $publishVersion = if ($versionWasProvided) { $Version } else { $null } + Publish-ArchitecturePayload -Architecture $architecture -RuntimeIdentifier $rid -PublishVersion $publishVersion } $payload = Assert-PayloadReady $architecture diff --git a/src/Directory.Build.props b/src/Directory.Build.props index ed09971dd..ea26792f5 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -12,4 +12,8 @@ all + + + + diff --git a/src/OpenClaw.Shared/AppVersionInfo.cs b/src/OpenClaw.Shared/AppVersionInfo.cs index 3f292bc58..371c9de92 100644 --- a/src/OpenClaw.Shared/AppVersionInfo.cs +++ b/src/OpenClaw.Shared/AppVersionInfo.cs @@ -7,8 +7,7 @@ namespace OpenClaw.Shared; /// Single source of truth for the OpenClaw Companion app version that is /// surfaced to users. Reads /// (or falls back to AssemblyVersion) from the tray executable so every -/// UI/diagnostic/handshake site reports the same number driven by the csproj -/// <Version> property. +/// UI/diagnostic/handshake site reports the same GitVersion-derived value. /// /// /// Under dotnet test and inside CLI siblings, @@ -29,10 +28,10 @@ public static class AppVersionInfo /// internal static string? TestOverride { get; set; } - /// Bare version string, e.g. "0.4.7". + /// Bare version string, e.g. "1.2.3-alpha.4". public static string Version => TestOverride ?? _version; - /// Version prefixed with v, e.g. "v0.4.7". + /// Version prefixed with v, e.g. "v1.2.3-alpha.4". public static string DisplayVersion => "v" + Version; private static string ResolveVersion() @@ -48,7 +47,7 @@ private static string ResolveVersion() ?.InformationalVersion; if (!string.IsNullOrWhiteSpace(informational)) { - return NormalizeSemVer(informational); + return NormalizeInformationalVersion(informational); } var name = assembly.GetName().Version; @@ -82,20 +81,12 @@ private static string ResolveVersion() return null; } - private static string NormalizeSemVer(string s) + internal static string NormalizeInformationalVersion(string s) { - // Strip SourceLink build metadata, e.g. "0.4.7+abc123" -> "0.4.7". + // Strip SourceLink/GitVersion build metadata while preserving prerelease labels. var plus = s.IndexOf('+'); if (plus >= 0) s = s.Substring(0, plus); - // Strip the SemVer pre-release suffix, e.g. "0.4.7-beta.1" -> "0.4.7". - // This keeps the UI string aligned with Updatum, which compares the - // numeric AssemblyVersion only. Revisit if pre-release labels should - // ever be surfaced to users. - var dash = s.IndexOf('-'); - if (dash >= 0) s = s.Substring(0, dash); - return s; } } - diff --git a/src/OpenClaw.Tray.WinUI/OpenClaw.Tray.WinUI.csproj b/src/OpenClaw.Tray.WinUI/OpenClaw.Tray.WinUI.csproj index 83a911f1f..121b2b837 100644 --- a/src/OpenClaw.Tray.WinUI/OpenClaw.Tray.WinUI.csproj +++ b/src/OpenClaw.Tray.WinUI/OpenClaw.Tray.WinUI.csproj @@ -9,7 +9,6 @@ true Assets\openclaw.ico OpenClawTray - 0.4.7 en-US x64;ARM64 win-x64;win-arm64 @@ -88,12 +87,12 @@ - + <_AppxManifestPath>$(MSBuildThisFileDirectory)Package.appxmanifest <_StrippedVersion>$([System.Text.RegularExpressions.Regex]::Replace('$(Version)', '[-+].*$', '')) diff --git a/tests/OpenClaw.Shared.Tests/AppVersionInfoTests.cs b/tests/OpenClaw.Shared.Tests/AppVersionInfoTests.cs index 45b22e6eb..e3612baac 100644 --- a/tests/OpenClaw.Shared.Tests/AppVersionInfoTests.cs +++ b/tests/OpenClaw.Shared.Tests/AppVersionInfoTests.cs @@ -66,11 +66,23 @@ public void Version_AfterClearingOverride_DoesNotReturnClearedValue() [Theory] [InlineData("1.2.3", "1.2.3")] - [InlineData("0.4.7", "0.4.7")] + [InlineData("1.2.3-alpha.4", "1.2.3-alpha.4")] + [InlineData("2.3.4", "2.3.4")] [InlineData("10.0.0", "10.0.0")] public void DisplayVersion_AlwaysEqualsVPlusVersion(string version, string expected) { AppVersionInfo.TestOverride = version; Assert.Equal("v" + expected, AppVersionInfo.DisplayVersion); } + + [Theory] + [InlineData("1.2.3+abcdef", "1.2.3")] + [InlineData("1.2.3-alpha.4+abcdef", "1.2.3-alpha.4")] + [InlineData("1.2.3-alpha.4", "1.2.3-alpha.4")] + public void NormalizeInformationalVersion_StripsBuildMetadataButKeepsPrerelease( + string informationalVersion, + string expected) + { + Assert.Equal(expected, AppVersionInfo.NormalizeInformationalVersion(informationalVersion)); + } } diff --git a/tests/OpenClaw.Tray.Tests/VersioningContractTests.cs b/tests/OpenClaw.Tray.Tests/VersioningContractTests.cs new file mode 100644 index 000000000..a669996fe --- /dev/null +++ b/tests/OpenClaw.Tray.Tests/VersioningContractTests.cs @@ -0,0 +1,100 @@ +using System.Text.RegularExpressions; + +namespace OpenClaw.Tray.Tests; + +public sealed class VersioningContractTests +{ + private static readonly Regex ProjectVersionElement = new( + @"\s*\d+\.\d+\.\d+[^<]*", + RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + + private static string GetRepositoryRoot() + { + var envRepoRoot = Environment.GetEnvironmentVariable("OPENCLAW_REPO_ROOT"); + if (!string.IsNullOrWhiteSpace(envRepoRoot) && Directory.Exists(envRepoRoot)) + return envRepoRoot; + + var directory = new DirectoryInfo(AppContext.BaseDirectory); + while (directory != null) + { + if ((Directory.Exists(Path.Combine(directory.FullName, ".git")) || + File.Exists(Path.Combine(directory.FullName, ".git"))) && + File.Exists(Path.Combine(directory.FullName, "README.md"))) + return directory.FullName; + directory = directory.Parent; + } + + throw new InvalidOperationException( + "Could not find repository root. Set OPENCLAW_REPO_ROOT to the repo path."); + } + + private static bool IsGeneratedOrIgnoredPath(string path) + { + var segments = path.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + return segments.Contains("bin", StringComparer.OrdinalIgnoreCase) || + segments.Contains("obj", StringComparer.OrdinalIgnoreCase) || + segments.Contains("node_modules", StringComparer.OrdinalIgnoreCase); + } + + [Fact] + public void ProductProjects_DoNotHardcodeReleaseVersion() + { + var repoRoot = GetRepositoryRoot(); + var projectFiles = Directory.EnumerateFiles( + Path.Combine(repoRoot, "src"), + "*.csproj", + SearchOption.AllDirectories); + + var offenders = projectFiles + .Where(path => ProjectVersionElement.IsMatch(File.ReadAllText(path))) + .Select(path => Path.GetRelativePath(repoRoot, path)) + .OrderBy(path => path) + .ToArray(); + + Assert.True( + offenders.Length == 0, + "Product project files must not hardcode release versions. Offenders:" + + Environment.NewLine + + string.Join(Environment.NewLine, offenders)); + } + + [Fact] + public void ActiveCodeAndTests_DoNotContainStaleReleaseVersion() + { + var repoRoot = GetRepositoryRoot(); + var staleBareVersion = string.Concat("0.", "4.7"); + var staleDisplayVersion = "v" + staleBareVersion; + var searchableExtensions = new HashSet(StringComparer.OrdinalIgnoreCase) + { + ".cs", + ".csproj", + ".props", + ".targets", + ".ps1", + ".iss", + ".yml" + }; + + var offenders = new[] { "src", "tests", "scripts", ".github" } + .Select(root => Path.Combine(repoRoot, root)) + .Where(Directory.Exists) + .SelectMany(root => Directory.EnumerateFiles(root, "*", SearchOption.AllDirectories)) + .Where(path => !IsGeneratedOrIgnoredPath(Path.GetRelativePath(repoRoot, path))) + .Where(path => searchableExtensions.Contains(Path.GetExtension(path))) + .Where(path => + { + var text = File.ReadAllText(path); + return text.Contains(staleBareVersion, StringComparison.Ordinal) || + text.Contains(staleDisplayVersion, StringComparison.Ordinal); + }) + .Select(path => Path.GetRelativePath(repoRoot, path)) + .OrderBy(path => path) + .ToArray(); + + Assert.True( + offenders.Length == 0, + "Active code/tests must not contain the stale release literal. Offenders:" + + Environment.NewLine + + string.Join(Environment.NewLine, offenders)); + } +} diff --git a/tests/OpenClaw.Tray.UITests/A2UIDashboardScaleTest.cs b/tests/OpenClaw.Tray.UITests/A2UIDashboardScaleTest.cs index bea35e8c2..7bd701c0f 100644 --- a/tests/OpenClaw.Tray.UITests/A2UIDashboardScaleTest.cs +++ b/tests/OpenClaw.Tray.UITests/A2UIDashboardScaleTest.cs @@ -6,6 +6,7 @@ using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Shapes; +using OpenClaw.Shared; using static OpenClaw.Tray.UITests.A2UI; using static OpenClaw.Tray.UITests.TestSupport; @@ -320,7 +321,7 @@ private static string BuildDashboardJsonl() // ── Footer row ──────────────────────────────────────────────────── components.Add(Component("ftr", "Row", new() { ["children"] = Children("ftrVer", "ftrDiv", "ftrConn") })); - components.Add(Component("ftrVer", "Text", new() { ["text"] = Lit("v0.4.7"), ["usageHint"] = "caption" })); + components.Add(Component("ftrVer", "Text", new() { ["text"] = Lit(AppVersionInfo.DisplayVersion), ["usageHint"] = "caption" })); components.Add(Component("ftrDiv", "Divider", new() { ["axis"] = "vertical" })); components.Add(Component("ftrConn", "Text", new() { ["text"] = Lit("Connected"), ["usageHint"] = "caption" }));