diff --git a/docs/docs/coverage/language/nodejs.md b/docs/docs/coverage/language/nodejs.md index addeb484867e..ded9ea144535 100644 --- a/docs/docs/coverage/language/nodejs.md +++ b/docs/docs/coverage/language/nodejs.md @@ -13,12 +13,12 @@ The following scanners are supported. The following table provides an outline of the features Trivy offers. -| Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | -|:---------------:|-------------------|:-----------------------:|:-----------------:|:------------------------------------:|:--------:| -| npm | package-lock.json | ✓ | [Excluded](#npm) | ✓ | ✓ | -| Yarn | yarn.lock | ✓ | [Excluded](#yarn) | ✓ | ✓ | -| pnpm | pnpm-lock.yaml | ✓ | Excluded | ✓ | - | -| Bun | yarn.lock | ✓ | [Excluded](#yarn) | ✓ | ✓ | +| Package manager | File | Transitive dependencies | Dev dependencies | [Dependency graph][dependency-graph] | Position | +|:---------------:|-------------------|:-----------------------:|:---------------------------------:|:------------------------------------:|:--------:| +| npm | package-lock.json | ✓ | [Excluded](#npm) | ✓ | ✓ | +| Yarn | yarn.lock | ✓ | [Excluded](#yarn) | ✓ | ✓ | +| pnpm | pnpm-lock.yaml | ✓ | [Excluded](#lock-file-v9-version) | ✓ | - | +| Bun | yarn.lock | ✓ | [Excluded](#yarn) | ✓ | ✓ | In addition, Trivy scans installed packages with `package.json`. @@ -55,8 +55,8 @@ By default, Trivy doesn't report development dependencies. Use the `--include-de ### pnpm Trivy parses `pnpm-lock.yaml`, then finds production dependencies and builds a [tree][dependency-graph] of dependencies with vulnerabilities. -!!! note - Trivy currently only supports Lockfile [v6][pnpm-lockfile-v6] or earlier. +#### lock file v9 version +Trivy supports `Dev` field for `pnpm-lock.yaml` v9 or later. Use the `--include-dev-deps` flag to include the developer's dependencies in the result. ### Bun Trivy supports scanning `yarn.lock` files generated by [Bun](https://bun.sh/docs/install/lockfile#how-do-i-inspect-bun-s-lockfile). You can use the command `bun install -y` to generate a Yarn-compatible `yarn.lock`. diff --git a/pkg/dependency/parser/nodejs/pnpm/parse.go b/pkg/dependency/parser/nodejs/pnpm/parse.go index 92fdc6131744..00c573a4c4db 100644 --- a/pkg/dependency/parser/nodejs/pnpm/parse.go +++ b/pkg/dependency/parser/nodejs/pnpm/parse.go @@ -2,10 +2,12 @@ package pnpm import ( "fmt" + "sort" "strconv" "strings" "github.com/samber/lo" + "golang.org/x/exp/maps" "golang.org/x/xerrors" "gopkg.in/yaml.v3" @@ -34,6 +36,28 @@ type LockFile struct { Dependencies map[string]any `yaml:"dependencies,omitempty"` DevDependencies map[string]any `yaml:"devDependencies,omitempty"` Packages map[string]PackageInfo `yaml:"packages,omitempty"` + + // V9 + Importers Importer `yaml:"importers,omitempty"` + Snapshots map[string]Snapshot `yaml:"snapshots,omitempty"` +} + +type Importer struct { + Root RootImporter `yaml:".,omitempty"` +} + +type RootImporter struct { + Dependencies map[string]ImporterDepVersion `yaml:"dependencies,omitempty"` + DevDependencies map[string]ImporterDepVersion `yaml:"devDependencies,omitempty"` +} + +type ImporterDepVersion struct { + Version string `yaml:"version,omitempty"` +} + +type Snapshot struct { + Dependencies map[string]string `yaml:"dependencies,omitempty"` + OptionalDependencies map[string]string `yaml:"optionalDependencies,omitempty"` } type Parser struct { @@ -57,8 +81,16 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc return nil, nil, nil } - pkgs, deps := p.parse(lockVer, lockFile) + var pkgs []ftypes.Package + var deps []ftypes.Dependency + if lockVer >= 9 { + pkgs, deps = p.parseV9(lockFile) + } else { + pkgs, deps = p.parse(lockVer, lockFile) + } + sort.Sort(ftypes.Packages(pkgs)) + sort.Sort(ftypes.Dependencies(deps)) return pkgs, deps, nil } @@ -78,9 +110,11 @@ func (p *Parser) parse(lockVer float64, lockFile LockFile) ([]ftypes.Package, [] // cf. https://github.com/pnpm/spec/blob/274ff02de23376ad59773a9f25ecfedd03a41f64/lockfile/6.0.md#packagesdependencypathname name := info.Name version := info.Version + var ref string if name == "" { - name, version = p.parsePackage(depPath, lockVer) + name, version, ref = p.parseDepPath(depPath, lockVer) + version = p.parseVersion(depPath, version, lockVer) } pkgID := packageID(name, version) @@ -90,13 +124,15 @@ func (p *Parser) parse(lockVer float64, lockFile LockFile) ([]ftypes.Package, [] } pkgs = append(pkgs, ftypes.Package{ - ID: pkgID, - Name: name, - Version: version, - Relationship: lo.Ternary(isDirectPkg(name, lockFile.Dependencies), ftypes.RelationshipDirect, ftypes.RelationshipIndirect), + ID: pkgID, + Name: name, + Version: version, + Relationship: lo.Ternary(isDirectPkg(name, lockFile.Dependencies), ftypes.RelationshipDirect, ftypes.RelationshipIndirect), + ExternalReferences: toExternalRefs(ref), }) if len(dependencies) > 0 { + sort.Strings(dependencies) deps = append(deps, ftypes.Dependency{ ID: pkgID, DependsOn: dependencies, @@ -107,6 +143,98 @@ func (p *Parser) parse(lockVer float64, lockFile LockFile) ([]ftypes.Package, [] return pkgs, deps } +func (p *Parser) parseV9(lockFile LockFile) ([]ftypes.Package, []ftypes.Dependency) { + lockVer := 9.0 + resolvedPkgs := make(map[string]ftypes.Package) + resolvedDeps := make(map[string]ftypes.Dependency) + + // Check all snapshots and save with resolved versions + resolvedSnapshots := make(map[string][]string) + for depPath, snapshot := range lockFile.Snapshots { + name, version, _ := p.parseDepPath(depPath, lockVer) + + var dependsOn []string + for depName, depVer := range lo.Assign(snapshot.OptionalDependencies, snapshot.Dependencies) { + depVer = p.trimPeerDeps(depVer, lockVer) // pnpm has already separated dep name. therefore, we only need to separate peer deps. + depVer = p.parseVersion(depPath, depVer, lockVer) + id := packageID(depName, depVer) + if _, ok := lockFile.Packages[id]; ok { + dependsOn = append(dependsOn, id) + } + } + if len(dependsOn) > 0 { + resolvedSnapshots[packageID(name, version)] = dependsOn + } + + } + + for depPath, pkgInfo := range lockFile.Packages { + name, ver, ref := p.parseDepPath(depPath, lockVer) + parsedVer := p.parseVersion(depPath, ver, lockVer) + + if pkgInfo.Version != "" { + parsedVer = pkgInfo.Version + } + + // By default, pkg is dev pkg. + // We will update `Dev` field later. + dev := true + relationship := ftypes.RelationshipIndirect + if dep, ok := lockFile.Importers.Root.DevDependencies[name]; ok && dep.Version == ver { + relationship = ftypes.RelationshipDirect + } + if dep, ok := lockFile.Importers.Root.Dependencies[name]; ok && dep.Version == ver { + relationship = ftypes.RelationshipDirect + dev = false // mark root direct deps to update `dev` field of their child deps. + } + + id := packageID(name, parsedVer) + resolvedPkgs[id] = ftypes.Package{ + ID: id, + Name: name, + Version: parsedVer, + Relationship: relationship, + Dev: dev, + ExternalReferences: toExternalRefs(ref), + } + + // Save child deps + if dependsOn, ok := resolvedSnapshots[depPath]; ok { + sort.Strings(dependsOn) + resolvedDeps[id] = ftypes.Dependency{ + ID: id, + DependsOn: dependsOn, // Deps from dependsOn has been resolved when parsing snapshots + } + } + } + + // Overwrite the `Dev` field for dev deps and their child dependencies. + for _, pkg := range resolvedPkgs { + if !pkg.Dev { + p.markRootPkgs(pkg.ID, resolvedPkgs, resolvedDeps) + } + } + + return maps.Values(resolvedPkgs), maps.Values(resolvedDeps) +} + +// markRootPkgs sets `Dev` to false for non dev dependency. +func (p *Parser) markRootPkgs(id string, pkgs map[string]ftypes.Package, deps map[string]ftypes.Dependency) { + pkg, ok := pkgs[id] + if !ok { + return + } + + pkg.Dev = false + pkgs[id] = pkg + + // Update child deps + for _, depID := range deps[id].DependsOn { + p.markRootPkgs(depID, pkgs, deps) + } + return +} + func (p *Parser) parseLockfileVersion(lockFile LockFile) float64 { switch v := lockFile.LockfileVersion.(type) { // v5 @@ -127,55 +255,109 @@ func (p *Parser) parseLockfileVersion(lockFile LockFile) float64 { } } -// cf. https://github.com/pnpm/pnpm/blob/ce61f8d3c29eee46cee38d56ced45aea8a439a53/packages/dependency-path/src/index.ts#L112-L163 -func (p *Parser) parsePackage(depPath string, lockFileVersion float64) (string, string) { - // The version separator is different between v5 and v6+. - versionSep := "@" - if lockFileVersion < 6 { - versionSep = "/" +func (p *Parser) parseDepPath(depPath string, lockVer float64) (string, string, string) { + dPath, nonDefaultRegistry := p.trimRegistry(depPath, lockVer) + + var scope string + scope, dPath = p.separateScope(dPath) + + var name string + name, dPath = p.separateName(dPath, lockVer) + + // add scope to pkg name + if scope != "" { + name = fmt.Sprintf("%s/%s", scope, name) } - return p.parseDepPath(depPath, versionSep) + + ver := p.trimPeerDeps(dPath, lockVer) + + return name, ver, lo.Ternary(nonDefaultRegistry, depPath, "") } -func (p *Parser) parseDepPath(depPath, versionSep string) (string, string) { - // Skip registry - // e.g. - // - "registry.npmjs.org/lodash/4.17.10" => "lodash/4.17.10" - // - "registry.npmjs.org/@babel/generator/7.21.9" => "@babel/generator/7.21.9" - // - "/lodash/4.17.10" => "lodash/4.17.10" - _, depPath, _ = strings.Cut(depPath, "/") +// trimRegistry trims registry (or `/` prefix) for depPath. +// It returns true if non-default registry has been trimmed. +// e.g. +// - "registry.npmjs.org/lodash/4.17.10" => "lodash/4.17.10", false +// - "registry.npmjs.org/@babel/generator/7.21.9" => "@babel/generator/7.21.9", false +// - "private.npm.org/@babel/generator/7.21.9" => "@babel/generator/7.21.9", true +// - "/lodash/4.17.10" => "lodash/4.17.10", false +// - "/asap@2.0.6" => "asap@2.0.6", false +func (p *Parser) trimRegistry(depPath string, lockVer float64) (string, bool) { + var nonDefaultRegistry bool + // lock file v9 doesn't use registry prefix + if lockVer < 9 { + var registry string + registry, depPath, _ = strings.Cut(depPath, "/") + if registry != "" && registry != "registry.npmjs.org" { + nonDefaultRegistry = true + } + } + return depPath, nonDefaultRegistry +} - // Parse scope - // e.g. - // - v5: "@babel/generator/7.21.9" => {"babel", "generator/7.21.9"} - // - v6+: "@babel/helper-annotate-as-pure@7.18.6" => "{"babel", "helper-annotate-as-pure@7.18.6"} +// separateScope separates the scope (if set) from the rest of the depPath. +// e.g. +// - v5: "@babel/generator/7.21.9" => {"babel", "generator/7.21.9"} +// - v6+: "@babel/helper-annotate-as-pure@7.18.6" => "{"babel", "helper-annotate-as-pure@7.18.6"} +func (p *Parser) separateScope(depPath string) (string, string) { var scope string if strings.HasPrefix(depPath, "@") { scope, depPath, _ = strings.Cut(depPath, "/") } + return scope, depPath +} - // Parse package name - // e.g. - // - v5: "generator/7.21.9" => {"generator", "7.21.9"} - // - v6+: "helper-annotate-as-pure@7.18.6" => {"helper-annotate-as-pure", "7.18.6"} - var name, version string - name, version, _ = strings.Cut(depPath, versionSep) - if scope != "" { - name = fmt.Sprintf("%s/%s", scope, name) +// separateName separates pkg name and version. +// e.g. +// - v5: "generator/7.21.9" => {"generator", "7.21.9"} +// - v6+: "7.21.5(@babel/core@7.20.7)" => "7.21.5" +// +// for v9+ version can be filePath or link: +// - "package1@file:package1:" +// - "is-negative@https://codeload.github.com/zkochan/is-negative/tar.gz/2fa0531ab04e300a24ef4fd7fb3a280eccb7ccc5" +// +// Also version can contain peer deps: +// - "debug@4.3.4(supports-color@8.1.1)" +func (p *Parser) separateName(depPath string, lockVer float64) (string, string) { + sep := "@" + if lockVer < 6 { + sep = "/" + } + name, version, _ := strings.Cut(depPath, sep) + return name, version +} + +// Trim peer deps +// e.g. +// - v5: "7.21.5_@babel+core@7.21.8" => "7.21.5" +// - v6+: "7.21.5(@babel/core@7.20.7)" => "7.21.5" +func (p *Parser) trimPeerDeps(depPath string, lockVer float64) string { + sep := "(" + if lockVer < 6 { + sep = "_" } - // Trim peer deps - // e.g. - // - v5: "7.21.5_@babel+core@7.21.8" => "7.21.5" - // - v6+: "7.21.5(@babel/core@7.20.7)" => "7.21.5" - if idx := strings.IndexAny(version, "_("); idx != -1 { - version = version[:idx] + version, _, _ := strings.Cut(depPath, sep) + return version +} + +// parseVersion parses version. +// v9 can use filePath or link as version - we need to clear these versions. +// e.g. +// - "package1@file:package1:" +// - "is-negative@https://codeload.github.com/zkochan/is-negative/tar.gz/2fa0531ab04e300a24ef4fd7fb3a280eccb7ccc5" +// +// Other versions should be semver valid. +func (p *Parser) parseVersion(depPath, ver string, lockVer float64) string { + if lockVer < 9 && (strings.HasPrefix(ver, "file:") || strings.HasPrefix(ver, "http")) { + return "" } - if _, err := semver.Parse(version); err != nil { + if _, err := semver.Parse(ver); err != nil { p.logger.Debug("Skip non-semver package", log.String("pkg_path", depPath), - log.String("version", version), log.Err(err)) - return "", "" + log.String("version", ver), log.Err(err)) + return "" } - return name, version + + return ver } func isDirectPkg(name string, directDeps map[string]interface{}) bool { @@ -186,3 +368,15 @@ func isDirectPkg(name string, directDeps map[string]interface{}) bool { func packageID(name, version string) string { return dependency.ID(ftypes.Pnpm, name, version) } + +func toExternalRefs(ref string) []ftypes.ExternalRef { + if ref == "" { + return nil + } + return []ftypes.ExternalRef{ + { + Type: ftypes.RefVCS, + URL: ref, + }, + } +} diff --git a/pkg/dependency/parser/nodejs/pnpm/parse_test.go b/pkg/dependency/parser/nodejs/pnpm/parse_test.go index b5d8816516fe..7a9caeb1fee9 100644 --- a/pkg/dependency/parser/nodejs/pnpm/parse_test.go +++ b/pkg/dependency/parser/nodejs/pnpm/parse_test.go @@ -5,10 +5,8 @@ import ( "sort" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/stretchr/testify/require" ) func TestParse(t *testing.T) { @@ -54,6 +52,12 @@ func TestParse(t *testing.T) { want: pnpmV6WithDev, wantDeps: pnpmV6WithDevDeps, }, + { + name: "v9", + file: "testdata/pnpm-lock_v9.yaml", + want: pnpmV9, + wantDeps: pnpmV9Deps, + }, } for _, tt := range tests { @@ -66,7 +70,7 @@ func TestParse(t *testing.T) { sort.Sort(ftypes.Packages(got)) sort.Sort(ftypes.Packages(tt.want)) - assert.Equal(t, tt.want, got) + require.Equal(t, tt.want, got) if tt.wantDeps != nil { sort.Sort(ftypes.Dependencies(deps)) @@ -77,19 +81,20 @@ func TestParse(t *testing.T) { for _, dep := range tt.wantDeps { sort.Strings(dep.DependsOn) } - assert.Equal(t, tt.wantDeps, deps) + require.Equal(t, tt.wantDeps, deps) } }) } } -func Test_parsePackage(t *testing.T) { +func Test_parseDepPath(t *testing.T) { tests := []struct { name string lockFileVer float64 pkg string wantName string wantVersion string + wantRef string }{ { name: "v5 - relative path", @@ -105,6 +110,14 @@ func Test_parsePackage(t *testing.T) { wantName: "lodash", wantVersion: "4.17.10", }, + { + name: "v5 - non-default registry", + lockFileVer: 5.0, + pkg: "private.npmjs.org/lodash/4.17.10", + wantName: "lodash", + wantVersion: "4.17.10", + wantRef: "private.npmjs.org/lodash/4.17.10", + }, { name: "v5 - relative path with slash", lockFileVer: 5.0, @@ -140,13 +153,6 @@ func Test_parsePackage(t *testing.T) { wantName: "@babel/helper-compilation-targets", wantVersion: "7.21.5", }, - { - name: "v5 - relative path with wrong version", - lockFileVer: 5.0, - pkg: "/lodash/4-wrong", - wantName: "", - wantVersion: "", - }, { name: "v6 - relative path", lockFileVer: 6.0, @@ -161,6 +167,14 @@ func Test_parsePackage(t *testing.T) { wantName: "lodash", wantVersion: "4.17.10", }, + { + name: "v6 - non-default registry", + lockFileVer: 6.0, + pkg: "private.npmjs.org/lodash@4.17.10", + wantName: "lodash", + wantVersion: "4.17.10", + wantRef: "private.npmjs.org/lodash@4.17.10", + }, { name: "v6 - relative path with slash", lockFileVer: 6.0, @@ -190,20 +204,77 @@ func Test_parsePackage(t *testing.T) { wantVersion: "7.21.5", }, { - name: "v6 - relative path with wrong version", - lockFileVer: 6.0, - pkg: "/lodash@4-wrong", - wantName: "", - wantVersion: "", + name: "v9 - scope and peer deps", + lockFileVer: 9.0, + pkg: "@babel/helper-compilation-targets@7.21.5(@babel/core@7.20.7)", + wantName: "@babel/helper-compilation-targets", + wantVersion: "7.21.5", + }, + { + name: "v9 - filePath as version", + lockFileVer: 9.0, + pkg: "lodash@file:foo/bar/lodash.tgz", + wantName: "lodash", + wantVersion: "file:foo/bar/lodash.tgz", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := NewParser() + gotName, gotVersion, gotRef := p.parseDepPath(tt.pkg, tt.lockFileVer) + require.Equal(t, tt.wantName, gotName) + require.Equal(t, tt.wantVersion, gotVersion) + require.Equal(t, tt.wantRef, gotRef) + }) + + } +} + +func Test_parseVersion(t *testing.T) { + tests := []struct { + name string + ver string + lockVer float64 + wantVer string + }{ + { + name: "happy path", + ver: "0.0.1", + lockVer: 5.0, + wantVer: "0.0.1", + }, + { + name: "v6 version is file", + ver: "file:foo/bar/lodash.tgz", + lockVer: 6.0, + wantVer: "", + }, + { + name: "v9 version is file", + ver: "file:foo/bar/lodash.tgz", + lockVer: 9.0, + wantVer: "", + }, + { + name: "v6 version is url", + ver: "https://codeload.github.com/zkochan/is-negative/tar.gz/2fa0531ab04e300a24ef4fd7fb3a280eccb7ccc5", + lockVer: 6.0, + wantVer: "", + }, + { + name: "v9 version is url", + ver: "https://codeload.github.com/zkochan/is-negative/tar.gz/2fa0531ab04e300a24ef4fd7fb3a280eccb7ccc5", + lockVer: 9.0, + wantVer: "", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { p := NewParser() - gotName, gotVersion := p.parsePackage(tt.pkg, tt.lockFileVer) - assert.Equal(t, tt.wantName, gotName) - assert.Equal(t, tt.wantVersion, gotVersion) + gotVer := p.parseVersion("depPath", tt.ver, tt.lockVer) + require.Equal(t, tt.wantVer, gotVer) }) } diff --git a/pkg/dependency/parser/nodejs/pnpm/parse_testcase.go b/pkg/dependency/parser/nodejs/pnpm/parse_testcase.go index 45eb7a7202e7..52b69ce79ee3 100644 --- a/pkg/dependency/parser/nodejs/pnpm/parse_testcase.go +++ b/pkg/dependency/parser/nodejs/pnpm/parse_testcase.go @@ -1,6 +1,8 @@ package pnpm -import ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" +import ( + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" +) var ( // docker run --name node --rm -it node:16-alpine sh @@ -730,4 +732,172 @@ var ( DependsOn: []string{"@babel/runtime@7.22.3"}, }, } + + pnpmV9 = []ftypes.Package{ + { + ID: "@babel/helper-string-parser@7.24.1", + Name: "@babel/helper-string-parser", + Version: "7.24.1", + Relationship: ftypes.RelationshipDirect, + }, + { + ID: "asap@2.0.6", + Name: "asap", + Version: "2.0.6", + Relationship: ftypes.RelationshipIndirect, + }, + { + ID: "asynckit@0.4.0", + Name: "asynckit", + Version: "0.4.0", + Relationship: ftypes.RelationshipIndirect, + }, + { + ID: "debug@4.3.4", + Name: "debug", + Version: "4.3.4", + Relationship: ftypes.RelationshipIndirect, + }, + { + ID: "debug@4.3.5", + Name: "debug", + Version: "4.3.5", + Relationship: ftypes.RelationshipDirect, + }, + { + ID: "ee-first@1.1.1", + Name: "ee-first", + Version: "1.1.1", + Relationship: ftypes.RelationshipIndirect, + }, + { + ID: "encodeurl@1.0.2", + Name: "encodeurl", + Version: "1.0.2", + Relationship: ftypes.RelationshipIndirect, + }, + { + ID: "escape-html@1.0.3", + Name: "escape-html", + Version: "1.0.3", + Relationship: ftypes.RelationshipIndirect, + }, + { + ID: "finalhandler@1.1.1", + Name: "finalhandler", + Version: "1.1.1", + Relationship: ftypes.RelationshipDirect, + }, + { + ID: "is-negative@2.0.1", + Name: "is-negative", + Version: "2.0.1", + Relationship: ftypes.RelationshipDirect, + }, + { + ID: "jquery@3.6.0", + Name: "jquery", + Version: "3.6.0", + Dev: true, + Relationship: ftypes.RelationshipDirect, + }, + { + ID: "lodash@4.17.21", + Name: "lodash", + Version: "4.17.21", + Dev: true, + Relationship: ftypes.RelationshipDirect, + }, + { + ID: "ms@2.0.0", + Name: "ms", + Version: "2.0.0", + Relationship: ftypes.RelationshipDirect, + }, + { + ID: "ms@2.1.2", + Name: "ms", + Version: "2.1.2", + Relationship: ftypes.RelationshipIndirect, + }, + { + ID: "on-finished@2.3.0", + Name: "on-finished", + Version: "2.3.0", + Relationship: ftypes.RelationshipDirect, + }, + { + ID: "package1", + Name: "package1", + Relationship: ftypes.RelationshipDirect, + }, + { + ID: "parseurl@1.3.3", + Name: "parseurl", + Version: "1.3.3", + Relationship: ftypes.RelationshipIndirect, + }, + { + ID: "promise@8.1.0", + Name: "promise", + Version: "8.1.0", + Relationship: ftypes.RelationshipDirect, + }, + { + ID: "statuses@1.4.0", + Name: "statuses", + Version: "1.4.0", + Relationship: ftypes.RelationshipIndirect, + }, + { + ID: "unpipe@1.0.0", + Name: "unpipe", + Version: "1.0.0", + Relationship: ftypes.RelationshipIndirect, + }, + } + pnpmV9Deps = []ftypes.Dependency{ + { + ID: "debug@4.3.4", + DependsOn: []string{ + "ms@2.0.0", + }, + }, + { + ID: "debug@4.3.5", + DependsOn: []string{ + "ms@2.1.2", + }, + }, + { + ID: "finalhandler@1.1.1", + DependsOn: []string{ + "debug@4.3.4", + "encodeurl@1.0.2", + "escape-html@1.0.3", + "on-finished@2.3.0", + "parseurl@1.3.3", + "statuses@1.4.0", + "unpipe@1.0.0", + }, + }, + { + ID: "on-finished@2.3.0", + DependsOn: []string{ + "ee-first@1.1.1", + }, + }, + { + ID: "package1", + DependsOn: []string{ + "asynckit@0.4.0", + }, + }, + { + ID: "promise@8.1.0", + DependsOn: []string{ + "asap@2.0.6", + }, + }, + } ) diff --git a/pkg/dependency/parser/nodejs/pnpm/testdata/pnpm-lock_v9.yaml b/pkg/dependency/parser/nodejs/pnpm/testdata/pnpm-lock_v9.yaml new file mode 100644 index 000000000000..12a61f02b79a --- /dev/null +++ b/pkg/dependency/parser/nodejs/pnpm/testdata/pnpm-lock_v9.yaml @@ -0,0 +1,191 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@babel/helper-string-parser': + specifier: 7.24.1 + version: 7.24.1 + debug: + specifier: https://github.com/debug-js/debug/tarball/4.3.5 + version: https://github.com/debug-js/debug/tarball/4.3.5 + finalhandler: + specifier: 1.1.1 + version: 1.1.1 + is-negative: + specifier: https://codeload.github.com/zkochan/is-negative/tar.gz/2fa0531ab04e300a24ef4fd7fb3a280eccb7ccc5 + version: https://codeload.github.com/zkochan/is-negative/tar.gz/2fa0531ab04e300a24ef4fd7fb3a280eccb7ccc5 + on-finished: + specifier: 2.3.0 + version: 2.3.0 + package1: + specifier: file:package1 + version: file:package1 + promise: + specifier: 8.1.0 + version: 8.1.0 + devDependencies: + jquery: + specifier: 3.6.0 + version: 3.6.0 + lodash: + specifier: file:foo/bar/lodash.tgz + version: file:foo/bar/lodash.tgz + ms: + specifier: 2.0.0 + version: 2.0.0 + +packages: + + '@babel/helper-string-parser@7.24.1': + resolution: {integrity: sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==} + engines: {node: '>=6.9.0'} + + asap@2.0.6: + resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + debug@4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@https://github.com/debug-js/debug/tarball/4.3.5: + resolution: {tarball: https://github.com/debug-js/debug/tarball/4.3.5} + version: 4.3.5 + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + encodeurl@1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + finalhandler@1.1.1: + resolution: {integrity: sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==} + engines: {node: '>= 0.8'} + + is-negative@https://codeload.github.com/zkochan/is-negative/tar.gz/2fa0531ab04e300a24ef4fd7fb3a280eccb7ccc5: + resolution: {tarball: https://codeload.github.com/zkochan/is-negative/tar.gz/2fa0531ab04e300a24ef4fd7fb3a280eccb7ccc5} + version: 2.0.1 + engines: {node: '>=0.10.0'} + + jquery@3.6.0: + resolution: {integrity: sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==} + + lodash@file:foo/bar/lodash.tgz: + resolution: {integrity: sha512-D2TlgcJ2ZQKKj4HVqOFkR+QETqRCXu+TCyxy7qH5QhxuJXCulHzBqmz4WrHA5Fs5ryY5RJYUqw2JBnbzveCtlA==, tarball: file:foo/bar/lodash.tgz} + version: 4.17.21 + + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + + ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + + on-finished@2.3.0: + resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} + engines: {node: '>= 0.8'} + + package1@file:package1: + resolution: {directory: package1, type: directory} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + promise@8.1.0: + resolution: {integrity: sha512-W04AqnILOL/sPRXziNicCjSNRruLAuIHEOVBazepu0545DDNGYHz7ar9ZgZ1fMU8/MA4mVxp5rkBWRi6OXIy3Q==} + + statuses@1.4.0: + resolution: {integrity: sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==} + engines: {node: '>= 0.6'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + +snapshots: + + '@babel/helper-string-parser@7.24.1': {} + + asap@2.0.6: + optional: true + + asynckit@0.4.0: {} + + debug@4.3.4(supports-color@8.1.1): + dependencies: + ms: 2.0.0 + optionalDependencies: + supports-color: 8.1.1 + + debug@https://github.com/debug-js/debug/tarball/4.3.5: + dependencies: + ms: 2.1.2 + + ee-first@1.1.1: {} + + encodeurl@1.0.2: {} + + escape-html@1.0.3: {} + + finalhandler@1.1.1: + dependencies: + debug: 4.3.4(supports-color@8.1.1) + encodeurl: 1.0.2 + escape-html: 1.0.3 + on-finished: 2.3.0 + parseurl: 1.3.3 + statuses: 1.4.0 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + is-negative@https://codeload.github.com/zkochan/is-negative/tar.gz/2fa0531ab04e300a24ef4fd7fb3a280eccb7ccc5: {} + + jquery@3.6.0: {} + + lodash@file:foo/bar/lodash.tgz: {} + + ms@2.0.0: {} + + ms@2.1.2: {} + + on-finished@2.3.0: + dependencies: + ee-first: 1.1.1 + + package1@file:package1: + dependencies: + asynckit: 0.4.0 + + parseurl@1.3.3: {} + + promise@8.1.0: + optionalDependencies: + asap: 2.0.6 + + statuses@1.4.0: {} + + unpipe@1.0.0: {}