feat: update changelog (#131)

closes #130 

- CHANGELOG.md file for people who can view changelog from github
---
`iris changelog` command:
`iris changelog [version]` (e.g. iris changelog v0.5.1)
`iris changelog -n 3` (see changelog of 3 latest releaseas)

=> `iris changelog` looks just like CHANGELOG.md, grouped headers and
all. It only stops looking that way if we personally overwrite that
release's description with something else
=> With 1-hour local cache (invalidated on updater-channel change),
`--refresh` flag to bypass, graceful fallback to stale cache on GitHub
rate-limiting

---
- New auto update config
```toml
# 0 = off (default, notify only), 1 = auto-install, 2 = always confirm first
auto-update = 1
```

- New just debug-changelog and just debug-autoupdate recipes (fully
offline, mocked HTTP servers, no real network/config touched)
This commit is contained in:
VERSE
2026-08-07 17:16:18 +07:00
committed by GitHub
parent 4a4ab3c102
commit 87b91c45d7
25 changed files with 2275 additions and 90 deletions
+77
View File
@@ -1,8 +1,10 @@
package root
import (
"io"
"os"
"path/filepath"
"strings"
"testing"
"time"
@@ -76,3 +78,78 @@ func TestUpdateState(t *testing.T) {
t.Errorf("Expected LastCheck %v, got %v", state.Updater.LastCheckTime, loaded.Updater.LastCheckTime)
}
}
func TestChangelogSummaryLines(t *testing.T) {
body := "## Changelog\n### Bug fixes\n* abc1234 fix something\n* def5678 fix another thing\n* ghi9012 a third fix\n## Update\n```bash\niris update\n```\n"
got := changelogSummaryLines(body, 2)
want := []string{"fix something", "fix another thing"}
if len(got) != len(want) {
t.Fatalf("expected %d lines, got %d: %v", len(want), len(got), got)
}
for i, line := range want {
if got[i] != line {
t.Errorf("line %d: expected %q, got %q", i, line, got[i])
}
}
}
func TestChangelogSummaryLinesEmptyBody(t *testing.T) {
if got := changelogSummaryLines("", 2); len(got) != 0 {
t.Errorf("expected no lines for an empty body, got %v", got)
}
}
func TestPrintUpdateNoticeIncludesChangelogSummary(t *testing.T) {
originalVersion := Version
Version = "v1.0.0"
t.Cleanup(func() { Version = originalVersion })
r, w, err := os.Pipe()
if err != nil {
t.Fatalf("os.Pipe: %v", err)
}
originalStdout := os.Stdout
os.Stdout = w
printUpdateNotice("v1.1.0", "## Changelog\n### Bug fixes\n* abc1234 fix something\n* def5678 fix another thing\n* ghi9012 a third fix\n")
_ = w.Close()
os.Stdout = originalStdout
out, _ := io.ReadAll(r)
got := string(out)
if !strings.Contains(got, "v1.0.0") || !strings.Contains(got, "v1.1.0") {
t.Errorf("expected the version transition in the notice, got %q", got)
}
if !strings.Contains(got, "fix something") || !strings.Contains(got, "fix another thing") {
t.Errorf("expected the first two changelog entries in the notice, got %q", got)
}
if strings.Contains(got, "a third fix") {
t.Errorf("expected the summary to cap at 2 entries, got %q", got)
}
}
func TestPrintUpdateNoticeWithoutNotes(t *testing.T) {
originalVersion := Version
Version = "v1.0.0"
t.Cleanup(func() { Version = originalVersion })
r, w, err := os.Pipe()
if err != nil {
t.Fatalf("os.Pipe: %v", err)
}
originalStdout := os.Stdout
os.Stdout = w
printUpdateNotice("v1.1.0", "")
_ = w.Close()
os.Stdout = originalStdout
out, _ := io.ReadAll(r)
got := string(out)
if !strings.Contains(got, "v1.1.0") {
t.Errorf("expected the notice to still show without changelog notes, got %q", got)
}
}