mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 16:33:21 +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...)
|
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{
|
p := pkg.Package{
|
||||||
Name: u.Name,
|
Name: u.Name,
|
||||||
Version: u.Version,
|
Version: u.Version,
|
||||||
@ -37,7 +70,7 @@ func newPackageJSONPackage(ctx context.Context, u packageJSON, indexLocation fil
|
|||||||
Name: u.Name,
|
Name: u.Name,
|
||||||
Version: u.Version,
|
Version: u.Version,
|
||||||
Description: u.Description,
|
Description: u.Description,
|
||||||
Author: u.Author.AuthorString(),
|
Author: authorInfo,
|
||||||
Homepage: u.Homepage,
|
Homepage: u.Homepage,
|
||||||
URL: u.Repository.URL,
|
URL: u.Repository.URL,
|
||||||
Private: u.Private,
|
Private: u.Private,
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/go-viper/mapstructure/v2"
|
"github.com/go-viper/mapstructure/v2"
|
||||||
|
|
||||||
@ -24,7 +25,10 @@ var _ generic.Parser = parsePackageJSON
|
|||||||
type packageJSON struct {
|
type packageJSON struct {
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
Latest []string `json:"latest"`
|
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"`
|
License json.RawMessage `json:"license"`
|
||||||
Licenses json.RawMessage `json:"licenses"`
|
Licenses json.RawMessage `json:"licenses"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
@ -35,12 +39,14 @@ type packageJSON struct {
|
|||||||
Private bool `json:"private"`
|
Private bool `json:"private"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type author struct {
|
type person struct {
|
||||||
Name string `json:"name" mapstructure:"name"`
|
Name string `json:"name" mapstructure:"name"`
|
||||||
Email string `json:"email" mapstructure:"email"`
|
Email string `json:"email" mapstructure:"email"`
|
||||||
URL string `json:"url" mapstructure:"url"`
|
URL string `json:"url" mapstructure:"url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type people []person
|
||||||
|
|
||||||
type repository struct {
|
type repository struct {
|
||||||
Type string `json:"type" mapstructure:"type"`
|
Type string `json:"type" mapstructure:"type"`
|
||||||
URL string `json:"url" mapstructure:"url"`
|
URL string `json:"url" mapstructure:"url"`
|
||||||
@ -76,9 +82,9 @@ func parsePackageJSON(ctx context.Context, _ file.Resolver, _ *generic.Environme
|
|||||||
return pkgs, nil, nil
|
return pkgs, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *author) UnmarshalJSON(b []byte) error {
|
func (p *person) UnmarshalJSON(b []byte) error {
|
||||||
var authorStr string
|
var authorStr string
|
||||||
var auth author
|
var auth person
|
||||||
|
|
||||||
if err := json.Unmarshal(b, &authorStr); err == nil {
|
if err := json.Unmarshal(b, &authorStr); err == nil {
|
||||||
// successfully parsed as a string, now parse that string into fields
|
// 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *author) AuthorString() string {
|
func (p *person) AuthorString() string {
|
||||||
result := a.Name
|
result := p.Name
|
||||||
if a.Email != "" {
|
if p.Email != "" {
|
||||||
result += fmt.Sprintf(" <%s>", a.Email)
|
result += fmt.Sprintf(" <%s>", p.Email)
|
||||||
}
|
}
|
||||||
if a.URL != "" {
|
if p.URL != "" {
|
||||||
result += fmt.Sprintf(" (%s)", a.URL)
|
result += fmt.Sprintf(" (%s)", p.URL)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@ -210,3 +216,58 @@ func pathContainsNodeModulesDirectory(p string) bool {
|
|||||||
}
|
}
|
||||||
return false
|
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{
|
Metadata: pkg.NpmPackage{
|
||||||
Name: "function-bind",
|
Name: "function-bind",
|
||||||
Version: "1.1.1",
|
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",
|
Homepage: "https://github.com/Raynos/function-bind",
|
||||||
URL: "git://github.com/Raynos/function-bind.git",
|
URL: "git://github.com/Raynos/function-bind.git",
|
||||||
Description: "Implementation of Function.prototype.bind",
|
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 {
|
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