github.com/joey-fossa/fossa-cli@v0.7.34-0.20190708193710-569f1e8679f0/analyzers/bower/bower.go (about) 1 // Package bower implements analyzers for the Bower package manager. 2 // 3 // A `BuildTarget` for bower is the path to the `bower.json` of a project. 4 package bower 5 6 import ( 7 "fmt" 8 "os" 9 "path/filepath" 10 11 "github.com/apex/log" 12 "github.com/mitchellh/mapstructure" 13 "github.com/pkg/errors" 14 15 "github.com/fossas/fossa-cli/buildtools/bower" 16 "github.com/fossas/fossa-cli/exec" 17 "github.com/fossas/fossa-cli/files" 18 "github.com/fossas/fossa-cli/graph" 19 "github.com/fossas/fossa-cli/module" 20 "github.com/fossas/fossa-cli/pkg" 21 ) 22 23 type Analyzer struct { 24 BowerCmd string 25 BowerVersion string 26 27 Bower bower.Bower 28 29 Module module.Module 30 Options Options 31 } 32 33 // TODO: strategies: 34 // - bower list 35 // - read components 36 // - read manifest 37 38 type Options struct { 39 Strategy string `mapstructure:"strategy"` 40 ComponentsDir string `mapstructure:"components"` 41 } 42 43 func New(m module.Module) (*Analyzer, error) { 44 log.WithField("module", m).Debug("constructing Bower analyzer") 45 // Set Bower context variables 46 bowerCmd, bowerVersion, err := exec.Which("-v", os.Getenv("BOWER_BINARY"), "bower") 47 if err != nil { 48 return nil, errors.Wrap(err, "could not find Bower binary (try setting $BOWER_BINARY)") 49 } 50 51 // Decode options 52 var options Options 53 err = mapstructure.Decode(m.Options, &options) 54 if err != nil { 55 return nil, err 56 } 57 58 b, err := bower.New(bowerCmd, m.Dir) 59 if err != nil { 60 return nil, errors.Wrap(err, "could not set up Bower") 61 } 62 63 analyzer := Analyzer{ 64 BowerCmd: bowerCmd, 65 BowerVersion: bowerVersion, 66 Bower: *b, 67 68 Module: m, 69 Options: options, 70 } 71 72 log.WithField("analyzer", analyzer).Debug("constructed Bower analyzer") 73 return &analyzer, nil 74 } 75 76 // Discover finds any `bower.json`s not in `node_modules` or `bower_components` 77 // folders. 78 func Discover(dir string, options map[string]interface{}) ([]module.Module, error) { 79 var moduleConfigs []module.Module 80 err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { 81 if err != nil { 82 log.WithError(err).WithField("path", path).Debug("error while walking") 83 return err 84 } 85 // Skip **/node_modules and **/bower_components directories 86 if info.IsDir() && (info.Name() == "node_modules" || info.Name() == "bower_components") { 87 log.WithField("dir", info.Name()).Debug("skipping directory") 88 return filepath.SkipDir 89 } 90 91 if !info.IsDir() && info.Name() == "bower.json" { 92 dir := filepath.Dir(path) 93 name := filepath.Base(dir) 94 95 // Parse from bower.json and set name if successful 96 manifest, err := bower.ReadManifest(path) 97 if err == nil { 98 name = manifest.Name 99 } 100 101 log.WithFields(log.Fields{ 102 "path": path, 103 "name": name, 104 }).Debug("discovered Bower module") 105 moduleConfigs = append(moduleConfigs, module.Module{ 106 Name: name, 107 Type: pkg.Bower, 108 BuildTarget: path, 109 Dir: dir, 110 }) 111 } 112 return nil 113 }) 114 115 if err != nil { 116 return nil, fmt.Errorf("could not find bower package manifests: %s", err.Error()) 117 } 118 119 return moduleConfigs, nil 120 } 121 122 func (a *Analyzer) Clean() error { 123 return a.Bower.Clean() 124 } 125 126 // Build runs `bower install --production` 127 func (a *Analyzer) Build() error { 128 return a.Bower.Install(true) 129 } 130 131 // IsBuilt checks for the existence of a components folder. 132 func (a *Analyzer) IsBuilt() (bool, error) { 133 config, err := bower.ReadConfig(a.Module.Dir) 134 if err != nil { 135 return false, err 136 } 137 // TODO: Check if the installed modules are consistent with what's in the 138 // actual manifest. 139 bowerComponentsDir := config.Directory 140 if !filepath.IsAbs(bowerComponentsDir) { 141 bowerComponentsDir = filepath.Join(a.Module.Dir, config.Directory) 142 } 143 isBuilt, err := files.ExistsFolder(bowerComponentsDir) 144 if err != nil { 145 return false, err 146 } 147 148 log.WithField("isBuilt", isBuilt).Debug("done checking Bower build") 149 return isBuilt, nil 150 } 151 152 func (a *Analyzer) Analyze() (graph.Deps, error) { 153 p, err := a.Bower.List() 154 if err != nil { 155 return graph.Deps{}, err 156 } 157 158 var imports []pkg.Import 159 for _, dep := range p.Dependencies { 160 imports = append(imports, pkg.Import{ 161 Target: dep.PkgMeta.TargetName + "@" + dep.PkgMeta.TargetVersion, 162 Resolved: pkg.ID{ 163 Type: pkg.Bower, 164 Name: dep.PkgMeta.Name, 165 Revision: dep.PkgMeta.Version, 166 Location: dep.Endpoint.Source, 167 }, 168 }) 169 } 170 171 deps := make(map[pkg.ID]pkg.Package) 172 recurseDeps(deps, p) 173 174 return graph.Deps{ 175 Direct: imports, 176 Transitive: deps, 177 }, nil 178 } 179 180 func recurseDeps(pkgMap map[pkg.ID]pkg.Package, p bower.Package) { 181 for name, dep := range p.Dependencies { 182 // Construct ID. 183 id := pkg.ID{ 184 Type: pkg.Bower, 185 Name: name, 186 Revision: dep.PkgMeta.Version, 187 Location: dep.Endpoint.Source, 188 } 189 // Don't process duplicates. 190 _, ok := pkgMap[id] 191 if ok { 192 continue 193 } 194 // Get direct imports. 195 var imports []pkg.Import 196 for name, i := range dep.Dependencies { 197 imports = append(imports, pkg.Import{ 198 Target: i.PkgMeta.TargetName + "@" + i.PkgMeta.TargetVersion, 199 Resolved: pkg.ID{ 200 Type: pkg.Bower, 201 Name: name, 202 Revision: i.PkgMeta.Version, 203 Location: i.Endpoint.Source, 204 }, 205 }) 206 } 207 // Update map. 208 pkgMap[id] = pkg.Package{ 209 ID: id, 210 Imports: imports, 211 } 212 // Recurse in imports. 213 recurseDeps(pkgMap, dep) 214 } 215 }