github.com/joey-fossa/fossa-cli@v0.7.34-0.20190708193710-569f1e8679f0/buildtools/yarn/parse.go (about) 1 package yarn 2 3 import ( 4 "errors" 5 "path/filepath" 6 "regexp" 7 8 yaml "gopkg.in/yaml.v2" 9 10 "github.com/fossas/fossa-cli/buildtools/npm" 11 "github.com/fossas/fossa-cli/files" 12 "github.com/fossas/fossa-cli/graph" 13 "github.com/fossas/fossa-cli/pkg" 14 ) 15 16 // FromProject builds a dependency graph based on the provided lockfile and manifest 17 func FromProject(manifestPath string, lockFilePath string) (graph.Deps, error) { 18 rootPackageJsonExists, err := files.Exists(manifestPath) 19 if err != nil { 20 return graph.Deps{}, err 21 } 22 if !rootPackageJsonExists { 23 return graph.Deps{}, errors.New(manifestPath + " does not exist") 24 } 25 26 yarnLockfileExists, err := files.Exists(lockFilePath) 27 if err != nil { 28 return graph.Deps{}, err 29 } 30 if !yarnLockfileExists { 31 return graph.Deps{}, errors.New(lockFilePath + " does not exist") 32 } 33 34 // To know which deps are direct, we need the manifest def 35 lockfile, err := readLockfile(lockFilePath) 36 if err != nil { 37 return graph.Deps{}, err 38 } 39 40 manifest, err := npm.FromManifest(manifestPath) 41 if err != nil { 42 return graph.Deps{}, err 43 } 44 45 directDeps := make(pkg.Imports, len(manifest.Dependencies)) 46 i := 0 47 for name, revision := range manifest.Dependencies { 48 directDeps[i] = pkg.Import{ 49 Target: name, 50 Resolved: pkg.ID{ 51 Name: name, 52 Revision: lockfile.resolve(name, revision), 53 Type: pkg.NodeJS, 54 }, 55 } 56 i++ 57 } 58 59 transitiveDepGraph := make(map[pkg.ID]pkg.Package) 60 lockfile.resolveDepGraph(manifest.Dependencies, transitiveDepGraph) 61 62 return graph.Deps{ 63 Direct: directDeps, 64 Transitive: transitiveDepGraph, 65 }, nil 66 } 67 68 func readLockfile(pathElems ...string) (yarnLockfile, error) { 69 var lockfile yarnLockfile 70 71 filePath := filepath.Join(pathElems...) 72 73 fileContent, err := files.Read(filePath) 74 if err != nil { 75 return yarnLockfile{}, err 76 } 77 78 r, err := regexp.Compile("\\s\"") 79 if err != nil { 80 return yarnLockfile{}, err 81 } 82 yamlCompatLockfile := r.ReplaceAll(fileContent, []byte(": \"")) 83 84 err = yaml.Unmarshal(yamlCompatLockfile, &lockfile) 85 if err != nil { 86 return yarnLockfile{}, err 87 } 88 89 return lockfile, nil 90 } 91 92 type lockfileEntry struct { 93 // resolved version for the particular entry based on the provided semver revision 94 Version string 95 // the list of unresolved modules and revisions (e.g. type-detect : ^4.0.0) 96 Dependencies map[string]string 97 } 98 99 // Keys for each lockfile entry follow the schema <moduleName>@<semverVersion> (e.g. type-detect@^4.0.0) 100 type yarnLockfile map[string]lockfileEntry 101 102 func (l yarnLockfile) resolve(moduleName string, unresolvedRevision string) string { 103 return l[moduleName+"@"+unresolvedRevision].Version 104 } 105 106 func (l yarnLockfile) resolveDepGraph(unresolvedDirectDeps map[string]string, depGraph map[pkg.ID]pkg.Package) { 107 for directDepName, unresolvedVersion := range unresolvedDirectDeps { 108 lockfileKey := directDepName + "@" + unresolvedVersion 109 110 entry := l[lockfileKey] 111 112 // Create package key for current dep 113 pkgID := pkg.ID{ 114 Name: directDepName, 115 Revision: entry.Version, 116 Type: pkg.NodeJS, 117 } 118 119 // Build direct deps for current import 120 imports := make(pkg.Imports, len(entry.Dependencies)) 121 i := 0 122 for name, semverRevision := range entry.Dependencies { 123 imports[i] = pkg.Import{ 124 Target: name, 125 Resolved: pkg.ID{ 126 Name: name, 127 Revision: l.resolve(name, semverRevision), 128 Type: pkg.NodeJS, 129 }, 130 } 131 i++ 132 } 133 134 // Add new package to dep graph 135 depGraph[pkgID] = pkg.Package{ 136 ID: pkgID, 137 Imports: imports, 138 Strategy: "yarn-lockfile", 139 } 140 141 // recurse for the n-2 direct deps 142 l.resolveDepGraph(entry.Dependencies, depGraph) 143 } 144 }