diff --git a/deplist.go b/deplist.go index 43964893..5458b75b 100644 --- a/deplist.go +++ b/deplist.go @@ -26,6 +26,10 @@ func init() { log.Fatal("yarn is required in PATH") } + if _, err := exec.LookPath("npm"); err != nil { + log.Fatal("npm is required in PATH") + } + if _, err := exec.LookPath("go"); err != nil { log.Fatal("go is required") } @@ -86,8 +90,8 @@ func GetDeps(fullPath string) ([]Dependency, Bitmask, error) { // paths. Useful if we're looking for top of repo switch filename := info.Name(); filename { - // for now only go for yarn - case "yarn.lock": + // for now only go for yarn and npm + case "yarn.lock", "package-lock.json": pkgs, err := scan.GetNodeJSDeps(path) if err != nil { return err diff --git a/internal/scan/nodejs.go b/internal/scan/nodejs.go index 7c3bdaaf..ec7c87f4 100644 --- a/internal/scan/nodejs.go +++ b/internal/scan/nodejs.go @@ -2,6 +2,7 @@ package scan import ( "encoding/json" + "fmt" "os/exec" "path/filepath" "strings" @@ -23,9 +24,36 @@ type yarnOutput struct { } } +type npmDependency struct { + Version string `json:"version"` + Dependencies map[string]npmDependency `json:"dependencies"` +} + +type npmListOutput struct { + Dependencies map[string]npmDependency `json:"dependencies"` +} + var gathered map[string]string -func gatherNode(dep yarnDependency) { +func recordPackage(packageName, version string) { + // compare everything + if !strings.HasPrefix(version, "v") { + version = "v" + version + } + version = strings.Replace(version, "^", "", 1) + version = strings.Replace(version, "~", "", 1) + + version = strings.Replace(version, "x", "0", 1) + version = strings.Replace(version, "*", "0.0.0", 1) + + if oldVersion, ok := gathered[packageName]; ok { + gathered[packageName] = semver.Max(oldVersion, version) + } else { + gathered[packageName] = version + } +} + +func gatherYarnNode(dep yarnDependency) { // incase package starts with @ splitIdx := strings.LastIndex(dep.Name, "@") @@ -40,28 +68,33 @@ func gatherNode(dep yarnDependency) { version = "v0.0.0" } - // compare everything - version = strings.Replace(version, "^", "", 1) - version = strings.Replace(version, "~", "", 1) - - version = strings.Replace(version, "x", "0", 1) - version = strings.Replace(version, "*", "0.0.0", 1) - - if _, ok := gathered[name]; ok { - gathered[name] = semver.Max(gathered[name], version) - } else { - gathered[name] = version - } + recordPackage(name, version) if len(dep.Children) > 0 { for _, child := range dep.Children { - gatherNode(child) + gatherYarnNode(child) } } } +func gatherNPMNode(name string, dependency npmDependency) { + recordPackage(name, dependency.Version) + for childName, childDep := range dependency.Dependencies { + gatherNPMNode(childName, childDep) + } +} + func GetNodeJSDeps(path string) (map[string]string, error) { + switch filepath.Base(path) { + case "yarn.lock": + return getYarnDeps(path) + case "package-lock.json": + return getNPMDeps(path) + } + return nil, fmt.Errorf("unknown NodeJS dependency file %q", path) +} +func getYarnDeps(path string) (map[string]string, error) { var yarnOutput yarnOutput gathered = make(map[string]string) @@ -79,7 +112,32 @@ func GetNodeJSDeps(path string) (map[string]string, error) { } for _, deps := range yarnOutput.Data.Deps { - gatherNode(deps) + gatherYarnNode(deps) + } + + return gathered, nil +} + +func getNPMDeps(path string) (map[string]string, error) { + var npmOutput npmListOutput + gathered = make(map[string]string) + + cmd := exec.Command("npm", "list", "--prod", "--json", "--depth=99") + cmd.Dir = filepath.Dir(path) + + data, err := cmd.Output() + + if err != nil { + return nil, err + } + + err = json.Unmarshal(data, &npmOutput) + if err != nil { + return nil, err + } + + for depName, dep := range npmOutput.Dependencies { + gatherNPMNode(depName, dep) } return gathered, nil