mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 08:23:15 +01:00
feat: add support for authors, maintainers, and contributors in package.json. (#4003)
Fixes #2250 --------- Signed-off-by: Alan Pope <alan.pope@anchore.com> Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com> Co-authored-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
This commit is contained in:
parent
ab9db0024e
commit
87e1d8cb87
@ -25,6 +25,39 @@ func newPackageJSONPackage(ctx context.Context, u packageJSON, indexLocation fil
|
||||
}
|
||||
|
||||
license := pkg.NewLicensesFromLocationWithContext(ctx, indexLocation, licenseCandidates...)
|
||||
// Handle author, authors, contributors, and maintainers fields
|
||||
var authorParts []string
|
||||
|
||||
// Add a single author field if it exists
|
||||
if u.Author.Name != "" || u.Author.Email != "" || u.Author.URL != "" {
|
||||
if authStr := u.Author.AuthorString(); authStr != "" {
|
||||
authorParts = append(authorParts, authStr)
|
||||
}
|
||||
}
|
||||
|
||||
// Add authors field if it exists
|
||||
if len(u.Authors) > 0 {
|
||||
if authorsStr := u.Authors.String(); authorsStr != "" {
|
||||
authorParts = append(authorParts, authorsStr)
|
||||
}
|
||||
}
|
||||
|
||||
// Add contributors field if it exists
|
||||
if len(u.Contributors) > 0 {
|
||||
if contributorsStr := u.Contributors.String(); contributorsStr != "" {
|
||||
authorParts = append(authorParts, contributorsStr)
|
||||
}
|
||||
}
|
||||
|
||||
// Add maintainers field if it exists
|
||||
if len(u.Maintainers) > 0 {
|
||||
if maintainersStr := u.Maintainers.String(); maintainersStr != "" {
|
||||
authorParts = append(authorParts, maintainersStr)
|
||||
}
|
||||
}
|
||||
|
||||
authorInfo := strings.Join(authorParts, ", ")
|
||||
|
||||
p := pkg.Package{
|
||||
Name: u.Name,
|
||||
Version: u.Version,
|
||||
@ -37,7 +70,7 @@ func newPackageJSONPackage(ctx context.Context, u packageJSON, indexLocation fil
|
||||
Name: u.Name,
|
||||
Version: u.Version,
|
||||
Description: u.Description,
|
||||
Author: u.Author.AuthorString(),
|
||||
Author: authorInfo,
|
||||
Homepage: u.Homepage,
|
||||
URL: u.Repository.URL,
|
||||
Private: u.Private,
|
||||
|
||||
@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
|
||||
@ -24,7 +25,10 @@ var _ generic.Parser = parsePackageJSON
|
||||
type packageJSON struct {
|
||||
Version string `json:"version"`
|
||||
Latest []string `json:"latest"`
|
||||
Author author `json:"author"`
|
||||
Author person `json:"author"`
|
||||
Authors people `json:"authors"`
|
||||
Contributors people `json:"contributors"`
|
||||
Maintainers people `json:"maintainers"`
|
||||
License json.RawMessage `json:"license"`
|
||||
Licenses json.RawMessage `json:"licenses"`
|
||||
Name string `json:"name"`
|
||||
@ -35,12 +39,14 @@ type packageJSON struct {
|
||||
Private bool `json:"private"`
|
||||
}
|
||||
|
||||
type author struct {
|
||||
type person struct {
|
||||
Name string `json:"name" mapstructure:"name"`
|
||||
Email string `json:"email" mapstructure:"email"`
|
||||
URL string `json:"url" mapstructure:"url"`
|
||||
}
|
||||
|
||||
type people []person
|
||||
|
||||
type repository struct {
|
||||
Type string `json:"type" mapstructure:"type"`
|
||||
URL string `json:"url" mapstructure:"url"`
|
||||
@ -76,9 +82,9 @@ func parsePackageJSON(ctx context.Context, _ file.Resolver, _ *generic.Environme
|
||||
return pkgs, nil, nil
|
||||
}
|
||||
|
||||
func (a *author) UnmarshalJSON(b []byte) error {
|
||||
func (p *person) UnmarshalJSON(b []byte) error {
|
||||
var authorStr string
|
||||
var auth author
|
||||
var auth person
|
||||
|
||||
if err := json.Unmarshal(b, &authorStr); err == nil {
|
||||
// successfully parsed as a string, now parse that string into fields
|
||||
@ -97,18 +103,18 @@ func (a *author) UnmarshalJSON(b []byte) error {
|
||||
}
|
||||
}
|
||||
|
||||
*a = auth
|
||||
*p = auth
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *author) AuthorString() string {
|
||||
result := a.Name
|
||||
if a.Email != "" {
|
||||
result += fmt.Sprintf(" <%s>", a.Email)
|
||||
func (p *person) AuthorString() string {
|
||||
result := p.Name
|
||||
if p.Email != "" {
|
||||
result += fmt.Sprintf(" <%s>", p.Email)
|
||||
}
|
||||
if a.URL != "" {
|
||||
result += fmt.Sprintf(" (%s)", a.URL)
|
||||
if p.URL != "" {
|
||||
result += fmt.Sprintf(" (%s)", p.URL)
|
||||
}
|
||||
return result
|
||||
}
|
||||
@ -210,3 +216,58 @@ func pathContainsNodeModulesDirectory(p string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *people) UnmarshalJSON(b []byte) error {
|
||||
// Try to unmarshal as an array of strings
|
||||
var authorStrings []string
|
||||
if err := json.Unmarshal(b, &authorStrings); err == nil {
|
||||
// Successfully parsed as an array of strings
|
||||
auths := make([]person, len(authorStrings))
|
||||
for i, authorStr := range authorStrings {
|
||||
// Parse each string into author fields
|
||||
fields := internal.MatchNamedCaptureGroups(authorPattern, authorStr)
|
||||
var auth person
|
||||
if err := mapstructure.Decode(fields, &auth); err != nil {
|
||||
return fmt.Errorf("unable to decode package.json author: %w", err)
|
||||
}
|
||||
// Trim whitespace from name if it was parsed
|
||||
if auth.Name != "" {
|
||||
auth.Name = strings.TrimSpace(auth.Name)
|
||||
}
|
||||
auths[i] = auth
|
||||
}
|
||||
*p = auths
|
||||
return nil
|
||||
}
|
||||
|
||||
// Try to unmarshal as an array of objects
|
||||
var authorObjs []map[string]interface{}
|
||||
if err := json.Unmarshal(b, &authorObjs); err == nil {
|
||||
// Successfully parsed as an array of objects
|
||||
auths := make([]person, len(authorObjs))
|
||||
for i, fields := range authorObjs {
|
||||
var auth person
|
||||
if err := mapstructure.Decode(fields, &auth); err != nil {
|
||||
return fmt.Errorf("unable to decode package.json author object: %w", err)
|
||||
}
|
||||
auths[i] = auth
|
||||
}
|
||||
*p = auths
|
||||
return nil
|
||||
}
|
||||
|
||||
// If we get here, it means neither format matched
|
||||
return fmt.Errorf("unable to parse package.json authors field: expected array of strings or array of objects")
|
||||
}
|
||||
|
||||
func (p people) String() string {
|
||||
if len(p) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
authorStrings := make([]string, len(p))
|
||||
for i, auth := range p {
|
||||
authorStrings[i] = auth.AuthorString()
|
||||
}
|
||||
return strings.Join(authorStrings, ", ")
|
||||
}
|
||||
|
||||
@ -152,7 +152,7 @@ func TestParsePackageJSON(t *testing.T) {
|
||||
Metadata: pkg.NpmPackage{
|
||||
Name: "function-bind",
|
||||
Version: "1.1.1",
|
||||
Author: "Raynos <raynos2@gmail.com>",
|
||||
Author: "Raynos <raynos2@gmail.com>, Raynos, Jordan Harband (https://github.com/ljharb)",
|
||||
Homepage: "https://github.com/Raynos/function-bind",
|
||||
URL: "git://github.com/Raynos/function-bind.git",
|
||||
Description: "Implementation of Function.prototype.bind",
|
||||
@ -202,6 +202,132 @@ func TestParsePackageJSON(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Fixture: "test-fixtures/pkg-json/package-authors-array.json",
|
||||
ExpectedPkg: pkg.Package{
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
PURL: "pkg:npm/npm@6.14.6",
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocationsWithContext(ctx, "Artistic-2.0", file.NewLocation("test-fixtures/pkg-json/package-authors-array.json")),
|
||||
),
|
||||
Language: pkg.JavaScript,
|
||||
Metadata: pkg.NpmPackage{
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
Author: "Harry Potter <hp@hogwards.com> (http://youknowwho.com/), John Smith <j.smith@something.com> (http://awebsite.com/)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Description: "a package manager for JavaScript",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Fixture: "test-fixtures/pkg-json/package-authors-objects.json",
|
||||
ExpectedPkg: pkg.Package{
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
PURL: "pkg:npm/npm@6.14.6",
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocationsWithContext(ctx, "Artistic-2.0", file.NewLocation("test-fixtures/pkg-json/package-authors-objects.json")),
|
||||
),
|
||||
Language: pkg.JavaScript,
|
||||
Metadata: pkg.NpmPackage{
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
Author: "Harry Potter <hp@hogwards.com> (http://youknowwho.com/), John Smith <j.smith@something.com> (http://awebsite.com/)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Description: "a package manager for JavaScript",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Fixture: "test-fixtures/pkg-json/package-both-author-and-authors.json",
|
||||
ExpectedPkg: pkg.Package{
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
PURL: "pkg:npm/npm@6.14.6",
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocationsWithContext(ctx, "Artistic-2.0", file.NewLocation("test-fixtures/pkg-json/package-both-author-and-authors.json")),
|
||||
),
|
||||
Language: pkg.JavaScript,
|
||||
Metadata: pkg.NpmPackage{
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me), Harry Potter <hp@hogwards.com> (http://youknowwho.com/), John Smith <j.smith@something.com> (http://awebsite.com/)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Description: "a package manager for JavaScript",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Fixture: "test-fixtures/pkg-json/package-contributors.json",
|
||||
ExpectedPkg: pkg.Package{
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
PURL: "pkg:npm/npm@6.14.6",
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocationsWithContext(ctx, "Artistic-2.0", file.NewLocation("test-fixtures/pkg-json/package-contributors.json")),
|
||||
),
|
||||
Language: pkg.JavaScript,
|
||||
Metadata: pkg.NpmPackage{
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
Author: "Alice Contributor <alice@example.com>, Bob Helper <bob@example.com>",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Description: "a package manager for JavaScript",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Fixture: "test-fixtures/pkg-json/package-maintainers.json",
|
||||
ExpectedPkg: pkg.Package{
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
PURL: "pkg:npm/npm@6.14.6",
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocationsWithContext(ctx, "Artistic-2.0", file.NewLocation("test-fixtures/pkg-json/package-maintainers.json")),
|
||||
),
|
||||
Language: pkg.JavaScript,
|
||||
Metadata: pkg.NpmPackage{
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
Author: "Charlie Maintainer <charlie@example.com>, Diana Keeper <diana@example.com>",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Description: "a package manager for JavaScript",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Fixture: "test-fixtures/pkg-json/package-all-author-fields.json",
|
||||
ExpectedPkg: pkg.Package{
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
PURL: "pkg:npm/npm@6.14.6",
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocationsWithContext(ctx, "Artistic-2.0", file.NewLocation("test-fixtures/pkg-json/package-all-author-fields.json")),
|
||||
),
|
||||
Language: pkg.JavaScript,
|
||||
Metadata: pkg.NpmPackage{
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
Author: "Main Author <main@example.com>, Second Author <second@example.com>, Contrib One <contrib1@example.com>, Maintainer One <maintain1@example.com>",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Description: "a package manager for JavaScript",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
{
|
||||
"version": "6.14.6",
|
||||
"name": "npm",
|
||||
"description": "a package manager for JavaScript",
|
||||
"homepage": "https://docs.npmjs.com/",
|
||||
"author": "Main Author <main@example.com>",
|
||||
"authors": [
|
||||
"Second Author <second@example.com>"
|
||||
],
|
||||
"contributors": [
|
||||
"Contrib One <contrib1@example.com>"
|
||||
],
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "Maintainer One",
|
||||
"email": "maintain1@example.com"
|
||||
}
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/npm/cli"
|
||||
},
|
||||
"license": "Artistic-2.0"
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
{
|
||||
"version": "6.14.6",
|
||||
"name": "npm",
|
||||
"description": "a package manager for JavaScript",
|
||||
"homepage": "https://docs.npmjs.com/",
|
||||
"authors": [
|
||||
"Harry Potter <hp@hogwards.com> (http://youknowwho.com/)",
|
||||
"John Smith <j.smith@something.com> (http://awebsite.com/)"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/npm/cli"
|
||||
},
|
||||
"license": "Artistic-2.0"
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
{
|
||||
"version": "6.14.6",
|
||||
"name": "npm",
|
||||
"description": "a package manager for JavaScript",
|
||||
"homepage": "https://docs.npmjs.com/",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Harry Potter",
|
||||
"email": "hp@hogwards.com",
|
||||
"url": "http://youknowwho.com/"
|
||||
},
|
||||
{
|
||||
"name": "John Smith",
|
||||
"email": "j.smith@something.com",
|
||||
"url": "http://awebsite.com/"
|
||||
}
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/npm/cli"
|
||||
},
|
||||
"license": "Artistic-2.0"
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
{
|
||||
"version": "6.14.6",
|
||||
"name": "npm",
|
||||
"description": "a package manager for JavaScript",
|
||||
"homepage": "https://docs.npmjs.com/",
|
||||
"author": "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
"authors": [
|
||||
"Harry Potter <hp@hogwards.com> (http://youknowwho.com/)",
|
||||
"John Smith <j.smith@something.com> (http://awebsite.com/)"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/npm/cli"
|
||||
},
|
||||
"license": "Artistic-2.0"
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
{
|
||||
"version": "6.14.6",
|
||||
"name": "npm",
|
||||
"description": "a package manager for JavaScript",
|
||||
"homepage": "https://docs.npmjs.com/",
|
||||
"contributors": [
|
||||
"Alice Contributor <alice@example.com>",
|
||||
"Bob Helper <bob@example.com>"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/npm/cli"
|
||||
},
|
||||
"license": "Artistic-2.0"
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
{
|
||||
"version": "6.14.6",
|
||||
"name": "npm",
|
||||
"description": "a package manager for JavaScript",
|
||||
"homepage": "https://docs.npmjs.com/",
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "Charlie Maintainer",
|
||||
"email": "charlie@example.com"
|
||||
},
|
||||
{
|
||||
"name": "Diana Keeper",
|
||||
"email": "diana@example.com"
|
||||
}
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/npm/cli"
|
||||
},
|
||||
"license": "Artistic-2.0"
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user