pulumi/pkg/util/archive/ignorer_node_modules.go
2017-11-29 12:15:16 -08:00

140 lines
3.5 KiB
Go

// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
package archive
import (
"container/list"
"encoding/json"
"os"
"path"
"strings"
"github.com/pulumi/pulumi/pkg/util/contract"
)
const nodeModulesDirName = "node_modules"
const packageJSONFileName = "package.json"
// newNodeModulesIgnorer constructs a new ignorer that ignores all files under node_modules that do not belong to
// packages in the transitive closure of the dependencies section of a package.json.
func newNodeModulesIgnorer(packageJSONPath string) (ignorer, error) {
depRoots, err := expandDepenencies(packageJSONPath)
if err != nil {
return nil, err
}
return &nodeModulesIgnorer{root: path.Dir(packageJSONPath), allowed: depRoots}, nil
}
func expandDepenencies(packageJSONPath string) ([]string, error) {
deps, err := getPackageDependencies(packageJSONPath)
if err != nil {
return nil, err
}
queue := list.New()
root := path.Dir(packageJSONPath)
for _, dep := range deps {
queue.PushBack(packageContext{name: dep, root: root})
}
var found struct{}
marked := make(map[string]struct{})
// While we still have packages we haven't processed, visit one.
for queue.Len() > 0 {
elm := queue.Front()
queue.Remove(elm)
ctx := elm.Value.(packageContext)
for curDir := ctx.root; strings.HasPrefix(curDir, root); curDir = path.Dir(curDir) {
candidateModuleFolder := path.Join(curDir, nodeModulesDirName, ctx.name)
if _, err := os.Stat(candidateModuleFolder); err == nil {
_, has := marked[candidateModuleFolder]
marked[candidateModuleFolder] = found
// If we hadn't already seen this module, ensure we also visit its dependencies.
if !has {
deps, err := getPackageDependencies(path.Join(candidateModuleFolder, packageJSONFileName))
if err != nil {
return nil, err
}
for _, dep := range deps {
queue.PushBack(packageContext{name: dep, root: candidateModuleFolder})
}
}
// Don't explore up the file system tree, since we found a match.
break
}
}
}
ret := make([]string, 0, len(marked))
for dir := range marked {
ret = append(ret, dir)
}
return ret, nil
}
func getPackageDependencies(packageJSONPath string) ([]string, error) {
f, err := os.Open(packageJSONPath)
if err != nil {
return nil, err
}
defer func() {
contract.IgnoreError(f.Close())
}()
dec := json.NewDecoder(f)
var file packageJSONFile
err = dec.Decode(&file)
if err != nil {
return nil, err
}
packages := make([]string, 0, len(file.Dependencies))
for dep := range file.Dependencies {
packages = append(packages, dep)
}
return packages, nil
}
type nodeModulesIgnorer struct {
root string // the directory for the node project this ingorer was built from
allowed []string // the list of all node_modules folders that the project needs
}
// MatchesPath for a nodeModulesIngorer can ignore any file under node_modules for the project
// it was constructed from as along as long as it is not in a directory for a module we know
// we must retain.
func (n *nodeModulesIgnorer) IsIgnored(f string) bool {
if !strings.HasPrefix(f, path.Join(n.root, nodeModulesDirName)) {
return false
}
for _, prefix := range n.allowed {
if strings.HasPrefix(f, prefix) {
return false
}
}
return true
}
type packageContext struct {
name string // The name of the package from the package.json.
root string // The directory of the package.json this was found in (this is where we have to start our search).
}
type packageJSONFile struct {
Dependencies map[string]string `json:"dependencies"`
}