From 3a3a86eb017bc3ccd290e38cfed2fa2ae8396830 Mon Sep 17 00:00:00 2001 From: Rez Moss Date: Wed, 7 Jan 2026 10:39:16 -0500 Subject: [PATCH] fixed #4430 exclude dev pnpm pkg (#4487) * fixed #4430 exclude dev pnpm pkg Signed-off-by: Rez Moss * use existing dev deps option Signed-off-by: Alex Goodman * fixed #4430 exclude dev pnpm pkg, add test Signed-off-by: Rez Moss * fixed #4430 exclude dev pnpm pkg, add test Signed-off-by: Rez Moss --------- Signed-off-by: Rez Moss Signed-off-by: Alex Goodman Co-authored-by: Alex Goodman --- .../cataloger/javascript/parse_pnpm_lock.go | 16 ++++--- .../javascript/parse_pnpm_lock_test.go | 42 ++++++++++++++++++- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/syft/pkg/cataloger/javascript/parse_pnpm_lock.go b/syft/pkg/cataloger/javascript/parse_pnpm_lock.go index 5b38cbaa8..6ab32cae5 100644 --- a/syft/pkg/cataloger/javascript/parse_pnpm_lock.go +++ b/syft/pkg/cataloger/javascript/parse_pnpm_lock.go @@ -25,6 +25,7 @@ type pnpmPackage struct { Version string Integrity string Dependencies map[string]string + Dev bool } // pnpmLockfileParser defines the interface for parsing different versions of pnpm lockfiles. @@ -35,6 +36,7 @@ type pnpmLockfileParser interface { type pnpmV6PackageEntry struct { Resolution map[string]string `yaml:"resolution"` Dependencies map[string]string `yaml:"dependencies"` + Dev bool `yaml:"dev"` } // pnpmV6LockYaml represents the structure of pnpm lockfiles for versions < 9.0. @@ -53,6 +55,7 @@ type pnpmV9SnapshotEntry struct { type pnpmV9PackageEntry struct { Resolution map[string]string `yaml:"resolution"` PeerDependencies map[string]string `yaml:"peerDependencies"` + Dev bool `yaml:"dev"` } // pnpmV9LockYaml represents the structure of pnpm lockfiles for versions >= 9.0. @@ -117,7 +120,7 @@ func (p *pnpmV6LockYaml) Parse(version float64, data []byte) ([]pnpmPackage, err dependencies[depName] = normalizedVersion } - packages[pkgKey] = pnpmPackage{Name: name, Version: ver, Integrity: integrity, Dependencies: dependencies} + packages[pkgKey] = pnpmPackage{Name: name, Version: ver, Integrity: integrity, Dependencies: dependencies, Dev: pkgInfo.Dev} } return toSortedSlice(packages), nil @@ -141,7 +144,7 @@ func (p *pnpmV9LockYaml) Parse(_ float64, data []byte) ([]pnpmPackage, error) { continue } pkgKey := name + "@" + ver - packages[pkgKey] = pnpmPackage{Name: name, Version: ver, Integrity: entry.Resolution["integrity"]} + packages[pkgKey] = pnpmPackage{Name: name, Version: ver, Integrity: entry.Resolution["integrity"], Dev: entry.Dev} } for key, snapshotInfo := range p.Snapshots { @@ -199,9 +202,12 @@ func (a genericPnpmLockAdapter) parsePnpmLock(ctx context.Context, resolver file return nil, nil, fmt.Errorf("failed to parse pnpm-lock.yaml file: %w", err) } - packages := make([]pkg.Package, len(pnpmPkgs)) - for i, p := range pnpmPkgs { - packages[i] = newPnpmPackage(ctx, a.cfg, resolver, reader.Location, p.Name, p.Version, p.Integrity, p.Dependencies) + packages := make([]pkg.Package, 0, len(pnpmPkgs)) + for _, p := range pnpmPkgs { + if p.Dev && !a.cfg.IncludeDevDependencies { + continue + } + packages = append(packages, newPnpmPackage(ctx, a.cfg, resolver, reader.Location, p.Name, p.Version, p.Integrity, p.Dependencies)) } return packages, dependency.Resolve(pnpmLockDependencySpecifier, packages), unknown.IfEmptyf(packages, "unable to determine packages") diff --git a/syft/pkg/cataloger/javascript/parse_pnpm_lock_test.go b/syft/pkg/cataloger/javascript/parse_pnpm_lock_test.go index bdbbb5fb1..e5fe882be 100644 --- a/syft/pkg/cataloger/javascript/parse_pnpm_lock_test.go +++ b/syft/pkg/cataloger/javascript/parse_pnpm_lock_test.go @@ -62,7 +62,47 @@ func TestParsePnpmLock(t *testing.T) { }, } - adapter := newGenericPnpmLockAdapter(CatalogerConfig{}) + adapter := newGenericPnpmLockAdapter(CatalogerConfig{IncludeDevDependencies: true}) + pkgtest.TestFileParser(t, fixture, adapter.parsePnpmLock, expectedPkgs, expectedRelationships) +} + +func TestParsePnpmLock_ExcludeDevDependencies(t *testing.T) { + var expectedRelationships []artifact.Relationship + fixture := "test-fixtures/pnpm/pnpm-lock.yaml" + + locationSet := file.NewLocationSet(file.NewLocation(fixture)) + + expectedPkgs := []pkg.Package{ + { + Name: "nanoid", + Version: "3.3.4", + PURL: "pkg:npm/nanoid@3.3.4", + Locations: locationSet, + Language: pkg.JavaScript, + Type: pkg.NpmPkg, + Metadata: pkg.PnpmLockEntry{Resolution: pkg.PnpmLockResolution{}}, + }, + { + Name: "picocolors", + Version: "1.0.0", + PURL: "pkg:npm/picocolors@1.0.0", + Locations: locationSet, + Language: pkg.JavaScript, + Type: pkg.NpmPkg, + Metadata: pkg.PnpmLockEntry{Resolution: pkg.PnpmLockResolution{}}, + }, + { + Name: "source-map-js", + Version: "1.0.2", + PURL: "pkg:npm/source-map-js@1.0.2", + Locations: locationSet, + Language: pkg.JavaScript, + Type: pkg.NpmPkg, + Metadata: pkg.PnpmLockEntry{Resolution: pkg.PnpmLockResolution{}}, + }, + } + + adapter := newGenericPnpmLockAdapter(CatalogerConfig{IncludeDevDependencies: false}) pkgtest.TestFileParser(t, fixture, adapter.parsePnpmLock, expectedPkgs, expectedRelationships) }