From 9f237b6b56cfcc3d38511316d2d4849cfa791450 Mon Sep 17 00:00:00 2001 From: Kieron Lanning Date: Sun, 27 Sep 2026 13:01:23 +0100 Subject: [PATCH] feat: mark GitHub releases as prereleases for prerelease versions --- docs/wiki/Configuration-Reference.md | 1 + docs/wiki/Pipeline-Modules.md | 2 +- .../Modules/CreateGitHubReleaseModule.cs | 18 ++++++++- src/src/Build/Settings/ReleaseSettings.cs | 20 ++++++++++ src/src/Build/appsettings.json | 3 +- .../ReleaseSettingsTests.cs | 39 +++++++++++++++++++ 6 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 src/tests/Build.IntegrationTests/ReleaseSettingsTests.cs diff --git a/docs/wiki/Configuration-Reference.md b/docs/wiki/Configuration-Reference.md index 2f6c3ee..36b83b8 100644 --- a/docs/wiki/Configuration-Reference.md +++ b/docs/wiki/Configuration-Reference.md @@ -96,6 +96,7 @@ Content entry paths and package-id keys are matched as globs (case-insensitive), | --- | --- | --- | | `Mode` | `None` | `None`, `LocalNuGet`, `NuGet`, or `GitHubRelease` | | `UploadArtifacts` | `false` | Upload every file in `Build:ArtifactsFolder` as GitHub release assets | +| `MarkPrerelease` | `true` | Create the GitHub release as a prerelease when the package version is a prerelease (for example `2.0.0-prerelease.25`) | ## Example diff --git a/docs/wiki/Pipeline-Modules.md b/docs/wiki/Pipeline-Modules.md index 0d35531..2fecca2 100644 --- a/docs/wiki/Pipeline-Modules.md +++ b/docs/wiki/Pipeline-Modules.md @@ -80,7 +80,7 @@ Moves the `.nupkg`/`.snupkg` files from `Build:ArtifactsFolder` into the local f Category `Release`. Depends on `PublishNuGetModule`, `ValidatePackModule`, and `VersionModule`. Skip condition: skipped unless `Release:Mode` is `NuGet` or `GitHubRelease` **and** a GitHub token resolves via `GitHub:GetGitHubToken()`. -Creates a GitHub release with tag `v{version}` and `GenerateReleaseNotes = true`. When `Release:UploadArtifacts` is true, uploads every file in `Build:ArtifactsFolder` as a release asset — for Web projects this is the `-.zip` produced by `PackModule`. The tag must not already exist; callers gate release eligibility (the tool does not skip an existing tag itself). +Creates a GitHub release with tag `v{version}` and `GenerateReleaseNotes = true`. Releases whose version is a prerelease (for example `2.0.0-prerelease.25`) are created as GitHub prereleases unless `Release:MarkPrerelease` is false, so prerelease builds are not presented as the latest stable release. When `Release:UploadArtifacts` is true, uploads every file in `Build:ArtifactsFolder` as a release asset — for Web projects this is the `-.zip` produced by `PackModule`. The tag must not already exist; callers gate release eligibility (the tool does not skip an existing tag itself). ## See also diff --git a/src/src/Build/Modules/CreateGitHubReleaseModule.cs b/src/src/Build/Modules/CreateGitHubReleaseModule.cs index 20d1c76..18c1e53 100644 --- a/src/src/Build/Modules/CreateGitHubReleaseModule.cs +++ b/src/src/Build/Modules/CreateGitHubReleaseModule.cs @@ -55,14 +55,28 @@ CancellationToken cancellationToken ); } - // Create a new release on GitHub with the specified tag and generate release notes + // Create a new release on GitHub with the specified tag and generate release notes. + // Prerelease versions are published as prereleases so they are not presented as the + // latest stable release. + var isPrerelease = releaseSettings.Value.ShouldMarkPrerelease(version); var release = await context .GitHub() .Client.Repository.Release.Create( repositoryId, - new NewRelease(tag) { Name = tag, GenerateReleaseNotes = true } + new NewRelease(tag) + { + Name = tag, + GenerateReleaseNotes = true, + Prerelease = isPrerelease, + } ); + context.Logger.LogInformation( + "Created GitHub release {Tag}{Prerelease}.", + tag, + isPrerelease ? " as a prerelease" : string.Empty + ); + if (releaseSettings.Value.UploadArtifacts) { var artifactsFolder = buildSettings.Value.ArtifactsFolder; diff --git a/src/src/Build/Settings/ReleaseSettings.cs b/src/src/Build/Settings/ReleaseSettings.cs index 6f9fa66..d0aef29 100644 --- a/src/src/Build/Settings/ReleaseSettings.cs +++ b/src/src/Build/Settings/ReleaseSettings.cs @@ -1,3 +1,5 @@ +using NuGet.Versioning; + namespace Purview.Build.Settings; public sealed record ReleaseSettings @@ -11,4 +13,22 @@ public sealed record ReleaseSettings /// (for example .nupkg/.snupkg or .vsix) as release assets. /// public bool UploadArtifacts { get; init; } + + /// + /// When true (the default), a GitHub release whose version is a prerelease (for example + /// 2.0.0-prerelease.25) is created as a prerelease, so it is not presented as the latest + /// stable release. Set to false to publish prerelease versions as stable releases. + /// + public bool MarkPrerelease { get; init; } = true; + + /// + /// Returns whether a release for should be created as a GitHub + /// prerelease. + /// + public bool ShouldMarkPrerelease(NuGetVersion version) + { + ArgumentNullException.ThrowIfNull(version); + + return MarkPrerelease && version.IsPrerelease; + } } diff --git a/src/src/Build/appsettings.json b/src/src/Build/appsettings.json index 6ba802b..1e38300 100644 --- a/src/src/Build/appsettings.json +++ b/src/src/Build/appsettings.json @@ -47,6 +47,7 @@ }, "Release": { "Mode": "None", - "UploadArtifacts": false + "UploadArtifacts": false, + "MarkPrerelease": true } } \ No newline at end of file diff --git a/src/tests/Build.IntegrationTests/ReleaseSettingsTests.cs b/src/tests/Build.IntegrationTests/ReleaseSettingsTests.cs new file mode 100644 index 0000000..928b986 --- /dev/null +++ b/src/tests/Build.IntegrationTests/ReleaseSettingsTests.cs @@ -0,0 +1,39 @@ +using NuGet.Versioning; +using Purview.Build.Settings; + +namespace Purview.Build; + +public class ReleaseSettingsTests +{ + [Test] + public async Task ShouldMarkPrerelease_GivenPrereleaseVersionAndDefaults_ReturnsTrue() + { + ReleaseSettings settings = new(); + + await Assert.That(settings.ShouldMarkPrerelease(NuGetVersion.Parse("2.0.0-prerelease.25"))).IsTrue(); + } + + [Test] + public async Task ShouldMarkPrerelease_GivenStableVersionAndDefaults_ReturnsFalse() + { + ReleaseSettings settings = new(); + + await Assert.That(settings.ShouldMarkPrerelease(NuGetVersion.Parse("2.0.0"))).IsFalse(); + } + + [Test] + public async Task ShouldMarkPrerelease_GivenMarkPrereleaseDisabled_ReturnsFalse() + { + ReleaseSettings settings = new() { MarkPrerelease = false }; + + await Assert.That(settings.ShouldMarkPrerelease(NuGetVersion.Parse("2.0.0-prerelease.25"))).IsFalse(); + } + + [Test] + public async Task MarkPrerelease_GivenDefaults_IsTrue() + { + ReleaseSettings settings = new(); + + await Assert.That(settings.MarkPrerelease).IsTrue(); + } +}