diff options
Diffstat (limited to 'cli/internal/packagemanager/berry.go')
| -rw-r--r-- | cli/internal/packagemanager/berry.go | 156 |
1 files changed, 156 insertions, 0 deletions
diff --git a/cli/internal/packagemanager/berry.go b/cli/internal/packagemanager/berry.go new file mode 100644 index 0000000..d6264b1 --- /dev/null +++ b/cli/internal/packagemanager/berry.go @@ -0,0 +1,156 @@ +package packagemanager + +import ( + "fmt" + "os/exec" + "strings" + + "github.com/Masterminds/semver" + "github.com/pkg/errors" + "github.com/vercel/turbo/cli/internal/fs" + "github.com/vercel/turbo/cli/internal/lockfile" + "github.com/vercel/turbo/cli/internal/turbopath" + "github.com/vercel/turbo/cli/internal/util" +) + +var nodejsBerry = PackageManager{ + Name: "nodejs-berry", + Slug: "yarn", + Command: "yarn", + Specfile: "package.json", + Lockfile: "yarn.lock", + PackageDir: "node_modules", + + 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, fmt.Errorf("package.json: no workspaces found. Turborepo requires Yarn workspaces to be defined in the root package.json") + } + return pkg.Workspaces, nil + }, + + getWorkspaceIgnores: func(pm PackageManager, rootpath turbopath.AbsoluteSystemPath) ([]string, error) { + // Matches upstream values: + // Key code: https://github.com/yarnpkg/berry/blob/8e0c4b897b0881878a1f901230ea49b7c8113fbe/packages/yarnpkg-core/sources/Workspace.ts#L64-L70 + return []string{ + "**/node_modules", + "**/.git", + "**/.yarn", + }, nil + }, + + canPrune: func(cwd turbopath.AbsoluteSystemPath) (bool, error) { + if isNMLinker, err := util.IsNMLinker(cwd.ToStringDuringMigration()); err != nil { + return false, errors.Wrap(err, "could not determine if yarn is using `nodeLinker: node-modules`") + } else if !isNMLinker { + return false, errors.New("only yarn v2/v3 with `nodeLinker: node-modules` is supported at this time") + } + return true, nil + }, + + // Versions newer than 2.0 are berry, and before that we simply call them yarn. + 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) + } + // -0 allows pre-releases versions to be considered valid + 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 berry needs to identify which version of yarn is running on the system. + // Further, berry can be configured in an incompatible way, so we check for compatibility here as well. + 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) + } + + // See if we're a match when we compare these two things. + matches, _ := packageManager.Matches(packageManager.Slug, string(out)) + + // Short-circuit, definitely not Berry because version number says we're Yarn. + if !matches { + return false, nil + } + + // We're Berry! + + // Check for supported configuration. + isNMLinker, err := util.IsNMLinker(projectDirectory.ToStringDuringMigration()) + + if err != nil { + // Failed to read the linker state, so we treat an unknown configuration as a failure. + return false, fmt.Errorf("could not check if yarn is using nm-linker: %w", err) + } else if !isNMLinker { + // Not using nm-linker, so unsupported configuration. + return false, fmt.Errorf("only yarn nm-linker is supported") + } + + // Berry, supported configuration. + return true, nil + }, + + UnmarshalLockfile: func(_rootPackageJSON *fs.PackageJSON, contents []byte) (lockfile.Lockfile, error) { + return lockfile.DecodeBerryLockfile(contents) + }, + + prunePatches: func(pkgJSON *fs.PackageJSON, patches []turbopath.AnchoredUnixPath) error { + pkgJSON.Mu.Lock() + defer pkgJSON.Mu.Unlock() + + keysToDelete := []string{} + resolutions, ok := pkgJSON.RawJSON["resolutions"].(map[string]interface{}) + if !ok { + return fmt.Errorf("Invalid structure for resolutions field in package.json") + } + + for dependency, untypedPatch := range resolutions { + inPatches := false + patch, ok := untypedPatch.(string) + if !ok { + return fmt.Errorf("Expected value of %s in package.json to be a string, got %v", dependency, untypedPatch) + } + + for _, wantedPatch := range patches { + if strings.HasSuffix(patch, wantedPatch.ToString()) { + inPatches = true + break + } + } + + // We only want to delete unused patches as they are the only ones that throw if unused + if !inPatches && strings.HasSuffix(patch, ".patch") { + keysToDelete = append(keysToDelete, dependency) + } + } + + for _, key := range keysToDelete { + delete(resolutions, key) + } + + return nil + }, +} |
