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  }