package lockfile import ( "bytes" "fmt" "io" "github.com/andybalholm/crlf" "github.com/iseki0/go-yarnlock" "github.com/pkg/errors" "github.com/vercel/turbo/cli/internal/turbopath" ) var _crlfLiteral = []byte("\r\n") // YarnLockfile representation of yarn lockfile type YarnLockfile struct { inner yarnlock.LockFile hasCRLF bool } var _ Lockfile = (*YarnLockfile)(nil) // ResolvePackage Given a package and version returns the key, resolved version, and if it was found func (l *YarnLockfile) ResolvePackage(_workspacePath turbopath.AnchoredUnixPath, name string, version string) (Package, error) { for _, key := range yarnPossibleKeys(name, version) { if entry, ok := (l.inner)[key]; ok { return Package{ Found: true, Key: key, Version: entry.Version, }, nil } } return Package{}, nil } // AllDependencies Given a lockfile key return all (dev/optional/peer) dependencies of that package func (l *YarnLockfile) AllDependencies(key string) (map[string]string, bool) { deps := map[string]string{} entry, ok := (l.inner)[key] if !ok { return deps, false } for name, version := range entry.Dependencies { deps[name] = version } for name, version := range entry.OptionalDependencies { deps[name] = version } return deps, true } // Subgraph Given a list of lockfile keys returns a Lockfile based off the original one that only contains the packages given func (l *YarnLockfile) Subgraph(_ []turbopath.AnchoredSystemPath, packages []string) (Lockfile, error) { lockfile := make(map[string]yarnlock.LockFileEntry, len(packages)) for _, key := range packages { entry, ok := (l.inner)[key] if ok { lockfile[key] = entry } } return &YarnLockfile{lockfile, l.hasCRLF}, nil } // Encode encode the lockfile representation and write it to the given writer func (l *YarnLockfile) Encode(w io.Writer) error { writer := w if l.hasCRLF { writer = crlf.NewWriter(w) } if err := l.inner.Encode(writer); err != nil { return errors.Wrap(err, "Unable to encode yarn.lock") } return nil } // Patches return a list of patches used in the lockfile func (l *YarnLockfile) Patches() []turbopath.AnchoredUnixPath { return nil } // DecodeYarnLockfile Takes the contents of a yarn lockfile and returns a struct representation func DecodeYarnLockfile(contents []byte) (*YarnLockfile, error) { lockfile, err := yarnlock.ParseLockFileData(contents) hasCRLF := bytes.HasSuffix(contents, _crlfLiteral) newline := []byte("\n") // there's no trailing newline for this file, need to inspect more to see newline style if !hasCRLF && !bytes.HasSuffix(contents, newline) { firstNewline := bytes.IndexByte(contents, newline[0]) if firstNewline != -1 && firstNewline != 0 { byteBeforeNewline := contents[firstNewline-1] hasCRLF = byteBeforeNewline == '\r' } } if err != nil { return nil, errors.Wrap(err, "Unable to decode yarn.lock") } return &YarnLockfile{lockfile, hasCRLF}, nil } // GlobalChange checks if there are any differences between lockfiles that would completely invalidate // the cache. func (l *YarnLockfile) GlobalChange(other Lockfile) bool { _, ok := other.(*YarnLockfile) return !ok } func yarnPossibleKeys(name string, version string) []string { return []string{ fmt.Sprintf("%v@%v", name, version), fmt.Sprintf("%v@npm:%v", name, version), fmt.Sprintf("%v@file:%v", name, version), fmt.Sprintf("%v@workspace:%v", name, version), fmt.Sprintf("%v@yarn:%v", name, version), } }