diff --git a/README.md b/README.md index 6bbb0ea..6a709b9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # enrichment -A Go library for fetching package metadata from multiple sources using [PURLs](https://github.com/package-url/purl-spec). It queries the [ecosyste.ms](https://ecosyste.ms) API, [deps.dev](https://deps.dev), or package registries directly, and returns a unified `PackageInfo` struct with license, version, description, repository, and changelog information. +A Go library for fetching package metadata from multiple sources using [PURLs](https://github.com/package-url/purl-spec). It queries the [ecosyste.ms](https://ecosyste.ms) API, [deps.dev](https://deps.dev), or package registries directly, and returns a unified `PackageInfo` struct with license, version, description, repository, changelog, funding, and maintainer information. ## Install diff --git a/client.go b/client.go index d1f7faa..4098424 100644 --- a/client.go +++ b/client.go @@ -43,6 +43,19 @@ type PackageInfo struct { // Security advisories (ecosyste.ms only) Advisories []Advisory + + // Funding and maintainers (ecosyste.ms only) + FundingLinks []string + Maintainers []Maintainer +} + +// Maintainer is a person or account that maintains a package on its registry. +type Maintainer struct { + Login string + Name string + Email string + URL string + Role string } // Advisory is a security advisory affecting a package. diff --git a/ecosystems.go b/ecosystems.go index 2704ae2..695e6bc 100644 --- a/ecosystems.go +++ b/ecosystems.go @@ -73,6 +73,8 @@ func (c *EcosystemsClient) BulkLookup(ctx context.Context, purls []string) (map[ info.DependentReposCount = pkg.DependentReposCount info.Advisories = convertAdvisories(pkg.Advisories) + info.FundingLinks = pkg.FundingLinks + info.Maintainers = convertMaintainers(pkg.Maintainers) result[purlStr] = info } @@ -155,6 +157,33 @@ func (c *EcosystemsClient) GetVersion(ctx context.Context, purlStr string) (*Ver return info, nil } +func convertMaintainers(maintainers []packages.Maintainer) []Maintainer { + if len(maintainers) == 0 { + return nil + } + result := make([]Maintainer, 0, len(maintainers)) + for _, m := range maintainers { + out := Maintainer{} + if m.Login != nil { + out.Login = *m.Login + } + if m.Name != nil { + out.Name = *m.Name + } + if m.Email != nil { + out.Email = *m.Email + } + if m.HtmlUrl != nil { + out.URL = *m.HtmlUrl + } + if m.Role != nil { + out.Role = *m.Role + } + result = append(result, out) + } + return result +} + func convertAdvisories(advisories []packages.Advisory) []Advisory { if len(advisories) == 0 { return nil diff --git a/enrichment_test.go b/enrichment_test.go index fadd478..5cd775a 100644 --- a/enrichment_test.go +++ b/enrichment_test.go @@ -5,7 +5,10 @@ import ( "encoding/json" "net/http" "net/http/httptest" + "reflect" "testing" + + "github.com/ecosyste-ms/ecosystems-go/packages" ) const testVersionLodash = "4.17.21" @@ -127,6 +130,66 @@ func TestAdvisoryMapping(t *testing.T) { } } +func TestConvertMaintainers(t *testing.T) { + login := "alice" + name := "Alice Example" + email := "alice@example.com" + htmlURL := "https://www.npmjs.com/~alice" + role := "owner" + + t.Run("populated", func(t *testing.T) { + got := convertMaintainers([]packages.Maintainer{ + {Login: &login, Name: &name, Email: &email, HtmlUrl: &htmlURL, Role: &role}, + {Login: &login}, + }) + want := []Maintainer{ + {Login: "alice", Name: "Alice Example", Email: "alice@example.com", URL: "https://www.npmjs.com/~alice", Role: "owner"}, + {Login: "alice"}, + } + if !reflect.DeepEqual(got, want) { + t.Errorf("convertMaintainers() = %+v, want %+v", got, want) + } + }) + + t.Run("nil fields", func(t *testing.T) { + got := convertMaintainers([]packages.Maintainer{{}}) + if len(got) != 1 || got[0] != (Maintainer{}) { + t.Errorf("convertMaintainers([{}]) = %+v, want [{}]", got) + } + }) + + t.Run("empty", func(t *testing.T) { + if got := convertMaintainers(nil); got != nil { + t.Errorf("convertMaintainers(nil) = %v, want nil", got) + } + if got := convertMaintainers([]packages.Maintainer{}); got != nil { + t.Errorf("convertMaintainers(empty) = %v, want nil", got) + } + }) +} + +func TestPackageInfoFundingAndMaintainers(t *testing.T) { + info := &PackageInfo{ + FundingLinks: []string{"https://github.com/sponsors/alice", "https://opencollective.com/foo"}, + Maintainers: []Maintainer{ + {Login: "alice", Role: "owner"}, + }, + } + + if len(info.FundingLinks) != 2 { + t.Errorf("len(FundingLinks) = %d, want 2", len(info.FundingLinks)) + } + if info.FundingLinks[0] != "https://github.com/sponsors/alice" { + t.Errorf("FundingLinks[0] = %q", info.FundingLinks[0]) + } + if len(info.Maintainers) != 1 { + t.Fatalf("len(Maintainers) = %d, want 1", len(info.Maintainers)) + } + if info.Maintainers[0].Login != "alice" || info.Maintainers[0].Role != "owner" { + t.Errorf("Maintainers[0] = %+v", info.Maintainers[0]) + } +} + func TestPackageInfoPopulationFields(t *testing.T) { info := &PackageInfo{ Downloads: 1000000,