aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/cli/internal/lockfile/npm_lockfile.go
blob: 67cd32af6389757698f73d26ce3272309670ab80 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
package lockfile

import (
	"encoding/json"
	"io"

	mapset "github.com/deckarep/golang-set"
	"github.com/vercel/turbo/cli/internal/ffi"
	"github.com/vercel/turbo/cli/internal/turbopath"
)

// NpmLockfile representation of package-lock.json
type NpmLockfile struct {
	// We just story the entire lockfile in memory and pass it for every call
	contents []byte
}

// ResolvePackage Given a workspace, a package it imports and version returns the key, resolved version, and if it was found
func (l *NpmLockfile) ResolvePackage(workspacePath turbopath.AnchoredUnixPath, name string, version string) (Package, error) {
	// This is only used when doing calculating the transitive deps, but Rust
	// implementations do this calculation on the Rust side.
	panic("Unreachable")
}

// AllDependencies Given a lockfile key return all (dev/optional/peer) dependencies of that package
func (l *NpmLockfile) AllDependencies(key string) (map[string]string, bool) {
	// This is only used when doing calculating the transitive deps, but Rust
	// implementations do this calculation on the Rust side.
	panic("Unreachable")
}

// Subgraph Given a list of lockfile keys returns a Lockfile based off the original one that only contains the packages given
func (l *NpmLockfile) Subgraph(workspacePackages []turbopath.AnchoredSystemPath, packages []string) (Lockfile, error) {
	workspaces := make([]string, len(workspacePackages))
	for i, workspace := range workspacePackages {
		workspaces[i] = workspace.ToUnixPath().ToString()
	}
	contents, err := ffi.NpmSubgraph(l.contents, workspaces, packages)
	if err != nil {
		return nil, err
	}
	return &NpmLockfile{contents: contents}, nil
}

// Encode the lockfile representation and write it to the given writer
func (l *NpmLockfile) Encode(w io.Writer) error {
	_, err := w.Write(l.contents)
	return err
}

// Patches return a list of patches used in the lockfile
func (l *NpmLockfile) Patches() []turbopath.AnchoredUnixPath {
	return nil
}

// GlobalChange checks if there are any differences between lockfiles that would completely invalidate
// the cache.
func (l *NpmLockfile) GlobalChange(other Lockfile) bool {
	o, ok := other.(*NpmLockfile)
	if !ok {
		return true
	}

	// We just grab the few global fields and check if they've changed
	type minimalJSON struct {
		LockfileVersion string `json:"version"`
		Requires        bool   `json:"requires"`
	}
	var self minimalJSON
	var otherJSON minimalJSON
	if err := json.Unmarshal(o.contents, &otherJSON); err != nil {
		return true
	}
	if err := json.Unmarshal(l.contents, &self); err != nil {
		return true
	}

	return self.LockfileVersion != otherJSON.LockfileVersion ||
		self.Requires != otherJSON.Requires
}

var _ (Lockfile) = (*NpmLockfile)(nil)

// DecodeNpmLockfile Parse contents of package-lock.json into NpmLockfile
func DecodeNpmLockfile(contents []byte) (Lockfile, error) {
	return &NpmLockfile{contents: contents}, nil
}

func npmTransitiveDeps(lockfile *NpmLockfile, workspacePath turbopath.AnchoredUnixPath, unresolvedDeps map[string]string) (mapset.Set, error) {
	pkgDir := workspacePath.ToString()

	packages, err := ffi.NpmTransitiveDeps(lockfile.contents, pkgDir, unresolvedDeps)
	if err != nil {
		return nil, err
	}

	deps := make([]interface{}, len(packages))
	for i, pkg := range packages {
		deps[i] = Package{
			Found:   pkg.Found,
			Key:     pkg.Key,
			Version: pkg.Version,
		}
	}

	return mapset.NewSetFromSlice(deps), nil
}