This is an automated email from the ASF dual-hosted git repository.
kezhenxu94 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-eyes.git
The following commit(s) were added to refs/heads/main by this push:
new 804fb3e Enhance NPM dependency resolver to resolve all the dependent
packages (#51)
804fb3e is described below
commit 804fb3e6af501770240411165dbf304507518acc
Author: Youhan Wu <[email protected]>
AuthorDate: Tue Aug 3 16:14:48 2021 +0800
Enhance NPM dependency resolver to resolve all the dependent packages (#51)
---
pkg/deps/npm.go | 209 ++++++++++++++++++++++++++++++++------------------------
1 file changed, 120 insertions(+), 89 deletions(-)
diff --git a/pkg/deps/npm.go b/pkg/deps/npm.go
index 0635b6a..05753a2 100644
--- a/pkg/deps/npm.go
+++ b/pkg/deps/npm.go
@@ -19,9 +19,10 @@ package deps
import (
"bufio"
+ "bytes"
"encoding/json"
- "errors"
"fmt"
+ "io"
"io/ioutil"
"os"
"os/exec"
@@ -38,57 +39,47 @@ type NpmResolver struct {
// Package represents package.json
type Package struct {
- Name string `json:"name"`
- Version string `json:"version"`
- License string `json:"license"`
- Dependencies map[string]string `json:"dependencies"`
+ Name string `json:"name"`
+ License string `json:"license"`
+ Path string
}
-const packageFile = "package.json"
+const PkgFileName = "package.json"
// CanResolve checks whether the given file is the npm package file
func (resolver *NpmResolver) CanResolve(file string) bool {
base := filepath.Base(file)
logger.Log.Debugln("Base name:", base)
- return base == packageFile
+ return base == PkgFileName
}
// Resolve resolves licenses of all dependencies declared in the package.json
file.
-func (resolver *NpmResolver) Resolve(packageFile string, report *Report) error
{
- // Parse the project package file to gather the required dependencies
- packageInfo, err := resolver.parsePackageFile(packageFile)
- if err != nil {
+func (resolver *NpmResolver) Resolve(pkgFile string, report *Report) error {
+ workDir := filepath.Dir(pkgFile)
+ if err := os.Chdir(workDir); err != nil {
return err
}
- depNames := make([]string, 0, len(packageInfo.Dependencies))
- for dep := range packageInfo.Dependencies {
- depNames = append(depNames, dep)
- }
// Run command 'npm install' to install or update the required node
packages
- // Query from the command line first whether to skip this procedure
+ // Query from the command line first whether to skip this procedure,
// in case that the dependent packages are downloaded and brought
up-to-date
- root := filepath.Dir(packageFile)
if needSkip := resolver.NeedSkipInstallPkgs(); !needSkip {
- if err := resolver.InstallPkgs(root); err != nil {
- return fmt.Errorf("fail to install depNames: %+v", err)
- }
+ resolver.InstallPkgs()
}
- // Change working directory to node_modules
- depPath := filepath.Join(root, "node_modules")
- if err := os.Chdir(depPath); err != nil {
- return err
- }
+ // Run command 'npm ls --all --parseable' to list all the installed
packages' paths
+ // Use a package directory's relative path from the node_modules
directory, to infer its package name
+ // Thus gathering all the installed packages' names and paths
+ pkgDir := filepath.Join(workDir, "node_modules")
+ pkgs := resolver.GetInstalledPkgs(pkgDir)
// Walk through each package's root directory to resolve licenses
- // First, try to parse the package's package.json file to check the
license file
- // If the previous step fails, then try to identify the package's
LICENSE file
- for _, depName := range depNames {
- if err := resolver.ResolvePackageLicense(depName, report); err
!= nil {
- logger.Log.Warnln("Failed to resolve the license of
dependency:", depName, err)
+ // Resolve from a package's package.json file or its license file
+ for _, pkg := range pkgs {
+ if err := resolver.ResolvePackageLicense(pkg.Name, pkg.Path,
report); err != nil {
+ logger.Log.Warnln("Failed to resolve the license of
dependency:", pkg.Name, err)
report.Skip(&Result{
- Dependency: depName,
+ Dependency: pkg.Name,
LicenseSpdxID: Unknown,
})
}
@@ -96,19 +87,6 @@ func (resolver *NpmResolver) Resolve(packageFile string,
report *Report) error {
return nil
}
-// parsePackageFile parses the content of the package file
-func (resolver *NpmResolver) parsePackageFile(packageFile string) (*Package,
error) {
- content, err := ioutil.ReadFile(packageFile)
- if err != nil {
- return nil, err
- }
- var packageInfo Package
- if err := json.Unmarshal(content, &packageInfo); err != nil {
- return nil, err
- }
- return &packageInfo, nil
-}
-
// NeedSkipInstallPkgs queries whether to skip the procedure of installing or
updating packages
func (resolver *NpmResolver) NeedSkipInstallPkgs() bool {
const countdown = 5
@@ -141,79 +119,132 @@ func (resolver *NpmResolver) NeedSkipInstallPkgs() bool {
}
// InstallPkgs runs command 'npm install' to install node packages
-func (resolver *NpmResolver) InstallPkgs(root string) error {
+func (resolver *NpmResolver) InstallPkgs() {
cmd := exec.Command("npm", "install")
- cmd.Dir = root
logger.Log.Println(fmt.Sprintf("Run command: %v, please wait",
cmd.String()))
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
+ // Error occurs all the time in npm commands, so no return statement
here
if err := cmd.Run(); err != nil {
- return err
+ logger.Log.Errorln(err)
}
- return nil
}
-// ResolvePackageLicense resolves the licenses of the given packages.
-func (resolver *NpmResolver) ResolvePackageLicense(depName string, report
*Report) error {
- depFiles, err := ioutil.ReadDir(depName)
+// ListPkgPaths runs npm command to list all the production only packages'
absolute paths, one path per line
+// Note that although the flag `--long` can show more information line like a
package's name,
+// its realization and printing format is not uniform in different npm-cli
versions
+func (resolver *NpmResolver) ListPkgPaths() (io.Reader, error) {
+ buffer := &bytes.Buffer{}
+ cmd := exec.Command("npm", "ls", "--all", "--production", "--parseable")
+ cmd.Stderr = os.Stderr
+ cmd.Stdout = buffer
+ // Error occurs all the time in npm commands, so no return statement
here
+ err := cmd.Run()
+ return buffer, err
+}
+
+// GetInstalledPkgs gathers all the installed packages' names and paths
+// it uses a package directory's relative path from the node_modules
directory, to infer its package name
+func (resolver *NpmResolver) GetInstalledPkgs(pkgDir string) []*Package {
+ buffer, err := resolver.ListPkgPaths()
+ // Error occurs all the time in npm commands, so no return statement
here
if err != nil {
- return err
+ logger.Log.Errorln(err)
}
-
- // Record the errors encountered when parsing the package.json file
- packageErr := errors.New("cannot find the package.json file")
-
- // STEP 1: Try to find and parse the package.json file to capture the
license field
- for _, info := range depFiles {
- if info.IsDir() || info.Name() != packageFile {
- continue
- }
- packageFilePath := filepath.Join(depName, info.Name())
- packageInfo, err := resolver.parsePackageFile(packageFilePath)
+ pkgs := make([]*Package, 0)
+ sc := bufio.NewScanner(buffer)
+ for sc.Scan() {
+ absPath := sc.Text()
+ rel, err := filepath.Rel(pkgDir, absPath)
if err != nil {
- packageErr = err
- break
+ logger.Log.Errorln(err)
+ continue
}
- if packageInfo.License == "" {
- packageErr = fmt.Errorf("cannot capture the license
field")
- break
+ if rel == "" || rel == "." || rel == ".." {
+ continue
}
+ pkgName := filepath.ToSlash(rel)
+ pkgs = append(pkgs, &Package{
+ Name: pkgName,
+ Path: absPath,
+ })
+ }
+ return pkgs
+}
+
+// ResolvePackageLicense resolves the licenses of the given packages.
+// First, try to find and parse the package's package.json file to check the
license file
+// If the previous step fails, then try to identify the package's LICENSE file
+func (resolver *NpmResolver) ResolvePackageLicense(pkgName, pkgPath string,
report *Report) error {
+ var resolveErrs error
+ expectedPkgFile := filepath.Join(pkgPath, PkgFileName)
+ lcs, err := resolver.ResolvePkgFile(expectedPkgFile)
+ if err == nil {
report.Resolve(&Result{
- Dependency: depName,
- LicenseFilePath: "",
- LicenseContent: "",
- LicenseSpdxID: packageInfo.License,
+ Dependency: pkgName,
+ LicenseSpdxID: lcs,
})
return nil
}
+ resolveErrs = err
- // Record the errors encountered when identifying the license file
- licenseErr := errors.New("cannot find the license file")
+ lcs, err = resolver.ResolveLcsFile(pkgName, pkgPath)
+ if err == nil {
+ report.Resolve(&Result{
+ Dependency: pkgName,
+ LicenseSpdxID: lcs,
+ })
+ return nil
+ }
+ resolveErrs = fmt.Errorf("%+v; %+v", resolveErrs, err)
+ return resolveErrs
+}
+
+// ResolvePkgFile tries to find and parse the package.json file to capture the
license field
+func (resolver *NpmResolver) ResolvePkgFile(pkgFile string) (string, error) {
+ packageInfo, err := resolver.ParsePkgFile(pkgFile)
+ if err != nil {
+ return "", err
+ }
+ if packageInfo.License == "" {
+ return "", fmt.Errorf("cannot capture the license field")
+ }
+ return packageInfo.License, nil
+}
- // STEP 2: Try to find the license file to identify the license
+// ResolveLcsFile tries to find the license file to identify the license
+func (resolver *NpmResolver) ResolveLcsFile(pkgName, pkgPath string) (string,
error) {
+ depFiles, err := ioutil.ReadDir(pkgPath)
+ if err != nil {
+ return "", err
+ }
for _, info := range depFiles {
if info.IsDir() ||
!possibleLicenseFileName.MatchString(info.Name()) {
continue
}
- licenseFilePath := filepath.Join(depName, info.Name())
+ licenseFilePath := filepath.Join(pkgPath, info.Name())
content, err := ioutil.ReadFile(licenseFilePath)
if err != nil {
- licenseErr = err
- break
+ return "", err
}
- identifier, err := license.Identify(depName, string(content))
+ identifier, err := license.Identify(pkgName, string(content))
if err != nil {
- licenseErr = err
- break
+ return "", err
}
- report.Resolve(&Result{
- Dependency: depName,
- LicenseFilePath: licenseFilePath,
- LicenseContent: string(content),
- LicenseSpdxID: identifier,
- })
- return nil
+ return identifier, nil
}
+ return "", fmt.Errorf("cannot find the license file")
+}
- return fmt.Errorf("%+v; %+v", packageErr, licenseErr)
+// ParsePkgFile parses the content of the package file
+func (resolver *NpmResolver) ParsePkgFile(pkgFile string) (*Package, error) {
+ content, err := ioutil.ReadFile(pkgFile)
+ if err != nil {
+ return nil, err
+ }
+ var packageInfo Package
+ if err := json.Unmarshal(content, &packageInfo); err != nil {
+ return nil, err
+ }
+ return &packageInfo, nil
}