diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0fe18bb..b9dfbf5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -153,7 +153,10 @@ jobs: with: solution: gclo.slnx app-project: gclo/gclo.csproj - app-id: gclo + # Prereleases pack as their own Velopack app (gclo-dev): separate install + # folder, Start menu entry, update feed and data root, so a dev build can be + # installed beside the stable one (#114). Asset names follow the app id. + app-id: ${{ contains(github.ref_name, '-') && 'gclo-dev' || 'gclo' }} main-exe: gclo.exe icon: gclo/Assets/Brand/gclo.ico platform: x64 @@ -193,6 +196,7 @@ jobs: TAG: ${{ github.ref_name }} K22_VERSION: ${{ needs.release.outputs.version }} K22_CHANNEL: ${{ needs.release.outputs.prerelease == 'true' && 'dev' || 'stable' }} + K22_APP_ID: ${{ needs.release.outputs.prerelease == 'true' && 'gclo-dev' || 'gclo' }} EXPECTED_SUBJECT: ${{ vars.TRUSTED_SIGNING_SUBJECT }} steps: - name: Download the published assets @@ -202,9 +206,9 @@ jobs: GH_REPO: ${{ github.repository }} run: | New-Item -ItemType Directory -Force assets | Out-Null - gh release download $env:TAG --dir assets --pattern "gclo-$env:K22_CHANNEL-Setup.exe" --pattern "gclo-$env:K22_CHANNEL-Portable.zip" --pattern "gclo-cli-win-x64-$env:K22_VERSION.zip" + gh release download $env:TAG --dir assets --pattern "$env:K22_APP_ID-$env:K22_CHANNEL-Setup.exe" --pattern "$env:K22_APP_ID-$env:K22_CHANNEL-Portable.zip" --pattern "gclo-cli-win-x64-$env:K22_VERSION.zip" if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - Expand-Archive assets/gclo-$env:K22_CHANNEL-Portable.zip -DestinationPath assets/portable + Expand-Archive assets/$env:K22_APP_ID-$env:K22_CHANNEL-Portable.zip -DestinationPath assets/portable Expand-Archive assets/gclo-cli-win-x64-$env:K22_VERSION.zip -DestinationPath assets/cli - name: signtool verify /pa /all on every shipped executable @@ -217,7 +221,7 @@ jobs: } $signtool = Get-ChildItem "${env:ProgramFiles(x86)}\Windows Kits\10\bin" -Recurse -Filter signtool.exe | Where-Object { $_.FullName -match '\\x64\\' } | Sort-Object FullName -Descending | Select-Object -First 1 - $files = @(Get-Item assets/gclo-$env:K22_CHANNEL-Setup.exe) + @(Get-ChildItem assets/portable, assets/cli -Recurse -Include *.exe, *.dll | + $files = @(Get-Item assets/$env:K22_APP_ID-$env:K22_CHANNEL-Setup.exe) + @(Get-ChildItem assets/portable, assets/cli -Recurse -Include *.exe, *.dll | Where-Object { $_.Name -like 'gclo*' -or $_.Name -in 'Update.exe' }) $failed = 0 foreach ($f in $files) diff --git a/docs/RELEASING.md b/docs/RELEASING.md index aa730c8..d0ba1e4 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -23,6 +23,21 @@ Any prerelease identifier works (`-alpha`, `-beta.2`, `-rc.1`, ...) — the rule is simply: **a `-` in the version means prerelease, which means the `dev` channel**. +### The dev edition is a separate app + +A prerelease is packed as its own Velopack app, id **`gclo-dev`**, while stable +releases use **`gclo`**. The two install side by side: separate install folder +(`%LOCALAPPDATA%\gclo-dev` vs `%LOCALAPPDATA%\gclo`), Start menu entry, update +feed and data root (`%LOCALAPPDATA%\KofTwentyTwo\gclo-dev` vs `…\gclo`), and +the dev edition's window title reads "gclo (dev)". A machine can therefore keep +the stable app for real work and a dev build for trying the next release; each +only ever updates within its own channel. The dev edition's assets follow its +app id: `gclo-dev-dev-Setup.exe`, `gclo-dev-dev-Portable.zip`, +`gclo-dev-1.2.3-beta.1-dev-full.nupkg`, `releases.dev.json`. The CLI zip is the +same for both (`gclo-cli-win-x64-.zip`). Dev installs made before this +split (app id `gclo` on the dev channel, up to 1.0.2-beta.3) cannot update into +`gclo-dev`: uninstall them, or install the stable app over them. + ### How self-update channels map The desktop app updates itself with [Velopack](https://velopack.io). Each diff --git a/gclo.Engine.Tests/GcloPathsTests.cs b/gclo.Engine.Tests/GcloPathsTests.cs index 3c84e13..b87cd3f 100644 --- a/gclo.Engine.Tests/GcloPathsTests.cs +++ b/gclo.Engine.Tests/GcloPathsTests.cs @@ -30,6 +30,21 @@ public sealed class GcloPathsTests + /// The dev edition keeps its own data root beside the stable one (#114). + [Fact] + public void DataFolderNameFor_DevEdition_UsesItsOwnFolder() + { + // Edition itself is process-wide state set once by the app's entry point; the + // tests exercise the pure mapping so they cannot race the vault's scoping. + Assert.Equal("gclo", GcloPaths.DataFolderNameFor("")); + Assert.Equal("gclo-dev", GcloPaths.DataFolderNameFor("dev")); + Assert.Throws(() => GcloPaths.DataFolderNameFor(null!)); + Assert.Equal("", GcloPaths.Edition); + Assert.Equal("gclo", GcloPaths.DataFolderName); + } + + + /// The default root is never the install root nor anything under it. [Fact] public void DefaultDataRoot_IsNotTheVelopackInstallRoot() diff --git a/gclo.ViewModels/GcloPaths.cs b/gclo.ViewModels/GcloPaths.cs index 62467d0..77404a8 100644 --- a/gclo.ViewModels/GcloPaths.cs +++ b/gclo.ViewModels/GcloPaths.cs @@ -34,11 +34,36 @@ public static class GcloPaths /// - /// %LOCALAPPDATA%\KofTwentyTwo\gclo: the root used when GCLO_DATA_DIR is not - /// set. Outside the Velopack install root on purpose (see the type remarks). + /// The installed edition: "" for the stable app (Velopack app id gclo) and + /// "dev" for the dev-channel app (gclo-dev), which installs beside the + /// stable one and keeps its own data root so both can run on one machine (#114). + /// Set once by the app's entry point before anything reads ; + /// local and CLI runs leave it empty. + /// + public static string Edition { get; set; } = ""; + + + + /// The folder name under %LOCALAPPDATA%\KofTwentyTwo: "gclo", or "gclo-dev" for the dev edition. + public static string DataFolderName => DataFolderNameFor(Edition); + + + + /// "gclo" for the stable edition (empty ), "gclo-<edition>" otherwise. + public static string DataFolderNameFor(string edition) + { + ArgumentNullException.ThrowIfNull(edition); + return edition.Length == 0 ? "gclo" : $"gclo-{edition}"; + } + + + + /// + /// %LOCALAPPDATA%\KofTwentyTwo\gclo (or gclo-dev): the root used when GCLO_DATA_DIR + /// is not set. Outside the Velopack install root on purpose (see the type remarks). /// public static string DefaultDataRoot => Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - "KofTwentyTwo", "gclo"); + "KofTwentyTwo", DataFolderName); } diff --git a/gclo/MainWindow.xaml.cs b/gclo/MainWindow.xaml.cs index f03711e..0e8551b 100644 --- a/gclo/MainWindow.xaml.cs +++ b/gclo/MainWindow.xaml.cs @@ -121,7 +121,10 @@ public sealed partial class MainWindow : Window InitializeComponent(); ApplyBackdrop(); - Title = "gclo — Git Clone Large Organizations"; + // The dev edition installs beside the stable app, so the title says which one this is. + Title = GcloPaths.Edition.Length == 0 + ? "gclo — Git Clone Large Organizations" + : $"gclo ({GcloPaths.Edition}) — Git Clone Large Organizations"; // Quick Sync is declared in XAML; its Guid tag and the account items are // runtime data, so they are filled in here. diff --git a/gclo/Program.cs b/gclo/Program.cs index 6026c45..c864d3a 100644 --- a/gclo/Program.cs +++ b/gclo/Program.cs @@ -4,6 +4,7 @@ */ using System; +using gclo.ViewModels; using Velopack; @@ -25,6 +26,14 @@ public static class Program // Must be first: handles Velopack install/update/uninstall hooks and may exit. VelopackApp.Build().Run(); + // The dev channel is packed as its own app (id gclo-dev) so it installs beside + // the stable one; it keeps its own data root and says so in the title (#114). + // Local and CLI runs have no Velopack app id and stay on the stable paths. + if(string.Equals(Velopack.Locators.VelopackLocator.Current?.AppId, "gclo-dev", StringComparison.Ordinal)) + { + GcloPaths.Edition = "dev"; + } + // The remainder mirrors the XAML-generated Main (obj\...\App.g.i.cs) verbatim. global::WinRT.ComWrappersSupport.InitializeComWrappers(); global::Microsoft.UI.Xaml.Application.Start((p) =>