diff --git a/pkg/lockfile/fixtures/pnpm/one-package-v6-lockfile.yaml b/pkg/lockfile/fixtures/pnpm/one-package-v6-lockfile.yaml new file mode 100644 index 0000000000..44e228b5ff --- /dev/null +++ b/pkg/lockfile/fixtures/pnpm/one-package-v6-lockfile.yaml @@ -0,0 +1,14 @@ +lockfileVersion: '6.0' + +dependencies: + acorn: + specifier: 8.7.0 + version: 8.7.0 + +packages: + + /acorn@8.7.0: + resolution: {integrity: sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: false diff --git a/pkg/lockfile/fixtures/pnpm/scoped-packages-v6-lockfile.yaml b/pkg/lockfile/fixtures/pnpm/scoped-packages-v6-lockfile.yaml new file mode 100644 index 0000000000..2beb0374d0 --- /dev/null +++ b/pkg/lockfile/fixtures/pnpm/scoped-packages-v6-lockfile.yaml @@ -0,0 +1,13 @@ +lockfileVersion: '6.0' + +dependencies: + '@typescript-eslint/types': + specifier: ^5.0.0 + version: 5.57.1 + +packages: + + /@typescript-eslint/types@5.57.1: + resolution: {integrity: sha512-bSs4LOgyV3bJ08F5RDqO2KXqg3WAdwHCu06zOqcQ6vqbTJizyBhuh1o1ImC69X4bV2g1OJxbH71PJqiO7Y1RuA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: false diff --git a/pkg/lockfile/parse-pnpm-lock.go b/pkg/lockfile/parse-pnpm-lock.go index 29f0628183..37a6331252 100644 --- a/pkg/lockfile/parse-pnpm-lock.go +++ b/pkg/lockfile/parse-pnpm-lock.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "regexp" + "strconv" "strings" "gopkg.in/yaml.v3" @@ -27,6 +28,30 @@ type PnpmLockfile struct { Packages map[string]PnpmLockPackage `yaml:"packages,omitempty"` } +type pnpmLockfileV6 struct { + Version string `yaml:"lockfileVersion"` + Packages map[string]PnpmLockPackage `yaml:"packages,omitempty"` +} + +func (l *PnpmLockfile) UnmarshalYAML(unmarshal func(interface{}) error) error { + var lockfileV6 pnpmLockfileV6 + + if err := unmarshal(&lockfileV6); err != nil { + return err + } + + parsedVersion, err := strconv.ParseFloat(lockfileV6.Version, 64) + + if err != nil { + return err + } + + l.Version = parsedVersion + l.Packages = lockfileV6.Packages + + return nil +} + const PnpmEcosystem = NpmEcosystem func startsWithNumber(str string) bool { @@ -64,6 +89,10 @@ func extractPnpmPackageNameAndVersion(dependencyPath string) (string, string) { version = parts[0] } + if version == "" { + name, version = parseNameAtVersion(name) + } + if version == "" || !startsWithNumber(version) { return "", "" } @@ -77,6 +106,17 @@ func extractPnpmPackageNameAndVersion(dependencyPath string) (string, string) { return name, version } +func parseNameAtVersion(value string) (name string, version string) { + // look for pattern "name@version", where name is allowed to contain zero or more "@" + matches := regexp.MustCompile(`^(.+)@([\d.]+)$`).FindStringSubmatch(value) + + if len(matches) != 3 { + return name, "" + } + + return matches[1], matches[2] +} + func parsePnpmLock(lockfile PnpmLockfile) []PackageDetails { packages := make([]PackageDetails, 0, len(lockfile.Packages)) diff --git a/pkg/lockfile/parse-pnpm-lock_test.go b/pkg/lockfile/parse-pnpm-lock_test.go index a572328d90..72638f449d 100644 --- a/pkg/lockfile/parse-pnpm-lock_test.go +++ b/pkg/lockfile/parse-pnpm-lock_test.go @@ -54,6 +54,25 @@ func TestParsePnpmLock_OnePackage(t *testing.T) { }) } +func TestParsePnpmLock_OnePackageV6Lockfile(t *testing.T) { + t.Parallel() + + packages, err := lockfile.ParsePnpmLock("fixtures/pnpm/one-package-v6-lockfile.yaml") + + if err != nil { + t.Errorf("Got unexpected error: %v", err) + } + + expectPackages(t, packages, []lockfile.PackageDetails{ + { + Name: "acorn", + Version: "8.7.0", + Ecosystem: lockfile.PnpmEcosystem, + CompareAs: lockfile.PnpmEcosystem, + }, + }) +} + func TestParsePnpmLock_OnePackageDev(t *testing.T) { t.Parallel() @@ -92,6 +111,25 @@ func TestParsePnpmLock_ScopedPackages(t *testing.T) { }) } +func TestParsePnpmLock_ScopedPackagesV6Lockfile(t *testing.T) { + t.Parallel() + + packages, err := lockfile.ParsePnpmLock("fixtures/pnpm/scoped-packages-v6-lockfile.yaml") + + if err != nil { + t.Errorf("Got unexpected error: %v", err) + } + + expectPackages(t, packages, []lockfile.PackageDetails{ + { + Name: "@typescript-eslint/types", + Version: "5.57.1", + Ecosystem: lockfile.PnpmEcosystem, + CompareAs: lockfile.PnpmEcosystem, + }, + }) +} + func TestParsePnpmLock_PeerDependencies(t *testing.T) { t.Parallel()