140 lines
3.5 KiB
Go
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"`
|
|
}
|