Skip to content

Commit c338a7b

Browse files
shanselmanCopilot
andcommitted
Prepare alpha fallback update testing
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 303f45f commit c338a7b

9 files changed

Lines changed: 143 additions & 26 deletions

File tree

docs/RELEASING.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,27 @@ git push origin vX.Y.Z-alpha.1
2727

2828
The stable MSIX package identity is `OpenClaw.Companion`. Alpha-tagged MSIX packages are patched during CI to use `OpenClaw.Companion.Alpha`, which lets testers install the signed alpha package without upgrading a stable MSIX install.
2929

30+
## Alpha update testing
31+
32+
Normal update checks stay on stable releases. To test alpha release updates from a local Release build, isolate the run and opt into the alpha update channel before launching:
33+
34+
```powershell
35+
$env:OPENCLAW_TRAY_DATA_DIR = "$env:TEMP\OpenClawTray-alpha-test"
36+
$env:OPENCLAW_UPDATE_CHANNEL = "alpha"
37+
.\run-app-local.ps1 -Configuration Release -AllowNonMaster
38+
```
39+
40+
`OPENCLAW_UPDATE_CHANNEL=alpha` enables pre-release checks and release-list fetching. The in-app updater downloads ZIP assets only; MSIX alpha packages are installed or sideloaded separately under the `OpenClaw.Companion.Alpha` identity.
41+
42+
To force a local lower-version baseline against an already-published alpha, build the Release output with an explicit older version, then launch that output:
43+
44+
```powershell
45+
dotnet build .\src\OpenClaw.Tray.WinUI\OpenClaw.Tray.WinUI.csproj -c Release -r win-x64 -p:Version=0.5.0
46+
$env:OPENCLAW_TRAY_DATA_DIR = "$env:TEMP\OpenClawTray-alpha-test"
47+
$env:OPENCLAW_UPDATE_CHANNEL = "alpha"
48+
.\run-app-local.ps1 -Configuration Release -NoBuild -AllowNonMaster
49+
```
50+
3051
## Why this is the correct flow
3152

3253
- `GitVersion.yml` is configured for `ContinuousDelivery` with `tag-prefix: 'v'`.

docs/VERSIONING.md

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,20 @@ This project uses GitVersion for automatic semantic versioning based on git tags
66

77
### Version Properties in .csproj
88

9-
The project file (`OpenClaw.Tray.WinUI.csproj`) defines only the `<Version>` property:
9+
The project file (`OpenClaw.Tray.WinUI.csproj`) defines only the `<Version>` property. CI release builds override this value from the pushed tag; the checked-in value is the local-development fallback:
1010

1111
```xml
12-
<Version>0.3.0</Version>
12+
<Version>0.6.0-alpha.1</Version>
1313
```
1414

1515
Other version-related properties (`FileVersion` and `AssemblyVersion`) are **not** explicitly set in the csproj files. This is intentional.
1616

1717
### Automatic Version Derivation
1818

1919
When only `<Version>` is set in a .NET project:
20-
- **AssemblyVersion**: Automatically set to the numeric part of `Version` (e.g., `0.3.0` `0.3.0.0`)
21-
- **FileVersion**: Automatically set to the numeric part of `Version` (e.g., `0.3.0` `0.3.0.0`)
22-
- **InformationalVersion**: Set to the full `Version` value including suffixes (e.g., `0.3.0-beta.1`)
20+
- **AssemblyVersion**: Automatically set to the numeric part of `Version` (e.g., `0.6.0-alpha.1` -> `0.6.0.0`)
21+
- **FileVersion**: Automatically set to the numeric part of `Version` (e.g., `0.6.0-alpha.1` -> `0.6.0.0`)
22+
- **InformationalVersion**: Set to the full `Version` value including suffixes (e.g., `0.6.0-alpha.1`)
2323

2424
This ensures all version properties stay in sync automatically.
2525

@@ -31,7 +31,7 @@ During CI builds (`.github/workflows/ci.yml`), GitVersion determines the semanti
3131
dotnet build -p:Version=${{ needs.test.outputs.semVer }}
3232
```
3333

34-
This `-p:Version=...` argument overrides the `<Version>` property in the csproj, and consequently also sets `FileVersion` and `AssemblyVersion` to match.
34+
This `-p:Version=...` argument overrides the `<Version>` property in the csproj, and consequently also sets `FileVersion`, `AssemblyVersion`, and `InformationalVersion` from the release tag.
3535

3636
### Auto-Updater Version Detection
3737

@@ -41,7 +41,9 @@ The Updatum auto-updater determines the current application version by reading t
4141
Assembly.GetExecutingAssembly().GetName().Version
4242
```
4343

44-
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.
44+
This is why it's critical that `AssemblyVersion` (and `FileVersion`) match the numeric release version - otherwise, the updater will get confused and keep offering the same update repeatedly. Pre-release labels are preserved in `InformationalVersion` for display and diagnostics, but the updater's comparison is numeric.
45+
46+
Stable update checks use GitHub's latest release endpoint and ignore pre-releases. Alpha testing is opt-in: set `OPENCLAW_UPDATE_CHANNEL=alpha` before launching a Release build. That enables Updatum pre-release checks and makes it fetch the release list instead of only the latest stable release. The updater is also constrained to `.zip` release assets, because the portable ZIP payloads are the assets intended for in-app replacement; MSIX packages are installed/sideloaded separately.
4547

4648
## Historical Issue
4749

@@ -79,17 +81,17 @@ By removing the hardcoded `FileVersion` and `AssemblyVersion` properties, they n
7981

8082
`src/OpenClaw.Shared/AppVersionInfo.cs` exposes:
8183

82-
- `AppVersionInfo.Version` bare string, e.g. `"0.4.7"`
83-
- `AppVersionInfo.DisplayVersion` `"v"` prefix, e.g. `"v0.4.7"`
84+
- `AppVersionInfo.Version` -> bare string, e.g. `"0.6.0-alpha.1"`
85+
- `AppVersionInfo.DisplayVersion` -> `"v"` prefix, e.g. `"v0.6.0-alpha.1"`
8486

8587
It resolves the version by:
8688

8789
1. Looking for the `OpenClaw.Tray.WinUI` assembly in the current `AppDomain` (so `dotnet test` and CLI siblings
8890
still report the tray's version rather than the testhost / dotnet host).
8991
2. Falling back to `Assembly.GetEntryAssembly()`, then to the Shared assembly.
9092
3. Reading `AssemblyInformationalVersionAttribute` (preferred) or `AssemblyVersion`.
91-
4. Stripping SourceLink build metadata (`+abc123`) **and** the SemVer pre-release suffix (`-beta.1`) so the
92-
displayed value matches what Updatum compares (Updatum reads the numeric `AssemblyVersion` only).
93+
4. Stripping SourceLink build metadata (`+abc123`) while preserving SemVer pre-release suffixes (`-alpha.1`) so
94+
alpha builds are visibly identifiable in About, diagnostics, handshakes, and update-check status.
9395

9496
For tests that need a deterministic value regardless of host process, set the `internal` test hook:
9597

src/OpenClaw.Shared/AppVersionInfo.cs

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ public static class AppVersionInfo
2929
/// </summary>
3030
internal static string? TestOverride { get; set; }
3131

32-
/// <summary>Bare version string, e.g. <c>"0.4.7"</c>.</summary>
32+
/// <summary>Bare version string, e.g. <c>"0.6.0-alpha.1"</c>.</summary>
3333
public static string Version => TestOverride ?? _version;
3434

35-
/// <summary>Version prefixed with <c>v</c>, e.g. <c>"v0.4.7"</c>.</summary>
35+
/// <summary>Version prefixed with <c>v</c>, e.g. <c>"v0.6.0-alpha.1"</c>.</summary>
3636
public static string DisplayVersion => "v" + Version;
3737

3838
private static string ResolveVersion()
@@ -48,7 +48,7 @@ private static string ResolveVersion()
4848
?.InformationalVersion;
4949
if (!string.IsNullOrWhiteSpace(informational))
5050
{
51-
return NormalizeSemVer(informational);
51+
return NormalizeInformationalVersion(informational);
5252
}
5353

5454
var name = assembly.GetName().Version;
@@ -82,20 +82,12 @@ private static string ResolveVersion()
8282
return null;
8383
}
8484

85-
private static string NormalizeSemVer(string s)
85+
internal static string NormalizeInformationalVersion(string s)
8686
{
87-
// Strip SourceLink build metadata, e.g. "0.4.7+abc123" -> "0.4.7".
87+
// Strip SourceLink build metadata, e.g. "0.6.0-alpha.1+abc123" -> "0.6.0-alpha.1".
8888
var plus = s.IndexOf('+');
8989
if (plus >= 0) s = s.Substring(0, plus);
9090

91-
// Strip the SemVer pre-release suffix, e.g. "0.4.7-beta.1" -> "0.4.7".
92-
// This keeps the UI string aligned with Updatum, which compares the
93-
// numeric AssemblyVersion only. Revisit if pre-release labels should
94-
// ever be surfaced to users.
95-
var dash = s.IndexOf('-');
96-
if (dash >= 0) s = s.Substring(0, dash);
97-
9891
return s;
9992
}
10093
}
101-

src/OpenClaw.Tray.WinUI/App.xaml.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,12 @@ namespace OpenClawTray;
3232

3333
public partial class App : Application, OpenClawTray.Services.IAppCommands
3434
{
35+
internal static readonly UpdateChannelSettings UpdateChannel = UpdateChannelPolicy.Resolve();
3536
internal static readonly UpdatumManager AppUpdater = new("shanselman", "openclaw-windows-hub")
3637
{
37-
FetchOnlyLatestRelease = true,
38+
FetchOnlyLatestRelease = UpdateChannel.FetchOnlyLatestRelease,
39+
AllowPreReleases = UpdateChannel.AllowPreReleases,
40+
AssetExtensionFilter = UpdateChannel.AssetExtensionFilter,
3841
InstallUpdateSingleFileExecutableName = "OpenClaw.Tray.WinUI",
3942
};
4043

src/OpenClaw.Tray.WinUI/OpenClaw.Tray.WinUI.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<EnableMsixTooling>true</EnableMsixTooling>
1010
<ApplicationIcon>Assets\openclaw.ico</ApplicationIcon>
1111
<RootNamespace>OpenClawTray</RootNamespace>
12-
<Version>0.5.0</Version>
12+
<Version>0.6.0-alpha.1</Version>
1313
<DefaultLanguage>en-US</DefaultLanguage>
1414
<Platforms>x64;ARM64</Platforms>
1515
<RuntimeIdentifiers>win-x64;win-arm64</RuntimeIdentifiers>
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System;
2+
3+
namespace OpenClawTray.Services;
4+
5+
internal readonly record struct UpdateChannelSettings(
6+
bool AllowPreReleases,
7+
bool FetchOnlyLatestRelease,
8+
string AssetExtensionFilter);
9+
10+
internal static class UpdateChannelPolicy
11+
{
12+
internal const string ChannelEnvironmentVariable = "OPENCLAW_UPDATE_CHANNEL";
13+
internal const string StableChannel = "stable";
14+
internal const string AlphaChannel = "alpha";
15+
internal const string PreReleaseChannel = "prerelease";
16+
internal const string ReleaseAssetExtension = "zip";
17+
18+
internal static UpdateChannelSettings Resolve(Func<string, string?>? envLookup = null)
19+
{
20+
envLookup ??= Environment.GetEnvironmentVariable;
21+
var channel = envLookup(ChannelEnvironmentVariable);
22+
var allowPreReleases = IsPreReleaseChannel(channel);
23+
24+
return new UpdateChannelSettings(
25+
AllowPreReleases: allowPreReleases,
26+
FetchOnlyLatestRelease: !allowPreReleases,
27+
AssetExtensionFilter: ReleaseAssetExtension);
28+
}
29+
30+
internal static bool IsPreReleaseChannel(string? channel)
31+
{
32+
if (string.IsNullOrWhiteSpace(channel))
33+
return false;
34+
35+
var normalized = channel.Trim();
36+
return string.Equals(normalized, AlphaChannel, StringComparison.OrdinalIgnoreCase) ||
37+
string.Equals(normalized, PreReleaseChannel, StringComparison.OrdinalIgnoreCase) ||
38+
string.Equals(normalized, "pre-release", StringComparison.OrdinalIgnoreCase);
39+
}
40+
}

tests/OpenClaw.Shared.Tests/AppVersionInfoTests.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,23 @@ public void Version_AfterClearingOverride_DoesNotReturnClearedValue()
6767
[Theory]
6868
[InlineData("1.2.3", "1.2.3")]
6969
[InlineData("0.4.7", "0.4.7")]
70+
[InlineData("0.6.0-alpha.1", "0.6.0-alpha.1")]
7071
[InlineData("10.0.0", "10.0.0")]
7172
public void DisplayVersion_AlwaysEqualsVPlusVersion(string version, string expected)
7273
{
7374
AppVersionInfo.TestOverride = version;
7475
Assert.Equal("v" + expected, AppVersionInfo.DisplayVersion);
7576
}
77+
78+
[Theory]
79+
[InlineData("0.6.0", "0.6.0")]
80+
[InlineData("0.6.0+abc123", "0.6.0")]
81+
[InlineData("0.6.0-alpha.1", "0.6.0-alpha.1")]
82+
[InlineData("0.6.0-alpha.1+abc123", "0.6.0-alpha.1")]
83+
public void NormalizeInformationalVersion_StripsBuildMetadataButKeepsPreRelease(
84+
string input,
85+
string expected)
86+
{
87+
Assert.Equal(expected, AppVersionInfo.NormalizeInformationalVersion(input));
88+
}
7689
}

tests/OpenClaw.Tray.Tests/OpenClaw.Tray.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
<Compile Include="..\..\src\OpenClaw.Tray.WinUI\Services\GatewayNavVisibilityDebouncePolicy.cs" Link="Services\GatewayNavVisibilityDebouncePolicy.cs" />
5555
<Compile Include="..\..\src\OpenClaw.Tray.WinUI\Services\NodeInvokeActivityFormatter.cs" Link="Services\NodeInvokeActivityFormatter.cs" />
5656
<Compile Include="..\..\src\OpenClaw.Tray.WinUI\Services\NodeCapabilityGating.cs" Link="Services\NodeCapabilityGating.cs" />
57+
<Compile Include="..\..\src\OpenClaw.Tray.WinUI\Services\UpdateChannelPolicy.cs" Link="Services\UpdateChannelPolicy.cs" />
5758
<Compile Include="..\..\src\OpenClaw.Tray.WinUI\Services\ToastActivationRouter.cs" Link="Services\ToastActivationRouter.cs" />
5859
<Compile Include="..\..\src\OpenClaw.Tray.WinUI\Helpers\TrayTooltipFormatter.cs" Link="Helpers\TrayTooltipFormatter.cs" />
5960
<Compile Include="..\..\src\OpenClaw.Tray.WinUI\Helpers\GatewayDashboardUrlBuilder.cs" Link="Helpers\GatewayDashboardUrlBuilder.cs" />
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using OpenClawTray.Services;
2+
3+
namespace OpenClaw.Tray.Tests;
4+
5+
public sealed class UpdateChannelPolicyTests
6+
{
7+
[Fact]
8+
public void Resolve_DefaultsToStableLatestOnlyZipUpdates()
9+
{
10+
var settings = UpdateChannelPolicy.Resolve(_ => null);
11+
12+
Assert.False(settings.AllowPreReleases);
13+
Assert.True(settings.FetchOnlyLatestRelease);
14+
Assert.Equal("zip", settings.AssetExtensionFilter);
15+
}
16+
17+
[Theory]
18+
[InlineData("alpha")]
19+
[InlineData("Alpha")]
20+
[InlineData(" prerelease ")]
21+
[InlineData("pre-release")]
22+
public void Resolve_AlphaChannelAllowsPreReleasesAndFetchesReleaseList(string channel)
23+
{
24+
var settings = UpdateChannelPolicy.Resolve(key =>
25+
key == UpdateChannelPolicy.ChannelEnvironmentVariable ? channel : null);
26+
27+
Assert.True(settings.AllowPreReleases);
28+
Assert.False(settings.FetchOnlyLatestRelease);
29+
Assert.Equal("zip", settings.AssetExtensionFilter);
30+
}
31+
32+
[Theory]
33+
[InlineData("")]
34+
[InlineData("stable")]
35+
[InlineData("beta")]
36+
public void Resolve_NonAlphaChannelStaysOnStableLatestOnly(string? channel)
37+
{
38+
var settings = UpdateChannelPolicy.Resolve(key =>
39+
key == UpdateChannelPolicy.ChannelEnvironmentVariable ? channel : null);
40+
41+
Assert.False(settings.AllowPreReleases);
42+
Assert.True(settings.FetchOnlyLatestRelease);
43+
Assert.Equal("zip", settings.AssetExtensionFilter);
44+
}
45+
}

0 commit comments

Comments
 (0)