Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

---

## [0.1.5] — 2026-05-23

### Fixed

- **`GhCliReleaseSource` returns null when `--prerelease` / a `Channel` is in play.** The list path asked `gh release list --json tagName,name,url,publishedAt,isDraft,isPrerelease` — but `gh release list` exposes a narrower field set than `gh release view`, and `url` is view-only. gh exited non-zero ("Unknown JSON field: \"url\""), `GhProcess` threw, the source's catch-all swallowed it, and consumers saw "Could not determine the latest release." Surfaced by pl-app running `update --prerelease` against a private repo whose only releases were prereleases. Fix: drop `name` and `url` from the list `--json` value — neither was read from the list result anyway (only `tagName`, `publishedAt`, `isDraft`, `isPrerelease` drive filter/sort). The full detail (incl. `url`, `assets`) is still fetched per-tag via `gh release view`. New regression test in `GhCliReleaseSourceTests` asserts the list args stay within `release list`'s supported fields.

### Why this slipped through v0.1.4

- The existing tests use a fake gh runner with canned JSON, so they never exercised the real gh CLI's field-validation. `gh release list` was only reached when `Channel` was set or `IncludePrereleases` was `true` at the source — both uncommon configs before `--prerelease` landed.

---

## [0.1.4] — 2026-05-23

### Added
Expand Down Expand Up @@ -85,6 +97,7 @@ Initial commit. Never published to nuget.org — superseded by 0.1.1 before the
- Full XML documentation on the public surface, `TreatWarningsAsErrors=true`, `AnalysisLevel=latest`.
- SourceLink, deterministic builds, published symbol packages.

[0.1.5]: https://github.com/StuartMeeks/NextIteration.SpectreConsole.SelfUpdate/releases/tag/v0.1.5
[0.1.4]: https://github.com/StuartMeeks/NextIteration.SpectreConsole.SelfUpdate/releases/tag/v0.1.4
[0.1.3]: https://github.com/StuartMeeks/NextIteration.SpectreConsole.SelfUpdate/releases/tag/v0.1.3
[0.1.2]: https://github.com/StuartMeeks/NextIteration.SpectreConsole.SelfUpdate/releases/tag/v0.1.2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

<PropertyGroup>
<PackageId>NextIteration.SpectreConsole.SelfUpdate</PackageId>
<Version>0.1.4</Version>
<Version>0.1.5</Version>
<Authors>Stuart Meeks</Authors>
<Description>Self-update for Spectre.Console CLIs: pluggable update sources (GitHub Releases over HTTP, GitHub Releases via gh CLI for private repos, generic HTTPS manifest, custom), SHA-256 verification, atomic file swap, and a drop-in `update` command.</Description>
<GeneratePackageOnBuild Condition="'$(Configuration)' == 'Release'">true</GeneratePackageOnBuild>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,16 @@ internal GhCliReleaseSource(
return dto is null ? null : Convert(dto, channel);
}

// `gh release list --json` exposes a narrower field set than
// `gh release view --json` — notably `url` is view-only and
// asking for it on list exits the gh process with a non-zero
// status. Only request fields used for filtering / sort here;
// the full detail (incl. url, assets) is fetched per-tag below.
var listJson = await _runner(
new[]
{
"release", "list",
"--json", "tagName,name,url,publishedAt,isDraft,isPrerelease",
"--json", "tagName,publishedAt,isDraft,isPrerelease",
"--limit", "30",
"--repo", _repository,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,33 @@ public async Task GetLatestAsync_when_prereleases_enabled_lists_then_views_match
Assert.Contains("v2.0.0-beta.1", runner.Invocations[1]);
}

[Fact]
public async Task GetLatestAsync_list_call_does_not_request_view_only_json_fields()
{
// Regression: `gh release list --json` rejects `url` (and other
// fields that exist only on `release view`). Asking for them
// returns a non-zero exit code that the source's catch-all
// surfaces as "no result". Keep the list `--json` value restricted
// to fields supported by `release list`.
var runner = new RecordingRunner { NextStdout = "[]" };
var source = new GhCliReleaseSource(Repo, includePrereleases: true, runner.RunAsync);

await source.GetLatestAsync(channel: null, includePrereleasesOverride: null, CancellationToken.None);

var listInvocation = runner.Invocations.Single();
Assert.Equal("list", listInvocation[1]);

var jsonIdx = -1;
for (var i = 0; i < listInvocation.Count; i++)
{
if (listInvocation[i] == "--json") { jsonIdx = i; break; }
}
Assert.True(jsonIdx >= 0 && jsonIdx + 1 < listInvocation.Count);
var fields = listInvocation[jsonIdx + 1].Split(',');
Assert.DoesNotContain("url", fields);
Assert.DoesNotContain("assets", fields);
}

[Fact]
public async Task GetLatestAsync_when_channel_does_not_match_returns_null()
{
Expand Down
Loading