github.com/kastenhq/syft@v0.0.0-20230821225854-0710af25cdbe/syft/pkg/cataloger/javascript/parse_pnpm_lock.go (about) 1 package javascript 2 3 import ( 4 "fmt" 5 "io" 6 "regexp" 7 "strconv" 8 "strings" 9 10 "gopkg.in/yaml.v3" 11 12 "github.com/kastenhq/syft/internal/log" 13 "github.com/kastenhq/syft/syft/artifact" 14 "github.com/kastenhq/syft/syft/file" 15 "github.com/kastenhq/syft/syft/pkg" 16 "github.com/kastenhq/syft/syft/pkg/cataloger/generic" 17 ) 18 19 // integrity check 20 var _ generic.Parser = parsePnpmLock 21 22 type pnpmLockYaml struct { 23 Version string `json:"lockfileVersion" yaml:"lockfileVersion"` 24 Dependencies map[string]interface{} `json:"dependencies" yaml:"dependencies"` 25 Packages map[string]interface{} `json:"packages" yaml:"packages"` 26 } 27 28 func parsePnpmLock(resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { 29 bytes, err := io.ReadAll(reader) 30 if err != nil { 31 return nil, nil, fmt.Errorf("failed to load pnpm-lock.yaml file: %w", err) 32 } 33 34 var pkgs []pkg.Package 35 var lockFile pnpmLockYaml 36 37 if err := yaml.Unmarshal(bytes, &lockFile); err != nil { 38 return nil, nil, fmt.Errorf("failed to parse pnpm-lock.yaml file: %w", err) 39 } 40 41 lockVersion, _ := strconv.ParseFloat(lockFile.Version, 64) 42 43 for name, info := range lockFile.Dependencies { 44 version := "" 45 46 switch info := info.(type) { 47 case string: 48 version = info 49 case map[string]interface{}: 50 v, ok := info["version"] 51 if !ok { 52 break 53 } 54 ver, ok := v.(string) 55 if ok { 56 version = parseVersion(ver) 57 } 58 default: 59 log.Tracef("unsupported pnpm dependency type: %+v", info) 60 continue 61 } 62 63 if hasPkg(pkgs, name, version) { 64 continue 65 } 66 67 pkgs = append(pkgs, newPnpmPackage(resolver, reader.Location, name, version)) 68 } 69 70 packageNameRegex := regexp.MustCompile(`^/?([^(]*)(?:\(.*\))*$`) 71 splitChar := "/" 72 if lockVersion >= 6.0 { 73 splitChar = "@" 74 } 75 76 // parse packages from packages section of pnpm-lock.yaml 77 for nameVersion := range lockFile.Packages { 78 nameVersion = packageNameRegex.ReplaceAllString(nameVersion, "$1") 79 nameVersionSplit := strings.Split(strings.TrimPrefix(nameVersion, "/"), splitChar) 80 81 // last element in split array is version 82 version := nameVersionSplit[len(nameVersionSplit)-1] 83 84 // construct name from all array items other than last item (version) 85 name := strings.Join(nameVersionSplit[:len(nameVersionSplit)-1], splitChar) 86 87 if hasPkg(pkgs, name, version) { 88 continue 89 } 90 91 pkgs = append(pkgs, newPnpmPackage(resolver, reader.Location, name, version)) 92 } 93 94 pkg.Sort(pkgs) 95 96 return pkgs, nil, nil 97 } 98 99 func hasPkg(pkgs []pkg.Package, name, version string) bool { 100 for _, p := range pkgs { 101 if p.Name == name && p.Version == version { 102 return true 103 } 104 } 105 return false 106 } 107 108 func parseVersion(version string) string { 109 return strings.SplitN(version, "(", 2)[0] 110 }