mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 08:23:15 +01:00
feat(javascript): Add dependency parsing (#4304)
* feat: Add dependency parsing to javascript package locks Signed-off-by: Tim Olshansky <456103+timols@users.noreply.github.com> * Bump schema version Signed-off-by: Tim Olshansky <456103+timols@users.noreply.github.com> * Add support for yarn and pnpm, excl. yarn v1 Signed-off-by: Tim Olshansky <456103+timols@users.noreply.github.com> * Add support for dependencies for v1 yarn lock files Signed-off-by: Tim Olshansky <456103+timols@users.noreply.github.com> * Ensure schema is correctly generated Signed-off-by: Tim Olshansky <456103+timols@users.noreply.github.com> * Fix tests Signed-off-by: Tim Olshansky <456103+timols@users.noreply.github.com> * PR feedback Signed-off-by: Tim Olshansky <456103+timols@users.noreply.github.com> --------- Signed-off-by: Tim Olshansky <456103+timols@users.noreply.github.com>
This commit is contained in:
parent
e5711e9b42
commit
4e06a7ab32
@ -3,5 +3,5 @@ package internal
|
||||
const (
|
||||
// JSONSchemaVersion is the current schema version output by the JSON encoder
|
||||
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
|
||||
JSONSchemaVersion = "16.0.41"
|
||||
JSONSchemaVersion = "16.0.42"
|
||||
)
|
||||
|
||||
@ -49,6 +49,7 @@ func AllTypes() []any {
|
||||
pkg.PhpComposerLockEntry{},
|
||||
pkg.PhpPearEntry{},
|
||||
pkg.PhpPeclEntry{},
|
||||
pkg.PnpmLockEntry{},
|
||||
pkg.PortageEntry{},
|
||||
pkg.PythonPackage{},
|
||||
pkg.PythonPdmLockEntry{},
|
||||
|
||||
@ -95,6 +95,7 @@ var jsonTypes = makeJSONTypes(
|
||||
jsonNames(pkg.NpmPackage{}, "javascript-npm-package", "NpmPackageJsonMetadata"),
|
||||
jsonNames(pkg.NpmPackageLockEntry{}, "javascript-npm-package-lock-entry", "NpmPackageLockJsonMetadata"),
|
||||
jsonNames(pkg.YarnLockEntry{}, "javascript-yarn-lock-entry", "YarnLockJsonMetadata"),
|
||||
jsonNames(pkg.PnpmLockEntry{}, "javascript-pnpm-lock-entry"),
|
||||
jsonNames(pkg.PEBinary{}, "pe-binary"),
|
||||
jsonNames(pkg.PhpComposerLockEntry{}, "php-composer-lock-entry", "PhpComposerJsonMetadata"),
|
||||
jsonNamesWithoutLookup(pkg.PhpComposerInstalledEntry{}, "php-composer-installed-entry", "PhpComposerJsonMetadata"), // the legacy value is split into two types, where the other is preferred
|
||||
|
||||
4069
schema/json/schema-16.0.42.json
Normal file
4069
schema/json/schema-16.0.42.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "anchore.io/schema/syft/json/16.0.41/document",
|
||||
"$id": "anchore.io/schema/syft/json/16.0.42/document",
|
||||
"$ref": "#/$defs/Document",
|
||||
"$defs": {
|
||||
"AlpmDbEntry": {
|
||||
@ -1876,15 +1876,48 @@
|
||||
"integrity": {
|
||||
"type": "string",
|
||||
"description": "Integrity is Subresource Integrity hash for verification using standard SRI format (sha512-... or sha1-...). npm changed from SHA-1 to SHA-512 in newer versions. For registry sources this is the integrity from registry, for remote tarballs it's SHA-512 of the file. npm verifies tarball matches this hash before unpacking, throwing EINTEGRITY error if mismatch detected."
|
||||
},
|
||||
"dependencies": {
|
||||
"patternProperties": {
|
||||
".*": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"description": "Dependencies is a map of dependencies and their version markers, i.e. \"lodash\": \"^1.0.0\""
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"resolved",
|
||||
"integrity"
|
||||
"integrity",
|
||||
"dependencies"
|
||||
],
|
||||
"description": "NpmPackageLockEntry represents a single entry within the \"packages\" section of a package-lock.json file."
|
||||
},
|
||||
"JavascriptPnpmLockEntry": {
|
||||
"properties": {
|
||||
"resolution": {
|
||||
"$ref": "#/$defs/PnpmLockResolution",
|
||||
"description": "Resolution is the resolution information for the package"
|
||||
},
|
||||
"dependencies": {
|
||||
"patternProperties": {
|
||||
".*": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"description": "Dependencies is a map of dependencies and their versions"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"resolution",
|
||||
"dependencies"
|
||||
],
|
||||
"description": "PnpmLockEntry represents a single entry in the \"packages\" section of a pnpm-lock.yaml file."
|
||||
},
|
||||
"JavascriptYarnLockEntry": {
|
||||
"properties": {
|
||||
"resolved": {
|
||||
@ -1894,12 +1927,22 @@
|
||||
"integrity": {
|
||||
"type": "string",
|
||||
"description": "Integrity is Subresource Integrity hash for verification (SRI format)"
|
||||
},
|
||||
"dependencies": {
|
||||
"patternProperties": {
|
||||
".*": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"description": "Dependencies is a map of dependencies and their versions"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"resolved",
|
||||
"integrity"
|
||||
"integrity",
|
||||
"dependencies"
|
||||
],
|
||||
"description": "YarnLockEntry represents a single entry section of a yarn.lock file."
|
||||
},
|
||||
@ -2507,6 +2550,9 @@
|
||||
{
|
||||
"$ref": "#/$defs/JavascriptNpmPackageLockEntry"
|
||||
},
|
||||
{
|
||||
"$ref": "#/$defs/JavascriptPnpmLockEntry"
|
||||
},
|
||||
{
|
||||
"$ref": "#/$defs/JavascriptYarnLockEntry"
|
||||
},
|
||||
@ -2958,6 +3004,18 @@
|
||||
],
|
||||
"description": "PhpPeclEntry represents a single package entry found within php pecl metadata files."
|
||||
},
|
||||
"PnpmLockResolution": {
|
||||
"properties": {
|
||||
"integrity": {
|
||||
"type": "string",
|
||||
"description": "Integrity is Subresource Integrity hash for verification (SRI format)"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"integrity"
|
||||
]
|
||||
},
|
||||
"PortageDbEntry": {
|
||||
"properties": {
|
||||
"installedSize": {
|
||||
|
||||
@ -40,6 +40,7 @@ func Test_OriginatorSupplier(t *testing.T) {
|
||||
pkg.PhpComposerInstalledEntry{},
|
||||
pkg.PhpPearEntry{},
|
||||
pkg.PhpPeclEntry{},
|
||||
pkg.PnpmLockEntry{},
|
||||
pkg.PortageEntry{},
|
||||
pkg.PythonPipfileLockEntry{},
|
||||
pkg.PythonPdmLockEntry{},
|
||||
|
||||
87
syft/pkg/cataloger/javascript/dependency.go
Normal file
87
syft/pkg/cataloger/javascript/dependency.go
Normal file
@ -0,0 +1,87 @@
|
||||
package javascript
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/dependency"
|
||||
)
|
||||
|
||||
func packageLockDependencySpecifier(p pkg.Package) dependency.Specification {
|
||||
meta, ok := p.Metadata.(pkg.NpmPackageLockEntry)
|
||||
if !ok {
|
||||
log.Tracef("cataloger failed to extract package lock metadata for package %+v", p.Name)
|
||||
return dependency.Specification{}
|
||||
}
|
||||
|
||||
provides := []string{p.Name}
|
||||
|
||||
var requires []string
|
||||
|
||||
for name, dependencySpecifier := range meta.Dependencies {
|
||||
purl, err := packageurl.FromString(strings.ReplaceAll(dependencySpecifier, "npm:", "pkg:npm/"))
|
||||
if err == nil {
|
||||
// if the package url is valid, include the name from the package url since this is likely an alias
|
||||
var fullName = fmt.Sprintf("%s/%s", purl.Namespace, purl.Name)
|
||||
requires = append(requires, fullName)
|
||||
} else {
|
||||
fmt.Println("error", err)
|
||||
}
|
||||
|
||||
requires = append(requires, name)
|
||||
}
|
||||
|
||||
return dependency.Specification{
|
||||
ProvidesRequires: dependency.ProvidesRequires{
|
||||
Provides: provides,
|
||||
Requires: requires,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func pnpmLockDependencySpecifier(p pkg.Package) dependency.Specification {
|
||||
meta, ok := p.Metadata.(pkg.PnpmLockEntry)
|
||||
if !ok {
|
||||
log.Tracef("cataloger failed to extract pnpm lock metadata for package %+v", p.Name)
|
||||
return dependency.Specification{}
|
||||
}
|
||||
|
||||
provides := []string{p.Name}
|
||||
|
||||
var requires []string
|
||||
|
||||
for name := range meta.Dependencies {
|
||||
requires = append(requires, name)
|
||||
}
|
||||
return dependency.Specification{
|
||||
ProvidesRequires: dependency.ProvidesRequires{
|
||||
Provides: provides,
|
||||
Requires: requires,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func yarnLockDependencySpecifier(p pkg.Package) dependency.Specification {
|
||||
meta, ok := p.Metadata.(pkg.YarnLockEntry)
|
||||
if !ok {
|
||||
log.Tracef("cataloger failed to extract yarn lock metadata for package %+v", p.Name)
|
||||
return dependency.Specification{}
|
||||
}
|
||||
|
||||
provides := []string{p.Name}
|
||||
|
||||
var requires []string
|
||||
|
||||
for name := range meta.Dependencies {
|
||||
requires = append(requires, name)
|
||||
}
|
||||
return dependency.Specification{
|
||||
ProvidesRequires: dependency.ProvidesRequires{
|
||||
Provides: provides,
|
||||
Requires: requires,
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -158,12 +158,12 @@ func newPackageLockV2Package(ctx context.Context, cfg CatalogerConfig, resolver
|
||||
PURL: packageURL(name, u.Version),
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.NpmPackageLockEntry{Resolved: u.Resolved, Integrity: u.Integrity},
|
||||
Metadata: pkg.NpmPackageLockEntry{Resolved: u.Resolved, Integrity: u.Integrity, Dependencies: u.Dependencies},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func newPnpmPackage(ctx context.Context, cfg CatalogerConfig, resolver file.Resolver, location file.Location, name, version string) pkg.Package {
|
||||
func newPnpmPackage(ctx context.Context, cfg CatalogerConfig, resolver file.Resolver, location file.Location, name, version string, integrity string, dependencies map[string]string) pkg.Package {
|
||||
var licenseSet pkg.LicenseSet
|
||||
|
||||
if cfg.SearchRemoteLicenses {
|
||||
@ -187,11 +187,12 @@ func newPnpmPackage(ctx context.Context, cfg CatalogerConfig, resolver file.Reso
|
||||
PURL: packageURL(name, version),
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.PnpmLockEntry{Resolution: pkg.PnpmLockResolution{Integrity: integrity}, Dependencies: dependencies},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func newYarnLockPackage(ctx context.Context, cfg CatalogerConfig, resolver file.Resolver, location file.Location, name, version string, resolved string, integrity string) pkg.Package {
|
||||
func newYarnLockPackage(ctx context.Context, cfg CatalogerConfig, resolver file.Resolver, location file.Location, name, version string, resolved string, integrity string, dependencies map[string]string) pkg.Package {
|
||||
var licenseSet pkg.LicenseSet
|
||||
|
||||
if cfg.SearchRemoteLicenses {
|
||||
@ -215,7 +216,7 @@ func newYarnLockPackage(ctx context.Context, cfg CatalogerConfig, resolver file.
|
||||
PURL: packageURL(name, version),
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.YarnLockEntry{Resolved: resolved, Integrity: integrity},
|
||||
Metadata: pkg.YarnLockEntry{Resolved: resolved, Integrity: integrity, Dependencies: dependencies},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@ -14,6 +14,7 @@ import (
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/dependency"
|
||||
)
|
||||
|
||||
// packageLock represents a JavaScript package.lock json file
|
||||
@ -33,12 +34,13 @@ type lockDependency struct {
|
||||
}
|
||||
|
||||
type lockPackage struct {
|
||||
Name string `json:"name"` // only present in the root package entry (named "")
|
||||
Version string `json:"version"`
|
||||
Resolved string `json:"resolved"`
|
||||
Integrity string `json:"integrity"`
|
||||
License packageLockLicense `json:"license"`
|
||||
Dev bool `json:"dev"`
|
||||
Name string `json:"name"` // only present in the root package entry (named "")
|
||||
Version string `json:"version"`
|
||||
Resolved string `json:"resolved"`
|
||||
Integrity string `json:"integrity"`
|
||||
License packageLockLicense `json:"license"`
|
||||
Dev bool `json:"dev"`
|
||||
Dependencies map[string]string `json:"dependencies"`
|
||||
}
|
||||
|
||||
// packageLockLicense
|
||||
@ -104,16 +106,14 @@ func (a genericPackageLockAdapter) parsePackageLock(ctx context.Context, resolve
|
||||
name = pkgMeta.Name
|
||||
}
|
||||
|
||||
pkgs = append(
|
||||
pkgs,
|
||||
newPackageLockV2Package(ctx, a.cfg, resolver, reader.Location, getNameFromPath(name), pkgMeta),
|
||||
)
|
||||
newPkg := newPackageLockV2Package(ctx, a.cfg, resolver, reader.Location, getNameFromPath(name), pkgMeta)
|
||||
pkgs = append(pkgs, newPkg)
|
||||
}
|
||||
}
|
||||
|
||||
pkg.Sort(pkgs)
|
||||
|
||||
return pkgs, nil, unknown.IfEmptyf(pkgs, "unable to determine packages")
|
||||
return pkgs, dependency.Resolve(packageLockDependencySpecifier, pkgs), unknown.IfEmptyf(pkgs, "unable to determine packages")
|
||||
}
|
||||
|
||||
func (licenses *packageLockLicense) UnmarshalJSON(data []byte) (err error) {
|
||||
|
||||
@ -122,7 +122,7 @@ func TestParsePackageLockV2(t *testing.T) {
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
PURL: "pkg:npm/npm@6.14.6",
|
||||
Metadata: pkg.NpmPackageLockEntry{},
|
||||
Metadata: pkg.NpmPackageLockEntry{Dependencies: map[string]string{"@types/react": "^18.0.9"}},
|
||||
},
|
||||
{
|
||||
Name: "@types/prop-types",
|
||||
@ -144,7 +144,7 @@ func TestParsePackageLockV2(t *testing.T) {
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocationsWithContext(ctx, "MIT", file.NewLocation(fixture)),
|
||||
),
|
||||
Metadata: pkg.NpmPackageLockEntry{Resolved: "https://registry.npmjs.org/@types/react/-/react-18.0.17.tgz", Integrity: "sha1-RYPZwyLWfv5LOak10iPtzHBQzPQ="},
|
||||
Metadata: pkg.NpmPackageLockEntry{Resolved: "https://registry.npmjs.org/@types/react/-/react-18.0.17.tgz", Integrity: "sha1-RYPZwyLWfv5LOak10iPtzHBQzPQ=", Dependencies: map[string]string{"@types/prop-types": "*", "@types/scheduler": "*", "csstype": "^3.0.2"}},
|
||||
},
|
||||
{
|
||||
Name: "@types/scheduler",
|
||||
@ -172,6 +172,28 @@ func TestParsePackageLockV2(t *testing.T) {
|
||||
for i := range expectedPkgs {
|
||||
expectedPkgs[i].Locations.Add(file.NewLocation(fixture))
|
||||
}
|
||||
expectedRelationships = []artifact.Relationship{
|
||||
{
|
||||
From: expectedPkgs[1],
|
||||
To: expectedPkgs[2],
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: expectedPkgs[3],
|
||||
To: expectedPkgs[2],
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: expectedPkgs[4],
|
||||
To: expectedPkgs[2],
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: expectedPkgs[2],
|
||||
To: expectedPkgs[0],
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
}
|
||||
adapter := newGenericPackageLockAdapter(CatalogerConfig{})
|
||||
pkgtest.TestFileParser(t, fixture, adapter.parsePackageLock, expectedPkgs, expectedRelationships)
|
||||
}
|
||||
@ -186,7 +208,7 @@ func TestParsePackageLockV3(t *testing.T) {
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
PURL: "pkg:npm/lock-v3-fixture@1.0.0",
|
||||
Metadata: pkg.NpmPackageLockEntry{},
|
||||
Metadata: pkg.NpmPackageLockEntry{Dependencies: map[string]string{"@types/react": "^18.0.9"}},
|
||||
},
|
||||
{
|
||||
Name: "@types/prop-types",
|
||||
@ -202,7 +224,7 @@ func TestParsePackageLockV3(t *testing.T) {
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
PURL: "pkg:npm/%40types/react@18.0.20",
|
||||
Metadata: pkg.NpmPackageLockEntry{Resolved: "https://registry.npmjs.org/@types/react/-/react-18.0.20.tgz", Integrity: "sha512-MWul1teSPxujEHVwZl4a5HxQ9vVNsjTchVA+xRqv/VYGCuKGAU6UhfrTdF5aBefwD1BHUD8i/zq+O/vyCm/FrA=="},
|
||||
Metadata: pkg.NpmPackageLockEntry{Resolved: "https://registry.npmjs.org/@types/react/-/react-18.0.20.tgz", Integrity: "sha512-MWul1teSPxujEHVwZl4a5HxQ9vVNsjTchVA+xRqv/VYGCuKGAU6UhfrTdF5aBefwD1BHUD8i/zq+O/vyCm/FrA==", Dependencies: map[string]string{"@types/prop-types": "*", "@types/scheduler": "*", "csstype": "^3.0.2"}},
|
||||
},
|
||||
{
|
||||
Name: "@types/scheduler",
|
||||
@ -224,6 +246,28 @@ func TestParsePackageLockV3(t *testing.T) {
|
||||
for i := range expectedPkgs {
|
||||
expectedPkgs[i].Locations.Add(file.NewLocation(fixture))
|
||||
}
|
||||
expectedRelationships = []artifact.Relationship{
|
||||
{
|
||||
From: expectedPkgs[1],
|
||||
To: expectedPkgs[2],
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: expectedPkgs[3],
|
||||
To: expectedPkgs[2],
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: expectedPkgs[4],
|
||||
To: expectedPkgs[2],
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: expectedPkgs[2],
|
||||
To: expectedPkgs[0],
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
}
|
||||
adapter := newGenericPackageLockAdapter(CatalogerConfig{})
|
||||
pkgtest.TestFileParser(t, fixture, adapter.parsePackageLock, expectedPkgs, expectedRelationships)
|
||||
}
|
||||
@ -271,7 +315,7 @@ func TestParsePackageLockAlias(t *testing.T) {
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocationsWithContext(ctx, "ISC", file.NewLocation(packageLockV2)),
|
||||
),
|
||||
Metadata: pkg.NpmPackageLockEntry{},
|
||||
Metadata: pkg.NpmPackageLockEntry{Dependencies: map[string]string{"case": "1.6.2", "case-alias": "npm:case@^1.6.3", "chai": "npm:@bundled-es-modules/chai@^4.2.2"}},
|
||||
}
|
||||
|
||||
for _, pl := range packageLocks {
|
||||
@ -285,6 +329,26 @@ func TestParsePackageLockAlias(t *testing.T) {
|
||||
for i := range expected {
|
||||
expected[i].Locations.Add(file.NewLocation(pl))
|
||||
}
|
||||
|
||||
if pl == packageLockV2 {
|
||||
expectedRelationships = []artifact.Relationship{
|
||||
{
|
||||
From: expected[0],
|
||||
To: expected[3],
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: expected[1],
|
||||
To: expected[3],
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: expected[2],
|
||||
To: expected[3],
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
}
|
||||
}
|
||||
adapter := newGenericPackageLockAdapter(CatalogerConfig{})
|
||||
pkgtest.TestFileParser(t, pl, adapter.parsePackageLock, expected, expectedRelationships)
|
||||
}
|
||||
@ -304,7 +368,7 @@ func TestParsePackageLockLicenseWithArray(t *testing.T) {
|
||||
pkg.NewLicenseFromLocationsWithContext(ctx, "ISC", file.NewLocation(fixture)),
|
||||
),
|
||||
PURL: "pkg:npm/tmp@1.0.0",
|
||||
Metadata: pkg.NpmPackageLockEntry{},
|
||||
Metadata: pkg.NpmPackageLockEntry{Dependencies: map[string]string{"pause-stream": "0.0.11"}},
|
||||
},
|
||||
{
|
||||
Name: "pause-stream",
|
||||
@ -317,7 +381,7 @@ func TestParsePackageLockLicenseWithArray(t *testing.T) {
|
||||
pkg.NewLicenseFromLocationsWithContext(ctx, "Apache2", file.NewLocation(fixture)),
|
||||
),
|
||||
PURL: "pkg:npm/pause-stream@0.0.11",
|
||||
Metadata: pkg.NpmPackageLockEntry{},
|
||||
Metadata: pkg.NpmPackageLockEntry{Dependencies: map[string]string{"through": "~2.3"}},
|
||||
},
|
||||
{
|
||||
Name: "through",
|
||||
@ -334,6 +398,19 @@ func TestParsePackageLockLicenseWithArray(t *testing.T) {
|
||||
for i := range expectedPkgs {
|
||||
expectedPkgs[i].Locations.Add(file.NewLocation(fixture))
|
||||
}
|
||||
|
||||
expectedRelationships = []artifact.Relationship{
|
||||
{
|
||||
From: expectedPkgs[2],
|
||||
To: expectedPkgs[1],
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: expectedPkgs[1],
|
||||
To: expectedPkgs[0],
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
}
|
||||
adapter := newGenericPackageLockAdapter(CatalogerConfig{})
|
||||
pkgtest.TestFileParser(t, fixture, adapter.parsePackageLock, expectedPkgs, expectedRelationships)
|
||||
}
|
||||
|
||||
@ -16,12 +16,15 @@ import (
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/dependency"
|
||||
)
|
||||
|
||||
// pnpmPackage holds the raw name and version extracted from the lockfile.
|
||||
type pnpmPackage struct {
|
||||
Name string
|
||||
Version string
|
||||
Name string
|
||||
Version string
|
||||
Integrity string
|
||||
Dependencies map[string]string
|
||||
}
|
||||
|
||||
// pnpmLockfileParser defines the interface for parsing different versions of pnpm lockfiles.
|
||||
@ -29,17 +32,35 @@ type pnpmLockfileParser interface {
|
||||
Parse(version float64, data []byte) ([]pnpmPackage, error)
|
||||
}
|
||||
|
||||
type pnpmV6PackageEntry struct {
|
||||
Resolution map[string]string `yaml:"resolution"`
|
||||
Dependencies map[string]string `yaml:"dependencies"`
|
||||
}
|
||||
|
||||
// pnpmV6LockYaml represents the structure of pnpm lockfiles for versions < 9.0.
|
||||
type pnpmV6LockYaml struct {
|
||||
Dependencies map[string]interface{} `yaml:"dependencies"`
|
||||
Packages map[string]interface{} `yaml:"packages"`
|
||||
Dependencies map[string]interface{} `yaml:"dependencies"`
|
||||
Packages map[string]pnpmV6PackageEntry `yaml:"packages"`
|
||||
}
|
||||
|
||||
type pnpmV9SnapshotEntry struct {
|
||||
Dependencies map[string]string `yaml:"dependencies"`
|
||||
Optional bool `yaml:"optional"`
|
||||
OptionalDependencies map[string]string `yaml:"optionalDependencies"`
|
||||
TransitivePeerDependencies []string `yaml:"transitivePeerDependencies"`
|
||||
}
|
||||
|
||||
type pnpmV9PackageEntry struct {
|
||||
Resolution map[string]string `yaml:"resolution"`
|
||||
PeerDependencies map[string]string `yaml:"peerDependencies"`
|
||||
}
|
||||
|
||||
// pnpmV9LockYaml represents the structure of pnpm lockfiles for versions >= 9.0.
|
||||
type pnpmV9LockYaml struct {
|
||||
LockfileVersion string `yaml:"lockfileVersion"`
|
||||
Importers map[string]interface{} `yaml:"importers"` // Using interface{} for forward compatibility
|
||||
Packages map[string]interface{} `yaml:"packages"`
|
||||
LockfileVersion string `yaml:"lockfileVersion"`
|
||||
Importers map[string]interface{} `yaml:"importers"` // Using interface{} for forward compatibility
|
||||
Packages map[string]pnpmV9PackageEntry `yaml:"packages"`
|
||||
Snapshots map[string]pnpmV9SnapshotEntry `yaml:"snapshots"`
|
||||
}
|
||||
|
||||
type genericPnpmLockAdapter struct {
|
||||
@ -77,14 +98,26 @@ func (p *pnpmV6LockYaml) Parse(version float64, data []byte) ([]pnpmPackage, err
|
||||
}
|
||||
|
||||
// All transitive dependencies
|
||||
for key := range p.Packages {
|
||||
for key, pkgInfo := range p.Packages {
|
||||
name, ver, ok := parsePnpmPackageKey(key, splitChar)
|
||||
if !ok {
|
||||
log.WithFields("key", key).Trace("unable to parse pnpm package key")
|
||||
continue
|
||||
}
|
||||
pkgKey := name + "@" + ver
|
||||
packages[pkgKey] = pnpmPackage{Name: name, Version: ver}
|
||||
|
||||
integrity := ""
|
||||
if value, ok := pkgInfo.Resolution["integrity"]; ok {
|
||||
integrity = value
|
||||
}
|
||||
|
||||
dependencies := make(map[string]string)
|
||||
for depName, depVersion := range pkgInfo.Dependencies {
|
||||
var normalizedVersion = strings.SplitN(depVersion, "(", 2)[0]
|
||||
dependencies[depName] = normalizedVersion
|
||||
}
|
||||
|
||||
packages[pkgKey] = pnpmPackage{Name: name, Version: ver, Integrity: integrity, Dependencies: dependencies}
|
||||
}
|
||||
|
||||
return toSortedSlice(packages), nil
|
||||
@ -100,7 +133,7 @@ func (p *pnpmV9LockYaml) Parse(_ float64, data []byte) ([]pnpmPackage, error) {
|
||||
|
||||
// In v9, all resolved dependencies are listed in the top-level "packages" field.
|
||||
// The key format is like /<name>@<version> or /<name>@<version>(<peer-deps>).
|
||||
for key := range p.Packages {
|
||||
for key, entry := range p.Packages {
|
||||
// The separator for name and version is consistently '@' in v9+ keys.
|
||||
name, ver, ok := parsePnpmPackageKey(key, "@")
|
||||
if !ok {
|
||||
@ -108,7 +141,26 @@ func (p *pnpmV9LockYaml) Parse(_ float64, data []byte) ([]pnpmPackage, error) {
|
||||
continue
|
||||
}
|
||||
pkgKey := name + "@" + ver
|
||||
packages[pkgKey] = pnpmPackage{Name: name, Version: ver}
|
||||
packages[pkgKey] = pnpmPackage{Name: name, Version: ver, Integrity: entry.Resolution["integrity"]}
|
||||
}
|
||||
|
||||
for key, snapshotInfo := range p.Snapshots {
|
||||
name, ver, ok := parsePnpmPackageKey(key, "@")
|
||||
if !ok {
|
||||
log.WithFields("key", key).Trace("unable to parse pnpm v9 package snapshot key")
|
||||
continue
|
||||
}
|
||||
pkgKey := name + "@" + ver
|
||||
if pkg, ok := packages[pkgKey]; ok {
|
||||
pkg.Dependencies = make(map[string]string)
|
||||
for name, versionSpecifier := range snapshotInfo.Dependencies {
|
||||
var normalizedVersion = strings.SplitN(versionSpecifier, "(", 2)[0]
|
||||
pkg.Dependencies[name] = normalizedVersion
|
||||
}
|
||||
packages[pkgKey] = pkg
|
||||
} else {
|
||||
log.WithFields("package", pkgKey).Trace("package not found in packages map")
|
||||
}
|
||||
}
|
||||
|
||||
return toSortedSlice(packages), nil
|
||||
@ -149,10 +201,10 @@ func (a genericPnpmLockAdapter) parsePnpmLock(ctx context.Context, resolver file
|
||||
|
||||
packages := make([]pkg.Package, len(pnpmPkgs))
|
||||
for i, p := range pnpmPkgs {
|
||||
packages[i] = newPnpmPackage(ctx, a.cfg, resolver, reader.Location, p.Name, p.Version)
|
||||
packages[i] = newPnpmPackage(ctx, a.cfg, resolver, reader.Location, p.Name, p.Version, p.Integrity, p.Dependencies)
|
||||
}
|
||||
|
||||
return packages, nil, unknown.IfEmptyf(packages, "unable to determine packages")
|
||||
return packages, dependency.Resolve(pnpmLockDependencySpecifier, packages), unknown.IfEmptyf(packages, "unable to determine packages")
|
||||
}
|
||||
|
||||
// parseVersionField extracts the version string from a dependency entry.
|
||||
|
||||
@ -28,6 +28,7 @@ func TestParsePnpmLock(t *testing.T) {
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.PnpmLockEntry{Resolution: pkg.PnpmLockResolution{}},
|
||||
},
|
||||
{
|
||||
Name: "picocolors",
|
||||
@ -36,6 +37,7 @@ func TestParsePnpmLock(t *testing.T) {
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.PnpmLockEntry{Resolution: pkg.PnpmLockResolution{}},
|
||||
},
|
||||
{
|
||||
Name: "source-map-js",
|
||||
@ -44,6 +46,7 @@ func TestParsePnpmLock(t *testing.T) {
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.PnpmLockEntry{Resolution: pkg.PnpmLockResolution{}},
|
||||
},
|
||||
{
|
||||
Name: "@bcoe/v8-coverage",
|
||||
@ -52,6 +55,10 @@ func TestParsePnpmLock(t *testing.T) {
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.PnpmLockEntry{
|
||||
Resolution: pkg.PnpmLockResolution{Integrity: "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw=="},
|
||||
Dependencies: map[string]string{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -73,6 +80,19 @@ func TestParsePnpmV6Lock(t *testing.T) {
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.PnpmLockEntry{
|
||||
Resolution: pkg.PnpmLockResolution{Integrity: "sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA=="},
|
||||
Dependencies: map[string]string{
|
||||
"@adobe/css-tools": "4.2.0",
|
||||
"@babel/runtime": "7.21.0",
|
||||
"@types/testing-library__jest-dom": "5.14.5",
|
||||
"aria-query": "5.1.3",
|
||||
"chalk": "3.0.0",
|
||||
"css.escape": "1.5.1",
|
||||
"dom-accessibility-api": "0.5.16",
|
||||
"lodash": "4.17.21",
|
||||
"redent": "3.0.0",
|
||||
}},
|
||||
},
|
||||
{
|
||||
Name: "@testing-library/react",
|
||||
@ -81,6 +101,16 @@ func TestParsePnpmV6Lock(t *testing.T) {
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.PnpmLockEntry{
|
||||
Resolution: pkg.PnpmLockResolution{Integrity: "sha512-sXOGON+WNTh3MLE9rve97ftaZukN3oNf2KjDy7YTx6hcTO2uuLHuCGynMDhFwGw/jYf4OJ2Qk0i4i79qMNNkyw=="},
|
||||
Dependencies: map[string]string{
|
||||
"@babel/runtime": "7.21.0",
|
||||
"@testing-library/dom": "8.20.0",
|
||||
"@types/react-dom": "18.2.1",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "@testing-library/user-event",
|
||||
@ -89,6 +119,13 @@ func TestParsePnpmV6Lock(t *testing.T) {
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.PnpmLockEntry{
|
||||
Resolution: pkg.PnpmLockResolution{Integrity: "sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg=="},
|
||||
Dependencies: map[string]string{
|
||||
"@babel/runtime": "7.21.0",
|
||||
"@testing-library/dom": "9.2.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "react",
|
||||
@ -97,6 +134,12 @@ func TestParsePnpmV6Lock(t *testing.T) {
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.PnpmLockEntry{
|
||||
Resolution: pkg.PnpmLockResolution{Integrity: "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="},
|
||||
Dependencies: map[string]string{
|
||||
"loose-envify": "1.4.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "react-dom",
|
||||
@ -105,6 +148,14 @@ func TestParsePnpmV6Lock(t *testing.T) {
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.PnpmLockEntry{
|
||||
Resolution: pkg.PnpmLockResolution{Integrity: "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g=="},
|
||||
Dependencies: map[string]string{
|
||||
"loose-envify": "1.4.0",
|
||||
"react": "18.2.0",
|
||||
"scheduler": "0.23.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "web-vitals",
|
||||
@ -113,6 +164,10 @@ func TestParsePnpmV6Lock(t *testing.T) {
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.PnpmLockEntry{
|
||||
Resolution: pkg.PnpmLockResolution{Integrity: "sha512-sVWcwhU5mX6crfI5Vd2dC4qchyTqxV8URinzt25XqVh+bHEPGH4C3NPrNionCP7Obx59wrYEbNlw4Z8sjALzZg=="},
|
||||
Dependencies: map[string]string{},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "@babel/core",
|
||||
@ -121,6 +176,26 @@ func TestParsePnpmV6Lock(t *testing.T) {
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.PnpmLockEntry{
|
||||
Resolution: pkg.PnpmLockResolution{Integrity: "sha512-qt/YV149Jman/6AfmlxJ04LMIu8bMoyl3RB91yTFrxQmgbrSvQMy7cI8Q62FHx1t8wJ8B5fu0UDoLwHAhUo1QA=="},
|
||||
Dependencies: map[string]string{
|
||||
"@ampproject/remapping": "2.2.1",
|
||||
"@babel/code-frame": "7.21.4",
|
||||
"@babel/generator": "7.21.4",
|
||||
"@babel/helper-compilation-targets": "7.21.4",
|
||||
"@babel/helper-module-transforms": "7.21.2",
|
||||
"@babel/helpers": "7.21.0",
|
||||
"@babel/parser": "7.21.4",
|
||||
"@babel/template": "7.20.7",
|
||||
"@babel/traverse": "7.21.4",
|
||||
"@babel/types": "7.21.4",
|
||||
"convert-source-map": "1.9.0",
|
||||
"debug": "4.3.4",
|
||||
"gensync": "1.0.0-beta.2",
|
||||
"json5": "2.2.3",
|
||||
"semver": "6.3.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "@types/eslint",
|
||||
@ -129,6 +204,13 @@ func TestParsePnpmV6Lock(t *testing.T) {
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.PnpmLockEntry{
|
||||
Resolution: pkg.PnpmLockResolution{Integrity: "sha512-Piet7dG2JBuDIfohBngQ3rCt7MgO9xCO4xIMKxBThCq5PNRB91IjlJ10eJVwfoNtvTErmxLzwBZ7rHZtbOMmFQ=="},
|
||||
Dependencies: map[string]string{
|
||||
"@types/estree": "1.0.1",
|
||||
"@types/json-schema": "7.0.11",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "read-cache",
|
||||
@ -137,6 +219,12 @@ func TestParsePnpmV6Lock(t *testing.T) {
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.PnpmLockEntry{
|
||||
Resolution: pkg.PnpmLockResolution{Integrity: "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="},
|
||||
Dependencies: map[string]string{
|
||||
"pify": "2.3.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "schema-utils",
|
||||
@ -145,9 +233,33 @@ func TestParsePnpmV6Lock(t *testing.T) {
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.PnpmLockEntry{
|
||||
Resolution: pkg.PnpmLockResolution{Integrity: "sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg=="},
|
||||
Dependencies: map[string]string{
|
||||
"@types/json-schema": "7.0.11",
|
||||
"ajv": "6.12.6",
|
||||
"ajv-keywords": "3.5.2",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
expectedRelationships = []artifact.Relationship{
|
||||
{
|
||||
From: expectedPkgs[3],
|
||||
To: expectedPkgs[1],
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: expectedPkgs[3],
|
||||
To: expectedPkgs[4],
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: expectedPkgs[4],
|
||||
To: expectedPkgs[1],
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
}
|
||||
|
||||
adapter := newGenericPnpmLockAdapter(CatalogerConfig{})
|
||||
pkgtest.TestFileParser(t, fixture, adapter.parsePnpmLock, expectedPkgs, expectedRelationships)
|
||||
}
|
||||
@ -165,6 +277,7 @@ func TestParsePnpmLockV9(t *testing.T) {
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.PnpmLockEntry{Resolution: pkg.PnpmLockResolution{Integrity: "sha512-4RjkiFFI42+268iBv2nC+iMLTJGQW3u9P7YvA3x/6MDrJ9IYZ8I/xx5a2GIhY5gBTOcI4iC5S5in2fGjE+P4Yw=="}},
|
||||
},
|
||||
{
|
||||
Name: "@babel/helper-plugin-utils",
|
||||
@ -173,6 +286,7 @@ func TestParsePnpmLockV9(t *testing.T) {
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.PnpmLockEntry{Resolution: pkg.PnpmLockResolution{Integrity: "sha512-8A2+zKm53/3w4rwbX11FMW/yFS6c5Vam02P/dw01aK6KbwkKqBaIt3eEATiKtn9I2uS1itk8/aZ2yZ/kURee4Q=="}},
|
||||
},
|
||||
{
|
||||
Name: "is-positive",
|
||||
@ -181,6 +295,7 @@ func TestParsePnpmLockV9(t *testing.T) {
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.PnpmLockEntry{Resolution: pkg.PnpmLockResolution{Integrity: "sha512-9ffLCf_f5sopimAhg2g91a7b9Rw5A1aA9eI6S391S3VEzYw99I3iKjcZGxLp25s0cRxNBV5aL2mhn7421SSlA=="}},
|
||||
},
|
||||
{
|
||||
Name: "rollup",
|
||||
@ -189,6 +304,7 @@ func TestParsePnpmLockV9(t *testing.T) {
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.PnpmLockEntry{Resolution: pkg.PnpmLockResolution{Integrity: "sha512-QpQY2Q5i0y0Q3RoAvoChE/R5iN2k05N//bNvQbC2XvRjHFT1qWJ2r3n1bNqE+gGRJaeuQf0BxE42D7CyuLh3ZQ=="}},
|
||||
},
|
||||
}
|
||||
adapter := newGenericPnpmLockAdapter(CatalogerConfig{})
|
||||
@ -196,6 +312,117 @@ func TestParsePnpmLockV9(t *testing.T) {
|
||||
pkgtest.TestFileParser(t, fixture, adapter.parsePnpmLock, expected, expectedRelationships)
|
||||
}
|
||||
|
||||
func TestParsePnpmLockV9WithDependencies(t *testing.T) {
|
||||
adapter := newGenericPnpmLockAdapter(CatalogerConfig{})
|
||||
fixture := "test-fixtures/pnpm-v9-snapshots/pnpm-lock.yaml"
|
||||
locationSet := file.NewLocationSet(file.NewLocation(fixture))
|
||||
expectedPkgs := []pkg.Package{
|
||||
{
|
||||
Name: "cross-spawn",
|
||||
Version: "7.0.6",
|
||||
PURL: "pkg:npm/cross-spawn@7.0.6",
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.PnpmLockEntry{
|
||||
Resolution: pkg.PnpmLockResolution{Integrity: "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="},
|
||||
Dependencies: map[string]string{
|
||||
"path-key": "3.1.1",
|
||||
"shebang-command": "2.0.0",
|
||||
"which": "2.0.2",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "isexe",
|
||||
Version: "2.0.0",
|
||||
PURL: "pkg:npm/isexe@2.0.0",
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.PnpmLockEntry{Resolution: pkg.PnpmLockResolution{Integrity: "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="},
|
||||
Dependencies: map[string]string{},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "path-key",
|
||||
Version: "3.1.1",
|
||||
PURL: "pkg:npm/path-key@3.1.1",
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.PnpmLockEntry{Resolution: pkg.PnpmLockResolution{Integrity: "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="},
|
||||
Dependencies: map[string]string{},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "shebang-command",
|
||||
Version: "2.0.0",
|
||||
PURL: "pkg:npm/shebang-command@2.0.0",
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.PnpmLockEntry{Resolution: pkg.PnpmLockResolution{Integrity: "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="},
|
||||
Dependencies: map[string]string{
|
||||
"shebang-regex": "3.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "shebang-regex",
|
||||
Version: "3.0.0",
|
||||
PURL: "pkg:npm/shebang-regex@3.0.0",
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.PnpmLockEntry{Resolution: pkg.PnpmLockResolution{Integrity: "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="},
|
||||
Dependencies: map[string]string{},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "which",
|
||||
Version: "2.0.2",
|
||||
PURL: "pkg:npm/which@2.0.2",
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.PnpmLockEntry{Resolution: pkg.PnpmLockResolution{Integrity: "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="},
|
||||
Dependencies: map[string]string{
|
||||
"isexe": "2.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
expectedRelationships := []artifact.Relationship{
|
||||
{
|
||||
From: expectedPkgs[1],
|
||||
To: expectedPkgs[5],
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: expectedPkgs[2],
|
||||
To: expectedPkgs[0],
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: expectedPkgs[3],
|
||||
To: expectedPkgs[0],
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: expectedPkgs[4],
|
||||
To: expectedPkgs[3],
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: expectedPkgs[5],
|
||||
To: expectedPkgs[0],
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
}
|
||||
pkgtest.TestFileParser(t, fixture, adapter.parsePnpmLock, expectedPkgs, expectedRelationships)
|
||||
}
|
||||
|
||||
func TestSearchPnpmForLicenses(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
fixture := "test-fixtures/pnpm-remote/pnpm-lock.yaml"
|
||||
@ -228,6 +455,10 @@ func TestSearchPnpmForLicenses(t *testing.T) {
|
||||
Licenses: pkg.NewLicenseSet(pkg.NewLicenseWithContext(ctx, "MIT")),
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.PnpmLockEntry{
|
||||
Resolution: pkg.PnpmLockResolution{Integrity: "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw=="},
|
||||
Dependencies: map[string]string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -1,18 +1,25 @@
|
||||
package javascript
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"maps"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/scylladb/go-set/strset"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/unknown"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/dependency"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -22,10 +29,6 @@ var (
|
||||
// "@babel/code-frame@^7.0.0" returns "@babel/code-frame"
|
||||
packageNameExp = regexp.MustCompile(`^"?((?:@\w[\w-_.]*\/)?\w[\w-_.]*)@`)
|
||||
|
||||
// versionExp matches the "version" line of a yarn.lock entry and captures the version value.
|
||||
// For example: version "4.10.1" (...and the value "4.10.1" is captured)
|
||||
versionExp = regexp.MustCompile(`^\W+version(?:\W+"|:\W+)([\w-_.]+)"?`)
|
||||
|
||||
// packageURLExp matches the name and version of the dependency in yarn.lock
|
||||
// from the resolved URL, including scope/namespace prefix if any.
|
||||
// For example:
|
||||
@ -34,21 +37,30 @@ var (
|
||||
//
|
||||
// `resolved "https://registry.yarnpkg.com/@4lolo/resize-observer-polyfill/-/resize-observer-polyfill-1.5.2.tgz#58868fc7224506236b5550d0c68357f0a874b84b"`
|
||||
// would return "@4lolo/resize-observer-polyfill" and "1.5.2"
|
||||
packageURLExp = regexp.MustCompile(`^\s+resolved\s+"https://registry\.(?:yarnpkg\.com|npmjs\.org)/(.+?)/-/(?:.+?)-(\d+\..+?)\.tgz`)
|
||||
packageURLExp = regexp.MustCompile(`^resolved\s+"https://registry\.(?:yarnpkg\.com|npmjs\.org)/(.+?)/-/(?:.+?)-(\d+\..+?)\.tgz`)
|
||||
|
||||
// resolvedExp matches the resolved of the dependency in yarn.lock
|
||||
// For example:
|
||||
// resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
|
||||
// would return "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
|
||||
resolvedExp = regexp.MustCompile(`^\s+resolved\s+"(.+?)"`)
|
||||
|
||||
// integrityExp matches the integrity of the dependency in yarn.lock
|
||||
// For example:
|
||||
// integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
|
||||
// would return "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==""
|
||||
integrityExp = regexp.MustCompile(`^\s+integrity\s+([^\s]+)`)
|
||||
resolvedExp = regexp.MustCompile(`^resolved\s+"(.+?)"`)
|
||||
)
|
||||
|
||||
type yarnPackage struct {
|
||||
Name string
|
||||
Version string
|
||||
Resolved string
|
||||
Integrity string
|
||||
Dependencies map[string]string // We don't currently support dependencies for yarn v1 lock files
|
||||
}
|
||||
|
||||
type yarnV2PackageEntry struct {
|
||||
Version string `yaml:"version"`
|
||||
Resolution string `yaml:"resolution"`
|
||||
Checksum string `yaml:"checksum"`
|
||||
Dependencies map[string]string `yaml:"dependencies"`
|
||||
}
|
||||
|
||||
type genericYarnLockAdapter struct {
|
||||
cfg CatalogerConfig
|
||||
}
|
||||
@ -59,6 +71,99 @@ func newGenericYarnLockAdapter(cfg CatalogerConfig) genericYarnLockAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
func parseYarnV1LockFile(reader io.ReadCloser) ([]yarnPackage, error) {
|
||||
content, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read yarn.lock file: %w", err)
|
||||
}
|
||||
|
||||
re := regexp.MustCompile(`\r?\n`)
|
||||
lines := re.Split(string(content), -1)
|
||||
var pkgs []yarnPackage
|
||||
var pkg = yarnPackage{}
|
||||
var seenPkgs = strset.New()
|
||||
dependencies := make(map[string]string)
|
||||
|
||||
for _, line := range lines {
|
||||
if strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
// Blank lines indicate the end of a package entry, so we add the package
|
||||
// to the list and reset the dependencies
|
||||
if len(line) == 0 && len(pkg.Name) > 0 && !seenPkgs.Has(pkg.Name+"@"+pkg.Version) {
|
||||
pkg.Dependencies = dependencies
|
||||
pkgs = append(pkgs, pkg)
|
||||
seenPkgs.Add(pkg.Name + "@" + pkg.Version)
|
||||
dependencies = make(map[string]string)
|
||||
pkg = yarnPackage{}
|
||||
continue
|
||||
}
|
||||
// The first line of a package entry is the name of the package with no
|
||||
// leading spaces
|
||||
if !strings.HasPrefix(line, " ") {
|
||||
name := line
|
||||
pkg.Name = findPackageName(name)
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(line, " ") && !strings.HasPrefix(line, " ") {
|
||||
line = strings.Trim(line, " ")
|
||||
array := strings.Split(line, " ")
|
||||
switch array[0] {
|
||||
case "version":
|
||||
pkg.Version = strings.Trim(array[1], "\"")
|
||||
case "resolved":
|
||||
name, version, resolved := findResolvedPackageAndVersion(line)
|
||||
if name != "" && version != "" && resolved != "" {
|
||||
pkg.Name = name
|
||||
pkg.Version = version
|
||||
pkg.Resolved = resolved
|
||||
} else {
|
||||
pkg.Resolved = strings.Trim(array[1], "\"")
|
||||
}
|
||||
case "integrity":
|
||||
pkg.Integrity = strings.Trim(array[1], "\"")
|
||||
}
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(line, " ") {
|
||||
line = strings.Trim(line, " ")
|
||||
array := strings.Split(line, " ")
|
||||
dependencyName := strings.Trim(array[0], "\"")
|
||||
dependencyVersion := strings.Trim(array[1], "\"")
|
||||
dependencies[dependencyName] = dependencyVersion
|
||||
}
|
||||
}
|
||||
// If the last package in the list is not the same as the current package, add the current package
|
||||
// to the list. In case there was no trailing new line before we hit EOF.
|
||||
if len(pkg.Name) > 0 && !seenPkgs.Has(pkg.Name+"@"+pkg.Version) {
|
||||
pkg.Dependencies = dependencies
|
||||
pkgs = append(pkgs, pkg)
|
||||
seenPkgs.Add(pkg.Name + "@" + pkg.Version)
|
||||
}
|
||||
|
||||
return pkgs, nil
|
||||
}
|
||||
|
||||
func parseYarnLockYaml(reader io.ReadCloser) ([]yarnPackage, error) {
|
||||
var lockfile = map[string]yarnV2PackageEntry{}
|
||||
if err := yaml.NewDecoder(reader, yaml.AllowDuplicateMapKey()).Decode(&lockfile); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal yarn v2 lockfile: %w", err)
|
||||
}
|
||||
|
||||
packages := make(map[string]yarnPackage)
|
||||
for key, value := range lockfile {
|
||||
packageName := findPackageName(key)
|
||||
if packageName == "" {
|
||||
log.WithFields("key", key).Error("unable to parse yarn v2 package key")
|
||||
continue
|
||||
}
|
||||
|
||||
packages[packageName] = yarnPackage{Name: packageName, Version: value.Version, Resolved: value.Resolution, Integrity: value.Checksum, Dependencies: value.Dependencies}
|
||||
}
|
||||
|
||||
return slices.Collect(maps.Values(packages)), nil
|
||||
}
|
||||
|
||||
func (a genericYarnLockAdapter) parseYarnLock(ctx context.Context, resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
// in the case we find yarn.lock files in the node_modules directories, skip those
|
||||
// as the whole purpose of the lock file is for the specific dependencies of the project
|
||||
@ -66,54 +171,33 @@ func (a genericYarnLockAdapter) parseYarnLock(ctx context.Context, resolver file
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
var pkgs []pkg.Package
|
||||
var currentPackage, currentVersion, currentResolved, currentIntegrity string
|
||||
|
||||
scanner := bufio.NewScanner(reader)
|
||||
parsedPackages := strset.New()
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
|
||||
if packageName := findPackageName(line); packageName != "" {
|
||||
// When we find a new package, check if we have unsaved identifiers
|
||||
if currentPackage != "" && currentVersion != "" && !parsedPackages.Has(currentPackage+"@"+currentVersion) {
|
||||
pkgs = append(pkgs, newYarnLockPackage(ctx, a.cfg, resolver, reader.Location, currentPackage, currentVersion, currentResolved, currentIntegrity))
|
||||
parsedPackages.Add(currentPackage + "@" + currentVersion)
|
||||
}
|
||||
|
||||
currentPackage = packageName
|
||||
} else if version := findPackageVersion(line); version != "" {
|
||||
currentVersion = version
|
||||
} else if packageName, version, resolved := findResolvedPackageAndVersion(line); packageName != "" && version != "" && resolved != "" {
|
||||
currentResolved = resolved
|
||||
currentPackage = packageName
|
||||
currentVersion = version
|
||||
} else if integrity := findIntegrity(line); integrity != "" && !parsedPackages.Has(currentPackage+"@"+currentVersion) {
|
||||
pkgs = append(pkgs, newYarnLockPackage(ctx, a.cfg, resolver, reader.Location, currentPackage, currentVersion, currentResolved, integrity))
|
||||
parsedPackages.Add(currentPackage + "@" + currentVersion)
|
||||
|
||||
// Cleanup to indicate no unsaved identifiers
|
||||
currentPackage = ""
|
||||
currentVersion = ""
|
||||
currentResolved = ""
|
||||
currentIntegrity = ""
|
||||
}
|
||||
data, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to load yarn.lock file: %w", err)
|
||||
}
|
||||
// Reset the reader to the beginning of the file
|
||||
reader.ReadCloser = io.NopCloser(bytes.NewBuffer(data))
|
||||
|
||||
// check if we have valid unsaved data after end-of-file has reached
|
||||
if currentPackage != "" && currentVersion != "" && !parsedPackages.Has(currentPackage+"@"+currentVersion) {
|
||||
pkgs = append(pkgs, newYarnLockPackage(ctx, a.cfg, resolver, reader.Location, currentPackage, currentVersion, currentResolved, currentIntegrity))
|
||||
parsedPackages.Add(currentPackage + "@" + currentVersion)
|
||||
var yarnPkgs []yarnPackage
|
||||
// v1 Yarn lockfiles are not YAML, so we need to parse them as a special case. They typically
|
||||
// include a comment line that indicates the version. I.e. "# yarn lockfile v1"
|
||||
if strings.Contains(string(data), "# yarn lockfile v1") {
|
||||
yarnPkgs, err = parseYarnV1LockFile(reader)
|
||||
} else {
|
||||
yarnPkgs, err = parseYarnLockYaml(reader)
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to parse yarn.lock file: %w", err)
|
||||
}
|
||||
|
||||
pkg.Sort(pkgs)
|
||||
packages := make([]pkg.Package, len(yarnPkgs))
|
||||
for i, p := range yarnPkgs {
|
||||
packages[i] = newYarnLockPackage(ctx, a.cfg, resolver, reader.Location, p.Name, p.Version, p.Resolved, p.Integrity, p.Dependencies)
|
||||
}
|
||||
|
||||
return pkgs, nil, unknown.IfEmptyf(pkgs, "unable to determine packages")
|
||||
pkg.Sort(packages)
|
||||
|
||||
return packages, dependency.Resolve(yarnLockDependencySpecifier, packages), unknown.IfEmptyf(packages, "unable to determine packages")
|
||||
}
|
||||
|
||||
func findPackageName(line string) string {
|
||||
@ -124,14 +208,6 @@ func findPackageName(line string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func findPackageVersion(line string) string {
|
||||
if matches := versionExp.FindStringSubmatch(line); len(matches) >= 2 {
|
||||
return matches[1]
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func findResolvedPackageAndVersion(line string) (string, string, string) {
|
||||
var resolved string
|
||||
if matches := resolvedExp.FindStringSubmatch(line); len(matches) >= 2 {
|
||||
@ -143,11 +219,3 @@ func findResolvedPackageAndVersion(line string) (string, string, string) {
|
||||
|
||||
return "", "", ""
|
||||
}
|
||||
|
||||
func findIntegrity(line string) string {
|
||||
if matches := integrityExp.FindStringSubmatch(line); len(matches) >= 2 {
|
||||
return matches[1]
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
@ -17,7 +17,6 @@ import (
|
||||
)
|
||||
|
||||
func TestParseYarnBerry(t *testing.T) {
|
||||
var expectedRelationships []artifact.Relationship
|
||||
fixture := "test-fixtures/yarn-berry/yarn.lock"
|
||||
locations := file.NewLocationSet(file.NewLocation(fixture))
|
||||
|
||||
@ -29,7 +28,13 @@ func TestParseYarnBerry(t *testing.T) {
|
||||
PURL: "pkg:npm/%40babel/code-frame@7.10.4",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.YarnLockEntry{},
|
||||
Metadata: pkg.YarnLockEntry{
|
||||
Resolved: "@babel/code-frame@npm:7.10.4",
|
||||
Integrity: "feb4543c8a509fe30f0f6e8d7aa84f82b41148b963b826cd330e34986f649a85cb63b2f13dd4effdf434ac555d16f14940b8ea5f4433297c2f5ff85486ded019",
|
||||
Dependencies: map[string]string{
|
||||
"@babel/highlight": "^7.10.4",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "@types/minimatch",
|
||||
@ -38,7 +43,10 @@ func TestParseYarnBerry(t *testing.T) {
|
||||
PURL: "pkg:npm/%40types/minimatch@3.0.3",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.YarnLockEntry{},
|
||||
Metadata: pkg.YarnLockEntry{
|
||||
Resolved: "@types/minimatch@npm:3.0.3",
|
||||
Integrity: "b80259d55b96ef24cb3bb961b6dc18b943f2bb8838b4d8e7bead204f3173e551a416ffa49f9aaf1dc431277fffe36214118628eacf4aea20119df8835229901b",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "@types/qs",
|
||||
@ -47,7 +55,10 @@ func TestParseYarnBerry(t *testing.T) {
|
||||
PURL: "pkg:npm/%40types/qs@6.9.4",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.YarnLockEntry{},
|
||||
Metadata: pkg.YarnLockEntry{
|
||||
Resolved: "@types/qs@npm:6.9.4",
|
||||
Integrity: "77e509ed213f7694ae35f84a58b88da8744aad019e93556af6aeab4289287abbe71836c051d00649dbac0289ea199e408442590cfb1785009de11c3c8d0cbbea",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "ajv",
|
||||
@ -56,7 +67,16 @@ func TestParseYarnBerry(t *testing.T) {
|
||||
PURL: "pkg:npm/ajv@6.12.3",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.YarnLockEntry{},
|
||||
Metadata: pkg.YarnLockEntry{
|
||||
Resolved: "ajv@npm:6.12.3",
|
||||
Integrity: "ca559d34710e6969d33bc1316282e1ece4d4d99ff5fdca4bfe31947740f8f90e7824238cdc2954e499cf75b2432e3e6c56b32814ebe04fccf8abcc3fbf36b348",
|
||||
Dependencies: map[string]string{
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "asn1.js",
|
||||
@ -65,7 +85,15 @@ func TestParseYarnBerry(t *testing.T) {
|
||||
PURL: "pkg:npm/asn1.js@4.10.1",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.YarnLockEntry{},
|
||||
Metadata: pkg.YarnLockEntry{
|
||||
Resolved: "asn1.js@npm:4.10.1",
|
||||
Integrity: "9289a1a55401238755e3142511d7b8f6fc32f08c86ff68bd7100da8b6c186179dd6b14234fba2f7f6099afcd6758a816708485efe44bc5b2a6ec87d9ceeddbb5",
|
||||
Dependencies: map[string]string{
|
||||
"bn.js": "^4.0.0",
|
||||
"inherits": "^2.0.1",
|
||||
"minimalistic-assert": "^1.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "atob",
|
||||
@ -74,7 +102,10 @@ func TestParseYarnBerry(t *testing.T) {
|
||||
PURL: "pkg:npm/atob@2.1.2",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.YarnLockEntry{},
|
||||
Metadata: pkg.YarnLockEntry{
|
||||
Resolved: "atob@npm:2.1.2",
|
||||
Integrity: "dfeeeb70090c5ebea7be4b9f787f866686c645d9f39a0d184c817252d0cf08455ed25267d79c03254d3be1f03ac399992a792edcd5ffb9c91e097ab5ef42833a",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "aws-sdk",
|
||||
@ -83,7 +114,21 @@ func TestParseYarnBerry(t *testing.T) {
|
||||
Locations: locations,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.YarnLockEntry{},
|
||||
Metadata: pkg.YarnLockEntry{
|
||||
Resolved: "aws-sdk@npm:2.706.0",
|
||||
Integrity: "bf8ca2fc4f758bdebd04051ec15729affad3eb0e18eed4ae41db5b7d6ff2aed2cf3a12ae082c11b955df0125378c57b8406e1f91006e48f0c162fdbe4ee4e330",
|
||||
Dependencies: map[string]string{
|
||||
"buffer": "4.9.2",
|
||||
"events": "1.1.1",
|
||||
"ieee754": "1.1.13",
|
||||
"jmespath": "0.15.0",
|
||||
"querystring": "0.2.0",
|
||||
"sax": "1.2.1",
|
||||
"url": "0.10.3",
|
||||
"uuid": "3.3.2",
|
||||
"xml2js": "0.4.19",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "c0n-fab_u.laTION",
|
||||
@ -92,7 +137,16 @@ func TestParseYarnBerry(t *testing.T) {
|
||||
PURL: "pkg:npm/c0n-fab_u.laTION@7.7.7",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.YarnLockEntry{},
|
||||
Metadata: pkg.YarnLockEntry{
|
||||
Resolved: "newtest@workspace:.",
|
||||
Dependencies: map[string]string{
|
||||
"@babel/code-frame": "7.10.4",
|
||||
"@types/minimatch": "3.0.3",
|
||||
"@types/qs": "6.9.4",
|
||||
"ajv": "6.12.3",
|
||||
"asn1.js": "4.10.1",
|
||||
"atob": "2.1.2",
|
||||
}},
|
||||
},
|
||||
{
|
||||
Name: "jhipster-core",
|
||||
@ -101,7 +155,48 @@ func TestParseYarnBerry(t *testing.T) {
|
||||
PURL: "pkg:npm/jhipster-core@7.3.4",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.YarnLockEntry{},
|
||||
Metadata: pkg.YarnLockEntry{
|
||||
Resolved: "jhipster-core@npm:7.3.4",
|
||||
Integrity: "6a97741d574a42a138f98596c668370b41ec8870335bcd758b6b890e279ba30d4d2be447f8cecbf416286f2c53636b406a63a773c7b00709c95af0a9a3f9b397",
|
||||
Dependencies: map[string]string{
|
||||
"chevrotain": "7.0.1",
|
||||
"fs-extra": "8.1.0",
|
||||
"lodash": "4.17.15",
|
||||
"winston": "3.2.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
expectedRelationships := []artifact.Relationship{
|
||||
{
|
||||
From: expectedPkgs[0],
|
||||
To: expectedPkgs[7],
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: expectedPkgs[1],
|
||||
To: expectedPkgs[7],
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: expectedPkgs[2],
|
||||
To: expectedPkgs[7],
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: expectedPkgs[3],
|
||||
To: expectedPkgs[7],
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: expectedPkgs[4],
|
||||
To: expectedPkgs[7],
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: expectedPkgs[5],
|
||||
To: expectedPkgs[7],
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
}
|
||||
|
||||
@ -125,6 +220,9 @@ func TestParseYarnLock(t *testing.T) {
|
||||
Metadata: pkg.YarnLockEntry{
|
||||
Resolved: "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a",
|
||||
Integrity: "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==",
|
||||
Dependencies: map[string]string{
|
||||
"@babel/highlight": "^7.10.4",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -135,8 +233,9 @@ func TestParseYarnLock(t *testing.T) {
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.YarnLockEntry{
|
||||
Resolved: "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d",
|
||||
Integrity: "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==",
|
||||
Resolved: "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d",
|
||||
Integrity: "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==",
|
||||
Dependencies: map[string]string{},
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -147,8 +246,9 @@ func TestParseYarnLock(t *testing.T) {
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.YarnLockEntry{
|
||||
Resolved: "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.4.tgz#a59e851c1ba16c0513ea123830dd639a0a15cb6a",
|
||||
Integrity: "sha512-+wYo+L6ZF6BMoEjtf8zB2esQsqdV6WsjRK/GP9WOgLPrq87PbNWgIxS76dS5uvl/QXtHGakZmwTznIfcPXcKlQ==",
|
||||
Resolved: "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.4.tgz#a59e851c1ba16c0513ea123830dd639a0a15cb6a",
|
||||
Integrity: "sha512-+wYo+L6ZF6BMoEjtf8zB2esQsqdV6WsjRK/GP9WOgLPrq87PbNWgIxS76dS5uvl/QXtHGakZmwTznIfcPXcKlQ==",
|
||||
Dependencies: map[string]string{},
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -161,6 +261,12 @@ func TestParseYarnLock(t *testing.T) {
|
||||
Metadata: pkg.YarnLockEntry{
|
||||
Resolved: "https://registry.yarnpkg.com/ajv/-/ajv-6.12.3.tgz#18c5af38a111ddeb4f2697bd78d68abc1cabd706",
|
||||
Integrity: "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==",
|
||||
Dependencies: map[string]string{
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -173,6 +279,11 @@ func TestParseYarnLock(t *testing.T) {
|
||||
Metadata: pkg.YarnLockEntry{
|
||||
Resolved: "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0",
|
||||
Integrity: "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==",
|
||||
Dependencies: map[string]string{
|
||||
"bn.js": "^4.0.0",
|
||||
"inherits": "^2.0.1",
|
||||
"minimalistic-assert": "^1.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -184,8 +295,9 @@ func TestParseYarnLock(t *testing.T) {
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.YarnLockEntry{
|
||||
Resolved: "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9",
|
||||
Integrity: "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
|
||||
Resolved: "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9",
|
||||
Integrity: "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
|
||||
Dependencies: map[string]string{},
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -198,6 +310,17 @@ func TestParseYarnLock(t *testing.T) {
|
||||
Metadata: pkg.YarnLockEntry{
|
||||
Resolved: "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.706.0.tgz#09f65e9a91ecac5a635daf934082abae30eca953",
|
||||
Integrity: "sha512-7GT+yrB5Wb/zOReRdv/Pzkb2Qt+hz6B/8FGMVaoysX3NryHvQUdz7EQWi5yhg9CxOjKxdw5lFwYSs69YlSp1KA==",
|
||||
Dependencies: map[string]string{
|
||||
"buffer": "4.9.2",
|
||||
"events": "1.1.1",
|
||||
"ieee754": "1.1.13",
|
||||
"jmespath": "0.15.0",
|
||||
"querystring": "0.2.0",
|
||||
"sax": "1.2.1",
|
||||
"url": "0.10.3",
|
||||
"uuid": "3.3.2",
|
||||
"xml2js": "0.4.19",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -210,6 +333,12 @@ func TestParseYarnLock(t *testing.T) {
|
||||
Metadata: pkg.YarnLockEntry{
|
||||
Resolved: "https://registry.yarnpkg.com/jhipster-core/-/jhipster-core-7.3.4.tgz#c34b8c97c7f4e8b7518dae015517e2112c73cc80",
|
||||
Integrity: "sha512-AUhT69kNkqppaJZVfan/xnKG4Gs9Ggj7YLtTZFVe+xg+THrbMb5Ng7PL07PDlDw4KAEA33GMCwuAf65E8EpC4g==",
|
||||
Dependencies: map[string]string{
|
||||
"chevrotain": "7.0.1",
|
||||
"fs-extra": "8.1.0",
|
||||
"lodash": "4.17.15",
|
||||
"winston": "3.2.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -220,8 +349,254 @@ func TestParseYarnLock(t *testing.T) {
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.YarnLockEntry{
|
||||
Resolved: "https://registry.yarnpkg.com/something-i-made-up/-/c0n-fab_u.laTION-7.7.7.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0",
|
||||
Resolved: "https://registry.yarnpkg.com/something-i-made-up/-/c0n-fab_u.laTION-7.7.7.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0",
|
||||
Integrity: "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==",
|
||||
Dependencies: map[string]string{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
adapter := newGenericYarnLockAdapter(CatalogerConfig{})
|
||||
pkgtest.TestFileParser(t, fixture, adapter.parseYarnLock, expectedPkgs, expectedRelationships)
|
||||
}
|
||||
|
||||
func TestParseYarnLockWithRelationships(t *testing.T) {
|
||||
fixture := "test-fixtures/yarn-v1-deps/yarn.lock"
|
||||
locations := file.NewLocationSet(file.NewLocation(fixture))
|
||||
|
||||
expectedPkgs := []pkg.Package{
|
||||
{
|
||||
Name: "@babel/code-frame",
|
||||
Version: "7.10.4",
|
||||
Locations: locations,
|
||||
PURL: "pkg:npm/%40babel/code-frame@7.10.4",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.YarnLockEntry{
|
||||
Resolved: "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a",
|
||||
Integrity: "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==",
|
||||
Dependencies: map[string]string{
|
||||
"@babel/highlight": "^7.10.4",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "@types/minimatch",
|
||||
Version: "3.0.3",
|
||||
Locations: locations,
|
||||
PURL: "pkg:npm/%40types/minimatch@3.0.3",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.YarnLockEntry{
|
||||
Resolved: "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d",
|
||||
Integrity: "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==",
|
||||
Dependencies: map[string]string{},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "@types/qs",
|
||||
Version: "6.9.4",
|
||||
Locations: locations,
|
||||
PURL: "pkg:npm/%40types/qs@6.9.4",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.YarnLockEntry{
|
||||
Resolved: "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.4.tgz#a59e851c1ba16c0513ea123830dd639a0a15cb6a",
|
||||
Integrity: "sha512-+wYo+L6ZF6BMoEjtf8zB2esQsqdV6WsjRK/GP9WOgLPrq87PbNWgIxS76dS5uvl/QXtHGakZmwTznIfcPXcKlQ==",
|
||||
Dependencies: map[string]string{},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "ajv",
|
||||
Version: "6.12.3",
|
||||
Locations: locations,
|
||||
PURL: "pkg:npm/ajv@6.12.3",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.YarnLockEntry{
|
||||
Resolved: "https://registry.yarnpkg.com/ajv/-/ajv-6.12.3.tgz#18c5af38a111ddeb4f2697bd78d68abc1cabd706",
|
||||
Integrity: "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==",
|
||||
Dependencies: map[string]string{
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "asn1.js",
|
||||
Version: "4.10.1",
|
||||
Locations: locations,
|
||||
PURL: "pkg:npm/asn1.js@4.10.1",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.YarnLockEntry{
|
||||
Resolved: "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0",
|
||||
Integrity: "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==",
|
||||
Dependencies: map[string]string{
|
||||
"atob": "^2.1.2",
|
||||
"bn.js": "^4.0.0",
|
||||
"inherits": "^2.0.1",
|
||||
"minimalistic-assert": "^1.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "atob",
|
||||
Version: "2.1.2",
|
||||
Locations: locations,
|
||||
|
||||
PURL: "pkg:npm/atob@2.1.2",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.YarnLockEntry{
|
||||
Resolved: "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9",
|
||||
Integrity: "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
|
||||
Dependencies: map[string]string{},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "aws-sdk",
|
||||
Version: "2.706.0",
|
||||
Locations: locations,
|
||||
PURL: "pkg:npm/aws-sdk@2.706.0",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.YarnLockEntry{
|
||||
Resolved: "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.706.0.tgz#09f65e9a91ecac5a635daf934082abae30eca953",
|
||||
Integrity: "sha512-7GT+yrB5Wb/zOReRdv/Pzkb2Qt+hz6B/8FGMVaoysX3NryHvQUdz7EQWi5yhg9CxOjKxdw5lFwYSs69YlSp1KA==",
|
||||
Dependencies: map[string]string{
|
||||
"asn1.js": "4.10.1",
|
||||
"buffer": "4.9.2",
|
||||
"events": "1.1.1",
|
||||
"ieee754": "1.1.13",
|
||||
"jmespath": "0.15.0",
|
||||
"querystring": "0.2.0",
|
||||
"sax": "1.2.1",
|
||||
"url": "0.10.3",
|
||||
"uuid": "3.3.2",
|
||||
"xml2js": "0.4.19",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "jhipster-core",
|
||||
Version: "7.3.4",
|
||||
Locations: locations,
|
||||
PURL: "pkg:npm/jhipster-core@7.3.4",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.YarnLockEntry{
|
||||
Resolved: "https://registry.yarnpkg.com/jhipster-core/-/jhipster-core-7.3.4.tgz#c34b8c97c7f4e8b7518dae015517e2112c73cc80",
|
||||
Integrity: "sha512-AUhT69kNkqppaJZVfan/xnKG4Gs9Ggj7YLtTZFVe+xg+THrbMb5Ng7PL07PDlDw4KAEA33GMCwuAf65E8EpC4g==",
|
||||
Dependencies: map[string]string{
|
||||
"chevrotain": "7.0.1",
|
||||
"fs-extra": "8.1.0",
|
||||
"lodash": "4.17.15",
|
||||
"winston": "3.2.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "something-i-made-up",
|
||||
Version: "7.7.7",
|
||||
Locations: locations,
|
||||
PURL: "pkg:npm/something-i-made-up@7.7.7",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.YarnLockEntry{
|
||||
Resolved: "https://registry.yarnpkg.com/something-i-made-up/-/c0n-fab_u.laTION-7.7.7.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0",
|
||||
Integrity: "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==",
|
||||
Dependencies: map[string]string{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expectedRelationships := []artifact.Relationship{
|
||||
{
|
||||
From: expectedPkgs[4],
|
||||
To: expectedPkgs[6],
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: expectedPkgs[5],
|
||||
To: expectedPkgs[4],
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
}
|
||||
adapter := newGenericYarnLockAdapter(CatalogerConfig{})
|
||||
pkgtest.TestFileParser(t, fixture, adapter.parseYarnLock, expectedPkgs, expectedRelationships)
|
||||
}
|
||||
func TestParseYarnLockWithDuplicates(t *testing.T) {
|
||||
var expectedRelationships []artifact.Relationship
|
||||
fixture := "test-fixtures/yarn-dups/yarn.lock"
|
||||
locations := file.NewLocationSet(file.NewLocation(fixture))
|
||||
|
||||
expectedPkgs := []pkg.Package{
|
||||
{
|
||||
Name: "async",
|
||||
Version: "0.9.2",
|
||||
Locations: locations,
|
||||
PURL: "pkg:npm/async@0.9.2",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.YarnLockEntry{
|
||||
Resolved: "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d",
|
||||
Integrity: "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=",
|
||||
Dependencies: map[string]string{},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "async",
|
||||
Version: "3.2.3",
|
||||
Locations: locations,
|
||||
PURL: "pkg:npm/async@3.2.3",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.YarnLockEntry{
|
||||
Resolved: "https://registry.yarnpkg.com/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9",
|
||||
Integrity: "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==",
|
||||
Dependencies: map[string]string{},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "merge-objects",
|
||||
Version: "1.0.5",
|
||||
Locations: locations,
|
||||
PURL: "pkg:npm/merge-objects@1.0.5",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.YarnLockEntry{
|
||||
Resolved: "https://registry.yarnpkg.com/merge-objects/-/merge-objects-1.0.5.tgz#ad923ff3910091acc1438f53eb75b8f37d862a86",
|
||||
Integrity: "sha1-rZI/85EAkazBQ49T63W4832GKoY=",
|
||||
Dependencies: map[string]string{},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "@4lolo/resize-observer-polyfill",
|
||||
Version: "1.5.2",
|
||||
Locations: locations,
|
||||
PURL: "pkg:npm/%404lolo/resize-observer-polyfill@1.5.2",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.YarnLockEntry{
|
||||
Resolved: "https://registry.yarnpkg.com/@4lolo/resize-observer-polyfill/-/resize-observer-polyfill-1.5.2.tgz#58868fc7224506236b5550d0c68357f0a874b84b",
|
||||
Integrity: "sha512-HY4JYLITsWBOdeqCF/x3q7Aa2PVl/BmfkPv4H/Qzplc4Lrn9cKmWz6jHyAREH9tFuD0xELjJVgX3JaEmdcXu3g==",
|
||||
Dependencies: map[string]string{},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "should-type",
|
||||
Version: "1.3.0",
|
||||
Locations: locations,
|
||||
PURL: "pkg:npm/should-type@1.3.0",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Metadata: pkg.YarnLockEntry{
|
||||
Resolved: "https://github.com/shouldjs/type.git#31d26945cb3b4ad21d2308776e4442c461666390",
|
||||
Integrity: "",
|
||||
Dependencies: map[string]string{},
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -270,6 +645,9 @@ func TestSearchYarnForLicenses(t *testing.T) {
|
||||
Metadata: pkg.YarnLockEntry{
|
||||
Resolved: "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a",
|
||||
Integrity: "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==",
|
||||
Dependencies: map[string]string{
|
||||
"@babel/highlight": "^7.10.4",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -361,94 +739,6 @@ func TestParseYarnFindPackageNames(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseYarnFindPackageVersions(t *testing.T) {
|
||||
tests := []struct {
|
||||
line string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
line: ` version "7.10.4"`,
|
||||
expected: "7.10.4",
|
||||
},
|
||||
{
|
||||
line: ` version "7.11.5"`,
|
||||
expected: "7.11.5",
|
||||
},
|
||||
{
|
||||
line: `version "7.12.6"`,
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
line: ` version "0.0.0"`,
|
||||
expected: "0.0.0",
|
||||
},
|
||||
{
|
||||
line: ` version "2" `,
|
||||
expected: "2",
|
||||
},
|
||||
{
|
||||
line: ` version "9.3"`,
|
||||
expected: "9.3",
|
||||
},
|
||||
{
|
||||
line: "ajv@^6.10.2, ajv@^6.5.5",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
line: "atob@^2.1.2:",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
line: `"color-convert@npm:^1.9.0":`,
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
line: " version: 1.9.3",
|
||||
expected: "1.9.3",
|
||||
},
|
||||
{
|
||||
line: " version: 2",
|
||||
expected: "2",
|
||||
},
|
||||
{
|
||||
line: " version: 9.3",
|
||||
expected: "9.3",
|
||||
},
|
||||
{
|
||||
line: "ajv@^6.10.2, ajv@^6.5.5",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
line: "atob@^2.1.2:",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
line: " version: 1.0.0-alpha+001",
|
||||
expected: "1.0.0-alpha",
|
||||
},
|
||||
{
|
||||
line: " version: 1.0.0-beta_test+exp.sha.5114f85",
|
||||
expected: "1.0.0-beta_test",
|
||||
},
|
||||
{
|
||||
line: " version: 1.0.0+21AF26D3-117B344092BD",
|
||||
expected: "1.0.0",
|
||||
},
|
||||
{
|
||||
line: " version: 0.0.0-use.local",
|
||||
expected: "0.0.0-use.local",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.expected, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
actual := findPackageVersion(test.line)
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func generateMockYarnRegistryHandler(responseFixture string) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
61
syft/pkg/cataloger/javascript/test-fixtures/pnpm-v9-snapshots/pnpm-lock.yaml
generated
Normal file
61
syft/pkg/cataloger/javascript/test-fixtures/pnpm-v9-snapshots/pnpm-lock.yaml
generated
Normal file
@ -0,0 +1,61 @@
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
cross-spawn:
|
||||
specifier: ^7.0.5
|
||||
version: 7.0.6
|
||||
|
||||
packages:
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
isexe@2.0.0:
|
||||
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||
|
||||
path-key@3.1.1:
|
||||
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
shebang-command@2.0.0:
|
||||
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
shebang-regex@3.0.0:
|
||||
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
which@2.0.2:
|
||||
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
||||
engines: {node: '>= 8'}
|
||||
hasBin: true
|
||||
|
||||
snapshots:
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
shebang-command: 2.0.0
|
||||
which: 2.0.2
|
||||
|
||||
isexe@2.0.0: {}
|
||||
|
||||
path-key@3.1.1: {}
|
||||
|
||||
shebang-command@2.0.0:
|
||||
dependencies:
|
||||
shebang-regex: 3.0.0
|
||||
|
||||
shebang-regex@3.0.0: {}
|
||||
|
||||
which@2.0.2:
|
||||
dependencies:
|
||||
isexe: 2.0.0
|
||||
@ -0,0 +1,28 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
async@0.9.2:
|
||||
version "0.9.2"
|
||||
resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d"
|
||||
integrity sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=
|
||||
|
||||
async@^3.2.3:
|
||||
version "3.2.3"
|
||||
resolved "https://registry.yarnpkg.com/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9"
|
||||
integrity sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==
|
||||
|
||||
merge-objects@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/merge-objects/-/merge-objects-1.0.5.tgz#ad923ff3910091acc1438f53eb75b8f37d862a86"
|
||||
integrity sha1-rZI/85EAkazBQ49T63W4832GKoY=
|
||||
|
||||
resize-observer-polyfill@^1.5.1:
|
||||
name "resize-observer-polyfill"
|
||||
version "1.5.2"
|
||||
resolved "https://registry.yarnpkg.com/@4lolo/resize-observer-polyfill/-/resize-observer-polyfill-1.5.2.tgz#58868fc7224506236b5550d0c68357f0a874b84b"
|
||||
integrity sha512-HY4JYLITsWBOdeqCF/x3q7Aa2PVl/BmfkPv4H/Qzplc4Lrn9cKmWz6jHyAREH9tFuD0xELjJVgX3JaEmdcXu3g==
|
||||
|
||||
"should-type@https://github.com/shouldjs/type.git#1.3.0":
|
||||
version "1.3.0"
|
||||
resolved "https://github.com/shouldjs/type.git#31d26945cb3b4ad21d2308776e4442c461666390"
|
||||
@ -0,0 +1,86 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4":
|
||||
version "7.10.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a"
|
||||
integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==
|
||||
dependencies:
|
||||
"@babel/highlight" "^7.10.4"
|
||||
|
||||
"@types/minimatch@*", "@types/minimatch@^3.0.3":
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
|
||||
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
|
||||
|
||||
"@types/qs@^6.2.31":
|
||||
version "6.9.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.4.tgz#a59e851c1ba16c0513ea123830dd639a0a15cb6a"
|
||||
integrity sha512-+wYo+L6ZF6BMoEjtf8zB2esQsqdV6WsjRK/GP9WOgLPrq87PbNWgIxS76dS5uvl/QXtHGakZmwTznIfcPXcKlQ==
|
||||
|
||||
"@types/qs@^6.2.31":
|
||||
version "6.9.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.4.tgz#a59e851c1ba16c0513ea123830dd639a0a15cb6a"
|
||||
integrity sha512-+wYo+L6ZF6BMoEjtf8zB2esQsqdV6WsjRK/GP9WOgLPrq87PbNWgIxS76dS5uvl/QXtHGakZmwTznIfcPXcKlQ==
|
||||
|
||||
ajv@^6.10.2, ajv@^6.5.5:
|
||||
version "6.12.3"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.3.tgz#18c5af38a111ddeb4f2697bd78d68abc1cabd706"
|
||||
integrity sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==
|
||||
dependencies:
|
||||
fast-deep-equal "^3.1.1"
|
||||
fast-json-stable-stringify "^2.0.0"
|
||||
json-schema-traverse "^0.4.1"
|
||||
uri-js "^4.2.2"
|
||||
|
||||
atob@^2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
|
||||
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
|
||||
|
||||
atob@^2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
|
||||
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
|
||||
|
||||
aws-sdk@2.706.0:
|
||||
version "2.706.0"
|
||||
resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.706.0.tgz#09f65e9a91ecac5a635daf934082abae30eca953"
|
||||
integrity sha512-7GT+yrB5Wb/zOReRdv/Pzkb2Qt+hz6B/8FGMVaoysX3NryHvQUdz7EQWi5yhg9CxOjKxdw5lFwYSs69YlSp1KA==
|
||||
dependencies:
|
||||
asn1.js "4.10.1"
|
||||
buffer "4.9.2"
|
||||
events "1.1.1"
|
||||
ieee754 "1.1.13"
|
||||
jmespath "0.15.0"
|
||||
querystring "0.2.0"
|
||||
sax "1.2.1"
|
||||
url "0.10.3"
|
||||
uuid "3.3.2"
|
||||
xml2js "0.4.19"
|
||||
|
||||
jhipster-core@7.3.4:
|
||||
version "7.3.4"
|
||||
resolved "https://registry.yarnpkg.com/jhipster-core/-/jhipster-core-7.3.4.tgz#c34b8c97c7f4e8b7518dae015517e2112c73cc80"
|
||||
integrity sha512-AUhT69kNkqppaJZVfan/xnKG4Gs9Ggj7YLtTZFVe+xg+THrbMb5Ng7PL07PDlDw4KAEA33GMCwuAf65E8EpC4g==
|
||||
dependencies:
|
||||
chevrotain "7.0.1"
|
||||
fs-extra "8.1.0"
|
||||
lodash "4.17.15"
|
||||
winston "3.2.1"
|
||||
|
||||
asn1.js@^4.0.0:
|
||||
version "4.10.1"
|
||||
resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0"
|
||||
integrity sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==
|
||||
dependencies:
|
||||
atob "^2.1.2"
|
||||
bn.js "^4.0.0"
|
||||
inherits "^2.0.1"
|
||||
minimalistic-assert "^1.0.0"
|
||||
|
||||
c0n-fab_u.laTION@^7.0.0:
|
||||
version "7.7.7"
|
||||
resolved "https://registry.yarnpkg.com/something-i-made-up/-/c0n-fab_u.laTION-7.7.7.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0"
|
||||
integrity sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==
|
||||
@ -31,6 +31,9 @@ type NpmPackageLockEntry struct {
|
||||
|
||||
// Integrity is Subresource Integrity hash for verification using standard SRI format (sha512-... or sha1-...). npm changed from SHA-1 to SHA-512 in newer versions. For registry sources this is the integrity from registry, for remote tarballs it's SHA-512 of the file. npm verifies tarball matches this hash before unpacking, throwing EINTEGRITY error if mismatch detected.
|
||||
Integrity string `mapstructure:"integrity" json:"integrity"`
|
||||
|
||||
// Dependencies is a map of dependencies and their version markers, i.e. "lodash": "^1.0.0"
|
||||
Dependencies map[string]string `mapstructure:"dependencies" json:"dependencies"`
|
||||
}
|
||||
|
||||
// YarnLockEntry represents a single entry section of a yarn.lock file.
|
||||
@ -40,4 +43,21 @@ type YarnLockEntry struct {
|
||||
|
||||
// Integrity is Subresource Integrity hash for verification (SRI format)
|
||||
Integrity string `mapstructure:"integrity" json:"integrity"`
|
||||
|
||||
// Dependencies is a map of dependencies and their versions
|
||||
Dependencies map[string]string `mapstructure:"dependencies" json:"dependencies"`
|
||||
}
|
||||
|
||||
type PnpmLockResolution struct {
|
||||
// Integrity is Subresource Integrity hash for verification (SRI format)
|
||||
Integrity string `mapstructure:"integrity" json:"integrity"`
|
||||
}
|
||||
|
||||
// PnpmLockEntry represents a single entry in the "packages" section of a pnpm-lock.yaml file.
|
||||
type PnpmLockEntry struct {
|
||||
// Resolution is the resolution information for the package
|
||||
Resolution PnpmLockResolution `mapstructure:"resolution" json:"resolution"`
|
||||
|
||||
// Dependencies is a map of dependencies and their versions
|
||||
Dependencies map[string]string `mapstructure:"dependencies" json:"dependencies"`
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user