diff options
Diffstat (limited to 'cli/internal/packagemanager/yarn.go')
| -rw-r--r-- | cli/internal/packagemanager/yarn.go | 116 |
1 files changed, 116 insertions, 0 deletions
diff --git a/cli/internal/packagemanager/yarn.go b/cli/internal/packagemanager/yarn.go new file mode 100644 index 0000000..8779c5f --- /dev/null +++ b/cli/internal/packagemanager/yarn.go @@ -0,0 +1,116 @@ +package packagemanager + +import ( + "errors" + "fmt" + "os/exec" + "path/filepath" + "strings" + + "github.com/Masterminds/semver" + "github.com/vercel/turbo/cli/internal/fs" + "github.com/vercel/turbo/cli/internal/lockfile" + "github.com/vercel/turbo/cli/internal/turbopath" +) + +// NoWorkspacesFoundError is a custom error used so that upstream implementations can switch on it +type NoWorkspacesFoundError struct{} + +func (e *NoWorkspacesFoundError) Error() string { + return "package.json: no workspaces found. Turborepo requires Yarn workspaces to be defined in the root package.json" +} + +var nodejsYarn = PackageManager{ + Name: "nodejs-yarn", + Slug: "yarn", + Command: "yarn", + Specfile: "package.json", + Lockfile: "yarn.lock", + PackageDir: "node_modules", + ArgSeparator: []string{"--"}, + + getWorkspaceGlobs: func(rootpath turbopath.AbsoluteSystemPath) ([]string, error) { + pkg, err := fs.ReadPackageJSON(rootpath.UntypedJoin("package.json")) + if err != nil { + return nil, fmt.Errorf("package.json: %w", err) + } + if len(pkg.Workspaces) == 0 { + return nil, &NoWorkspacesFoundError{} + } + return pkg.Workspaces, nil + }, + + getWorkspaceIgnores: func(pm PackageManager, rootpath turbopath.AbsoluteSystemPath) ([]string, error) { + // function: https://github.com/yarnpkg/yarn/blob/3119382885ea373d3c13d6a846de743eca8c914b/src/config.js#L799 + + // Yarn is unique in ignore patterns handling. + // The only time it does globbing is for package.json or yarn.json and it scopes the search to each workspace. + // For example: `apps/*/node_modules/**/+(package.json|yarn.json)` + // The `extglob` `+(package.json|yarn.json)` (from micromatch) after node_modules/** is redundant. + + globs, err := pm.getWorkspaceGlobs(rootpath) + if err != nil { + // In case of a non-monorepo, the workspaces field is empty and only node_modules in the root should be ignored + var e *NoWorkspacesFoundError + if errors.As(err, &e) { + return []string{"node_modules/**"}, nil + } + + return nil, err + } + + ignores := make([]string, len(globs)) + + for i, glob := range globs { + ignores[i] = filepath.Join(glob, "/node_modules/**") + } + + return ignores, nil + }, + + canPrune: func(cwd turbopath.AbsoluteSystemPath) (bool, error) { + return true, nil + }, + + // Versions older than 2.0 are yarn, after that they become berry + Matches: func(manager string, version string) (bool, error) { + if manager != "yarn" { + return false, nil + } + + v, err := semver.NewVersion(version) + if err != nil { + return false, fmt.Errorf("could not parse yarn version: %w", err) + } + c, err := semver.NewConstraint("<2.0.0-0") + if err != nil { + return false, fmt.Errorf("could not create constraint: %w", err) + } + + return c.Check(v), nil + }, + + // Detect for yarn needs to identify which version of yarn is running on the system. + detect: func(projectDirectory turbopath.AbsoluteSystemPath, packageManager *PackageManager) (bool, error) { + specfileExists := projectDirectory.UntypedJoin(packageManager.Specfile).FileExists() + lockfileExists := projectDirectory.UntypedJoin(packageManager.Lockfile).FileExists() + + // Short-circuit, definitely not Yarn. + if !specfileExists || !lockfileExists { + return false, nil + } + + cmd := exec.Command("yarn", "--version") + cmd.Dir = projectDirectory.ToString() + out, err := cmd.Output() + if err != nil { + return false, fmt.Errorf("could not detect yarn version: %w", err) + } + + return packageManager.Matches(packageManager.Slug, strings.TrimSpace(string(out))) + }, + + UnmarshalLockfile: func(_rootPackageJSON *fs.PackageJSON, contents []byte) (lockfile.Lockfile, error) { + return lockfile.DecodeYarnLockfile(contents) + }, +} |
