github.com/joey-fossa/fossa-cli@v0.7.34-0.20190708193710-569f1e8679f0/buildtools/npm/manifest.go (about) 1 package npm 2 3 import ( 4 "path/filepath" 5 6 "github.com/fossas/fossa-cli/errors" 7 "github.com/fossas/fossa-cli/files" 8 "github.com/fossas/fossa-cli/graph" 9 "github.com/fossas/fossa-cli/pkg" 10 ) 11 12 type Manifest struct { 13 Name string 14 Version string 15 Dependencies map[string]string 16 } 17 18 // FromManifest creates a manifest from the filepath provided 19 func FromManifest(pathElems ...string) (Manifest, error) { 20 var manifest Manifest 21 22 filePath := filepath.Join(pathElems...) 23 24 err := files.ReadJSON(&manifest, filePath) 25 if err != nil { 26 return Manifest{}, err 27 } 28 29 return manifest, nil 30 } 31 32 // PackageFromManifest generates a package definition for the provided manifest in the supplied directory. Performs revision resolution 33 func PackageFromManifest(pathElems ...string) (pkg.Package, error) { 34 filePath := filepath.Join(pathElems...) 35 manifest, err := FromManifest(filePath) 36 if err != nil { 37 return pkg.Package{}, err 38 } 39 40 manifestAsPackage := convertManifestToPkg(manifest) 41 42 // attempt to resolve revisions if node_modules folder exists and package any imports 43 if len(manifestAsPackage.Imports) == 0 { 44 return manifestAsPackage, nil 45 } 46 47 nodeModuleDirectory := filepath.Join(filepath.Dir(filePath), "node_modules") 48 if err != nil { 49 return pkg.Package{}, err 50 } 51 52 err = resolveDirectDependencyVersions(&manifestAsPackage, nodeModuleDirectory) 53 if err != nil { 54 return pkg.Package{}, err 55 } 56 57 return manifestAsPackage, nil 58 } 59 60 // FromNodeModules generates the dep graph based on the manifest provided at the supplied path 61 func FromNodeModules(pathElems ...string) (graph.Deps, error) { 62 manifestPath := filepath.Join(pathElems...) 63 exists, err := files.Exists(manifestPath) 64 if err != nil { 65 return graph.Deps{}, err 66 } else if !exists { 67 return graph.Deps{}, errors.New("no package.json at root of node project") 68 } 69 70 rootPackage, err := PackageFromManifest(manifestPath) 71 if err != nil { 72 return graph.Deps{}, err 73 } 74 75 transitiveDeps := make(map[pkg.ID]pkg.Package) 76 77 err = fromModulesHelper(manifestPath, transitiveDeps) 78 79 // The root package also get's bundled in, but it is not a dep of itself, so remove it 80 delete(transitiveDeps, rootPackage.ID) 81 82 if err != nil { 83 return graph.Deps{}, err 84 } 85 86 return graph.Deps{ 87 Direct: rootPackage.Imports, 88 Transitive: transitiveDeps, 89 }, nil 90 } 91 92 type Lockfile struct { 93 Dependencies map[string]struct { 94 Version string 95 Requires map[string]string 96 } 97 } 98 99 func FromLockfile(filename string) (Lockfile, error) { 100 return Lockfile{}, errors.ErrNotImplemented 101 } 102 103 // TODO: add support for NODE_PATH and GLOBAL_FOLDERS. 104 func modulePath(startingDir string, moduleName string) (string, error) { 105 filePath, err := files.WalkUp(startingDir, func(currentDir string) (err error) { 106 if filepath.Base(currentDir) == moduleName { 107 return files.ErrStopWalk 108 } 109 110 exists, err := files.ExistsFolder(filepath.Join(currentDir, moduleName)) 111 if err != nil { 112 return err 113 } 114 115 if exists { 116 return files.ErrStopWalk 117 } 118 119 return nil 120 }) 121 122 return filepath.Join(filePath, moduleName, "package.json"), err 123 } 124 125 // convertManifestToPkg converts a given manifest to a package. Does not resolve unresolved imports 126 func convertManifestToPkg(manifest Manifest) pkg.Package { 127 id := pkg.ID{ 128 Type: pkg.NodeJS, 129 Name: manifest.Name, 130 Revision: manifest.Version, 131 } 132 133 var imports pkg.Imports 134 for depName, version := range manifest.Dependencies { 135 id := pkg.ID{ 136 Type: pkg.NodeJS, 137 Name: depName, 138 Revision: version, 139 } 140 141 depImport := pkg.Import{ 142 Target: depName, 143 Resolved: id, 144 } 145 imports = append(imports, depImport) 146 } 147 148 return pkg.Package{ 149 ID: id, 150 Imports: imports, 151 } 152 } 153 154 /* 155 1. get package at pathToModule 156 2. add currentPackage to the accumulator 157 3. for each of the dependencies found in currentPackage 158 3a. determine the correct path to that module 159 3b. create a package for that dependency and add it to the accumulator 160 3c. recurse to 1 using the path to the dependency 161 */ 162 func fromModulesHelper(pathToModule string, moduleProjects map[pkg.ID]pkg.Package) error { 163 currentDir := filepath.Dir(pathToModule) 164 165 currentModule, err := PackageFromManifest(pathToModule) 166 if err != nil { 167 return err 168 } 169 170 moduleProjects[currentModule.ID] = currentModule 171 172 for _, imported := range currentModule.Imports { 173 currentDirWithNodeModules := filepath.Join(currentDir, "node_modules") 174 pathToDepModule, err := modulePath(currentDirWithNodeModules, imported.Target) 175 if err != nil { 176 return err 177 } 178 179 modulePackage, err := PackageFromManifest(pathToDepModule) //, "package.json") 180 if err != nil { 181 return err 182 } 183 184 moduleProjects[modulePackage.ID] = modulePackage 185 186 err = fromModulesHelper(pathToDepModule, moduleProjects) 187 if err != nil { 188 return err 189 } 190 } 191 192 return nil 193 } 194 195 func resolveDirectDependencyVersions(providedPackge *pkg.Package, nodeModuleDirectory string) error { 196 for i, directDep := range providedPackge.Imports { 197 directDepPath, err := modulePath(nodeModuleDirectory, directDep.Target) 198 if err != nil { 199 return err 200 } 201 202 directDepManifest, err := FromManifest(directDepPath) 203 if err != nil { 204 return err 205 } 206 207 providedPackge.Imports[i].Resolved.Revision = directDepManifest.Version 208 } 209 210 return nil 211 }