From 8930ece4618b029c7c4ce12dcfb4203d023a511e Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Oct 2026 12:26:15 +0000 Subject: [PATCH 1/3] Resolve release changelog paths against the workspace [patch] The changelog names are bare file names by default, and the release step used them as-is, so they resolved against the process's current directory. With --workspace pointing elsewhere, packages and the GitHub release shipped with no notes, or with the current directory's changelog. Resolve both names against the workspace before packing and before building the release options. Fixes #172 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01J9ABDLXQgwyGEboxA8DpmQ --- .../Publishing/ReleaseServiceTests.cs | 44 +++++++++++++++++++ KtsuBuild/Publishing/ReleaseService.cs | 17 +++++-- 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/KtsuBuild.Tests/Publishing/ReleaseServiceTests.cs b/KtsuBuild.Tests/Publishing/ReleaseServiceTests.cs index 111cdd6..254504e 100644 --- a/KtsuBuild.Tests/Publishing/ReleaseServiceTests.cs +++ b/KtsuBuild.Tests/Publishing/ReleaseServiceTests.cs @@ -56,6 +56,50 @@ await _dotNetService.Received(1).PackAsync( _tempDir, config.StagingPath, "Release", Arg.Any(), Arg.Any()).ConfigureAwait(false); } + [TestMethod] + [DoNotParallelize] + public async Task ExecuteReleaseAsync_ResolvesChangelogsAgainstTheWorkspace_NotTheCurrentDirectory() + { + // Run from a directory that has its own LATEST_CHANGELOG.md, as running from the KtsuBuild + // checkout does: neither that file nor the bare name may reach the pack or the release. + string elsewhere = TestHelpers.CreateTempDir("ReleaseSvcCwd"); + await File.WriteAllTextAsync(Path.Combine(elsewhere, "LATEST_CHANGELOG.md"), "someone else's notes").ConfigureAwait(false); + string original = Directory.GetCurrentDirectory(); + ReleaseOptions? captured = null; + _gitHubService.CreateReleaseAsync(Arg.Do(o => captured = o), Arg.Any()) + .Returns(Task.CompletedTask); + + try + { + Directory.SetCurrentDirectory(elsewhere); + await _service.ExecuteReleaseAsync(CreateDefaultConfig(), _tempDir, "Release").ConfigureAwait(false); + } + finally + { + Directory.SetCurrentDirectory(original); + Directory.Delete(elsewhere, recursive: true); + } + + await _dotNetService.Received(1).PackAsync( + _tempDir, Arg.Any(), "Release", Path.Combine(_tempDir, "LATEST_CHANGELOG.md"), Arg.Any()).ConfigureAwait(false); + Assert.IsNotNull(captured); + Assert.AreEqual(Path.Combine(_tempDir, "LATEST_CHANGELOG.md"), captured.LatestChangelogFile); + Assert.AreEqual(Path.Combine(_tempDir, "CHANGELOG.md"), captured.ChangelogFile); + } + + [TestMethod] + public async Task ExecuteReleaseAsync_KeepsRootedChangelogPathsAsGiven() + { + string rooted = Path.Combine(_tempDir, "notes", "NOTES.md"); + BuildConfiguration config = CreateDefaultConfig(); + config.LatestChangelogFile = rooted; + + await _service.ExecuteReleaseAsync(config, _tempDir, "Release").ConfigureAwait(false); + + await _dotNetService.Received(1).PackAsync( + _tempDir, Arg.Any(), "Release", rooted, Arg.Any()).ConfigureAwait(false); + } + [TestMethod] public async Task ExecuteReleaseAsync_PublishesExecutableProjects_ForAllRuntimes() { diff --git a/KtsuBuild/Publishing/ReleaseService.cs b/KtsuBuild/Publishing/ReleaseService.cs index 19500c6..539710c 100644 --- a/KtsuBuild/Publishing/ReleaseService.cs +++ b/KtsuBuild/Publishing/ReleaseService.cs @@ -32,7 +32,7 @@ public async Task ExecuteReleaseAsync(BuildConfiguration config, string workspac Ensure.NotNull(configuration); // Pack NuGet packages - await dotNetService.PackAsync(workspace, config.StagingPath, configuration, config.LatestChangelogFile, cancellationToken).ConfigureAwait(false); + await dotNetService.PackAsync(workspace, config.StagingPath, configuration, ResolveInWorkspace(workspace, config.LatestChangelogFile), cancellationToken).ConfigureAwait(false); await PublishApplicationsAsync(config, workspace, configuration, cancellationToken).ConfigureAwait(false); await WriteArchiveHashesAsync(config, cancellationToken).ConfigureAwait(false); @@ -184,8 +184,8 @@ private async Task CreateGitHubReleaseAsync(BuildConfiguration config, string wo Version = config.Version, CommitHash = config.ReleaseHash, GithubToken = config.GithubToken, - ChangelogFile = config.ChangelogFile, - LatestChangelogFile = config.LatestChangelogFile, + ChangelogFile = ResolveInWorkspace(workspace, config.ChangelogFile), + LatestChangelogFile = ResolveInWorkspace(workspace, config.LatestChangelogFile), AssetPaths = config.AssetPatterns, IsPrerelease = config.Version.Contains("-pre", StringComparison.OrdinalIgnoreCase) || config.Version.Contains("-alpha", StringComparison.OrdinalIgnoreCase) @@ -196,4 +196,15 @@ private async Task CreateGitHubReleaseAsync(BuildConfiguration config, string wo await gitHubService.CreateReleaseAsync(releaseOptions, cancellationToken).ConfigureAwait(false); } + + /// + /// Resolves a configured file name against the workspace, the directory the metadata step wrote it to. + /// + /// + /// The changelog names are bare file names by default. Left relative, they resolve against the + /// process's current directory, so a run with --workspace pointing elsewhere shipped packages + /// and releases with no notes, or with the changelog of whatever directory it was started from. + /// + private static string ResolveInWorkspace(string workspace, string fileName) => + string.IsNullOrEmpty(fileName) || Path.IsPathRooted(fileName) ? fileName : Path.Combine(workspace, fileName); } From fd2f6b9c0975411e302c7bdb5c7fd73a0ff60281 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Oct 2026 12:34:02 +0000 Subject: [PATCH 2/3] Pass the test's cancellation token in the new release tests Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01J9ABDLXQgwyGEboxA8DpmQ --- KtsuBuild.Tests/Publishing/ReleaseServiceTests.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/KtsuBuild.Tests/Publishing/ReleaseServiceTests.cs b/KtsuBuild.Tests/Publishing/ReleaseServiceTests.cs index 254504e..3f91e5f 100644 --- a/KtsuBuild.Tests/Publishing/ReleaseServiceTests.cs +++ b/KtsuBuild.Tests/Publishing/ReleaseServiceTests.cs @@ -19,6 +19,8 @@ public class ReleaseServiceTests private ReleaseService _service = null!; private string _tempDir = null!; + public TestContext TestContext { get; set; } = null!; + [TestInitialize] public void Setup() { @@ -63,7 +65,7 @@ public async Task ExecuteReleaseAsync_ResolvesChangelogsAgainstTheWorkspace_NotT // Run from a directory that has its own LATEST_CHANGELOG.md, as running from the KtsuBuild // checkout does: neither that file nor the bare name may reach the pack or the release. string elsewhere = TestHelpers.CreateTempDir("ReleaseSvcCwd"); - await File.WriteAllTextAsync(Path.Combine(elsewhere, "LATEST_CHANGELOG.md"), "someone else's notes").ConfigureAwait(false); + await File.WriteAllTextAsync(Path.Combine(elsewhere, "LATEST_CHANGELOG.md"), "someone else's notes", TestContext.CancellationToken).ConfigureAwait(false); string original = Directory.GetCurrentDirectory(); ReleaseOptions? captured = null; _gitHubService.CreateReleaseAsync(Arg.Do(o => captured = o), Arg.Any()) @@ -72,7 +74,7 @@ public async Task ExecuteReleaseAsync_ResolvesChangelogsAgainstTheWorkspace_NotT try { Directory.SetCurrentDirectory(elsewhere); - await _service.ExecuteReleaseAsync(CreateDefaultConfig(), _tempDir, "Release").ConfigureAwait(false); + await _service.ExecuteReleaseAsync(CreateDefaultConfig(), _tempDir, "Release", TestContext.CancellationToken).ConfigureAwait(false); } finally { @@ -94,7 +96,7 @@ public async Task ExecuteReleaseAsync_KeepsRootedChangelogPathsAsGiven() BuildConfiguration config = CreateDefaultConfig(); config.LatestChangelogFile = rooted; - await _service.ExecuteReleaseAsync(config, _tempDir, "Release").ConfigureAwait(false); + await _service.ExecuteReleaseAsync(config, _tempDir, "Release", TestContext.CancellationToken).ConfigureAwait(false); await _dotNetService.Received(1).PackAsync( _tempDir, Arg.Any(), "Release", rooted, Arg.Any()).ConfigureAwait(false); From 62926c2b0c1a1da3db2cf1744d62fcd6f0080185 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Oct 2026 12:41:31 +0000 Subject: [PATCH 3/3] Pass the test's cancellation token when writing a project file in the release tests Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01J9ABDLXQgwyGEboxA8DpmQ --- KtsuBuild.Tests/Publishing/ReleaseServiceTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KtsuBuild.Tests/Publishing/ReleaseServiceTests.cs b/KtsuBuild.Tests/Publishing/ReleaseServiceTests.cs index 3f91e5f..94e0876 100644 --- a/KtsuBuild.Tests/Publishing/ReleaseServiceTests.cs +++ b/KtsuBuild.Tests/Publishing/ReleaseServiceTests.cs @@ -388,7 +388,7 @@ private async Task WriteProjectAsync(string relativeDirectory, string fi { string projPath = Path.Combine(_tempDir, relativeDirectory, fileName); Directory.CreateDirectory(Path.GetDirectoryName(projPath)!); - await File.WriteAllTextAsync(projPath, "").ConfigureAwait(false); + await File.WriteAllTextAsync(projPath, "", TestContext.CancellationToken).ConfigureAwait(false); return projPath; }