From a36b3ece165ab1008141a1a8b927776e77122068 Mon Sep 17 00:00:00 2001 From: Ophestra Date: Thu, 5 Mar 2026 20:55:45 +0900 Subject: [PATCH] internal/rosa: release monitoring via Anitya This is much more sustainable than manual package flagging. Signed-off-by: Ophestra --- cmd/mbf/main.go | 26 ++++++++++++++++++++++ internal/rosa/all.go | 51 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/cmd/mbf/main.go b/cmd/mbf/main.go index 7a9864b..4b2c541 100644 --- a/cmd/mbf/main.go +++ b/cmd/mbf/main.go @@ -10,6 +10,7 @@ import ( "os/signal" "path/filepath" "runtime" + "strconv" "syscall" "time" "unique" @@ -129,6 +130,31 @@ func main() { ) } + c.NewCommand("updates", command.UsageInternal, func([]string) error { + var n int + for i := range rosa.PresetEnd { + p := rosa.PArtifact(i) + meta := rosa.GetMetadata(p) + if meta.ID == 0 { + continue + } + + if v, err := meta.GetVersions(ctx); err != nil { + return err + } else if current := rosa.Std.Version(p); v.Latest != current { + n++ + log.Printf("%s %s < %s", meta.Name, current, v.Latest) + } else { + msg.Verbosef("%s is up to date", meta.Name) + } + } + + if n > 0 { + return errors.New(strconv.Itoa(n) + " packages are out of date") + } + return nil + }) + { var ( flagGentoo string diff --git a/internal/rosa/all.go b/internal/rosa/all.go index 8b2aec1..da1e365 100644 --- a/internal/rosa/all.go +++ b/internal/rosa/all.go @@ -1,6 +1,11 @@ package rosa import ( + "context" + "encoding/json" + "errors" + "net/http" + "strconv" "sync" "hakurei.app/internal/pkg" @@ -152,11 +157,57 @@ type Metadata struct { Description string `json:"description"` // Project home page. Website string `json:"website,omitempty"` + + // Project identifier on [Anitya]. + // + // [Anitya]: https://release-monitoring.org/ + ID int `json:"-"` } // Unversioned denotes an unversioned [PArtifact]. const Unversioned = "\x00" +// UnpopulatedIDError is returned by [Metadata.GetLatest] for an instance of +// [Metadata] where ID is not populated. +type UnpopulatedIDError struct{} + +func (UnpopulatedIDError) Unwrap() error { return errors.ErrUnsupported } +func (UnpopulatedIDError) Error() string { return "Anitya ID is not populated" } + +// Versions are package versions returned by Anitya. +type Versions struct { + // The latest version for the project, as determined by the version sorting algorithm. + Latest string `json:"latest_version"` + // List of all versions that aren’t flagged as pre-release. + Stable []string `json:"stable_versions"` + // List of all versions stored, sorted from newest to oldest. + All []string `json:"versions"` +} + +// GetVersions returns versions fetched from Anitya. +func (meta *Metadata) GetVersions(ctx context.Context) (*Versions, error) { + if meta.ID == 0 { + return nil, UnpopulatedIDError{} + } + + var resp *http.Response + if req, err := http.NewRequestWithContext( + ctx, + http.MethodGet, + "https://release-monitoring.org/api/v2/versions/?project_id="+ + strconv.Itoa(meta.ID), + nil, + ); err != nil { + return nil, err + } else if resp, err = http.DefaultClient.Do(req); err != nil { + return nil, err + } + + var v Versions + err := json.NewDecoder(resp.Body).Decode(&v) + return &v, errors.Join(err, resp.Body.Close()) +} + var ( // artifactsM is an array of [PArtifact] metadata. artifactsM [PresetEnd]Metadata