aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/cli/internal/packagemanager
diff options
context:
space:
mode:
Diffstat (limited to 'cli/internal/packagemanager')
-rw-r--r--cli/internal/packagemanager/berry.go156
-rw-r--r--cli/internal/packagemanager/fixtures/package.json7
-rw-r--r--cli/internal/packagemanager/fixtures/pnpm-patches.json11
-rw-r--r--cli/internal/packagemanager/fixtures/pnpm-workspace.yaml3
-rw-r--r--cli/internal/packagemanager/infer_root.go146
-rw-r--r--cli/internal/packagemanager/infer_root_test.go347
-rw-r--r--cli/internal/packagemanager/npm.go59
-rw-r--r--cli/internal/packagemanager/packagemanager.go197
-rw-r--r--cli/internal/packagemanager/packagemanager_test.go411
-rw-r--r--cli/internal/packagemanager/pnpm.go168
-rw-r--r--cli/internal/packagemanager/pnpm6.go63
-rw-r--r--cli/internal/packagemanager/pnpm_test.go57
-rw-r--r--cli/internal/packagemanager/yarn.go116
13 files changed, 0 insertions, 1741 deletions
diff --git a/cli/internal/packagemanager/berry.go b/cli/internal/packagemanager/berry.go
deleted file mode 100644
index d6264b1..0000000
--- a/cli/internal/packagemanager/berry.go
+++ /dev/null
@@ -1,156 +0,0 @@
-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
- },
-}
diff --git a/cli/internal/packagemanager/fixtures/package.json b/cli/internal/packagemanager/fixtures/package.json
deleted file mode 100644
index 6b27f7c..0000000
--- a/cli/internal/packagemanager/fixtures/package.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "name": "fixture",
- "workspaces": [
- "apps/*",
- "packages/*"
- ]
-}
diff --git a/cli/internal/packagemanager/fixtures/pnpm-patches.json b/cli/internal/packagemanager/fixtures/pnpm-patches.json
deleted file mode 100644
index f772bc3..0000000
--- a/cli/internal/packagemanager/fixtures/pnpm-patches.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "name": "turborepo-prune-removes-patched",
- "version": "1.0.0",
- "packageManager": "pnpm@7.15.0",
- "workspaces": ["packages/*"],
- "pnpm": {
- "patchedDependencies": {
- "is-odd@3.0.1": "patches/is-odd@3.0.1.patch"
- }
- }
-}
diff --git a/cli/internal/packagemanager/fixtures/pnpm-workspace.yaml b/cli/internal/packagemanager/fixtures/pnpm-workspace.yaml
deleted file mode 100644
index 7fbb770..0000000
--- a/cli/internal/packagemanager/fixtures/pnpm-workspace.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-packages:
- - "packages/*"
- - "!packages/skip"
diff --git a/cli/internal/packagemanager/infer_root.go b/cli/internal/packagemanager/infer_root.go
deleted file mode 100644
index 7920f12..0000000
--- a/cli/internal/packagemanager/infer_root.go
+++ /dev/null
@@ -1,146 +0,0 @@
-package packagemanager
-
-import (
- "path/filepath"
-
- "github.com/vercel/turbo/cli/internal/doublestar"
- "github.com/vercel/turbo/cli/internal/turbopath"
-)
-
-// PackageType represents the mode in which turbo is running.
-type PackageType string
-
-const (
- // Single is for single-package mode.
- Single PackageType = "single"
- // Multi is for monorepo mode.
- Multi PackageType = "multi"
-)
-
-func candidateDirectoryWorkspaceGlobs(directory turbopath.AbsoluteSystemPath) []string {
- packageManagers := []PackageManager{
- nodejsNpm,
- nodejsPnpm,
- }
-
- for _, pm := range packageManagers {
- globs, err := pm.getWorkspaceGlobs(directory)
- if err != nil {
- // Try the other package manager workspace formats.
- continue
- }
-
- return globs
- }
-
- return nil
-}
-
-func isOneOfTheWorkspaces(globs []string, nearestPackageJSONDir turbopath.AbsoluteSystemPath, currentPackageJSONDir turbopath.AbsoluteSystemPath) bool {
- for _, glob := range globs {
- globpattern := currentPackageJSONDir.UntypedJoin(filepath.FromSlash(glob)).ToString()
- match, _ := doublestar.PathMatch(globpattern, nearestPackageJSONDir.ToString())
- if match {
- return true
- }
- }
-
- return false
-}
-
-// InferRoot identifies which directory we should treat as the root, and which mode
-// turbo should be in when operating at that directory.
-func InferRoot(directory turbopath.AbsoluteSystemPath) (turbopath.AbsoluteSystemPath, PackageType) {
- // Go doesn't have iterators, so this is very not-elegant.
-
- // Scenarios:
- // 0. Has a turbo.json but doesn't have a peer package.json. directory + multi
- // 1. Nearest turbo.json, check peer package.json/pnpm-workspace.yaml.
- // A. Has workspaces, multi package mode.
- // B. No workspaces, single package mode.
- // 2. If no turbo.json find the closest package.json parent.
- // A. No parent package.json, default to current behavior.
- // B. Nearest package.json defines workspaces. Can't be in single-package mode, so we bail. (This could be changed in the future.)
- // 3. Closest package.json does not define workspaces. Traverse toward the root looking for package.jsons.
- // A. No parent package.json with workspaces. nearestPackageJson + single
- // B. Stop at the first one that has workspaces.
- // i. If we are one of the workspaces, directory + multi. (This could be changed in the future.)
- // ii. If we're not one of the workspaces, nearestPackageJson + single.
-
- nearestTurboJSON, findTurboJSONErr := directory.Findup("turbo.json")
- if nearestTurboJSON == "" || findTurboJSONErr != nil {
- // We didn't find a turbo.json. We're in situation 2 or 3.
-
- // Unroll the first loop for Scenario 2
- nearestPackageJSON, nearestPackageJSONErr := directory.Findup("package.json")
-
- // If we fail to find any package.json files we aren't in single package mode.
- // We let things go through our existing failure paths.
- // Scenario 2A.
- if nearestPackageJSON == "" || nearestPackageJSONErr != nil {
- return directory, Multi
- }
-
- // If we find a package.json which has workspaces we aren't in single package mode.
- // We let things go through our existing failure paths.
- // Scenario 2B.
- if candidateDirectoryWorkspaceGlobs(nearestPackageJSON.Dir()) != nil {
- // In a future world we could maybe change this behavior.
- // return nearestPackageJson.Dir(), Multi
- return directory, Multi
- }
-
- // Scenario 3.
- // Find the nearest package.json that has workspaces.
- // If found _and_ the nearestPackageJson is one of the workspaces, thatPackageJson + multi.
- // Else, nearestPackageJson + single
- cursor := nearestPackageJSON.Dir().UntypedJoin("..")
- for {
- nextPackageJSON, nextPackageJSONErr := cursor.Findup("package.json")
- if nextPackageJSON == "" || nextPackageJSONErr != nil {
- // We haven't found a parent defining workspaces.
- // So we're single package mode at nearestPackageJson.
- // Scenario 3A.
- return nearestPackageJSON.Dir(), Single
- }
-
- // Found a package.json file, see if it has workspaces.
- // Workspaces are not allowed to be recursive, so we know what to
- // return the moment we find something with workspaces.
- globs := candidateDirectoryWorkspaceGlobs(nextPackageJSON.Dir())
- if globs != nil {
- if isOneOfTheWorkspaces(globs, nearestPackageJSON.Dir(), nextPackageJSON.Dir()) {
- // If it has workspaces, and nearestPackageJson is one of them, we're multi.
- // We don't infer in this scenario.
- // Scenario 3BI.
- // TODO: return nextPackageJson.Dir(), Multi
- return directory, Multi
- }
-
- // We found a parent with workspaces, but we're not one of them.
- // We choose to operate in single package mode.
- // Scenario 3BII
- return nearestPackageJSON.Dir(), Single
- }
-
- // Loop around and see if we have another parent.
- cursor = nextPackageJSON.Dir().UntypedJoin("..")
- }
- } else {
- // If there is no sibling package.json we do no inference.
- siblingPackageJSONPath := nearestTurboJSON.Dir().UntypedJoin("package.json")
- if !siblingPackageJSONPath.Exists() {
- // We do no inference.
- // Scenario 0
- return directory, Multi
- }
-
- if candidateDirectoryWorkspaceGlobs(nearestTurboJSON.Dir()) != nil {
- // Scenario 1A.
- return nearestTurboJSON.Dir(), Multi
- }
-
- // Scenario 1B.
- return nearestTurboJSON.Dir(), Single
- }
-}
diff --git a/cli/internal/packagemanager/infer_root_test.go b/cli/internal/packagemanager/infer_root_test.go
deleted file mode 100644
index 2e37a80..0000000
--- a/cli/internal/packagemanager/infer_root_test.go
+++ /dev/null
@@ -1,347 +0,0 @@
-package packagemanager
-
-import (
- "reflect"
- "testing"
-
- "github.com/vercel/turbo/cli/internal/turbopath"
- "gotest.tools/v3/assert"
-)
-
-func TestInferRoot(t *testing.T) {
- type file struct {
- path turbopath.AnchoredSystemPath
- content []byte
- }
-
- tests := []struct {
- name string
- fs []file
- executionDirectory turbopath.AnchoredSystemPath
- rootPath turbopath.AnchoredSystemPath
- packageMode PackageType
- }{
- // Scenario 0
- {
- name: "turbo.json at current dir, no package.json",
- fs: []file{
- {path: turbopath.AnchoredUnixPath("turbo.json").ToSystemPath()},
- },
- executionDirectory: turbopath.AnchoredUnixPath("").ToSystemPath(),
- rootPath: turbopath.AnchoredUnixPath("").ToSystemPath(),
- packageMode: Multi,
- },
- {
- name: "turbo.json at parent dir, no package.json",
- fs: []file{
- {path: turbopath.AnchoredUnixPath("execution/path/subdir/.file").ToSystemPath()},
- {path: turbopath.AnchoredUnixPath("turbo.json").ToSystemPath()},
- },
- executionDirectory: turbopath.AnchoredUnixPath("execution/path/subdir").ToSystemPath(),
- // This is "no inference"
- rootPath: turbopath.AnchoredUnixPath("execution/path/subdir").ToSystemPath(),
- packageMode: Multi,
- },
- // Scenario 1A
- {
- name: "turbo.json at current dir, has package.json, has workspaces key",
- fs: []file{
- {path: turbopath.AnchoredUnixPath("turbo.json").ToSystemPath()},
- {
- path: turbopath.AnchoredUnixPath("package.json").ToSystemPath(),
- content: []byte("{ \"workspaces\": [ \"exists\" ] }"),
- },
- },
- executionDirectory: turbopath.AnchoredUnixPath("").ToSystemPath(),
- rootPath: turbopath.AnchoredUnixPath("").ToSystemPath(),
- packageMode: Multi,
- },
- {
- name: "turbo.json at parent dir, has package.json, has workspaces key",
- fs: []file{
- {path: turbopath.AnchoredUnixPath("execution/path/subdir/.file").ToSystemPath()},
- {path: turbopath.AnchoredUnixPath("turbo.json").ToSystemPath()},
- {
- path: turbopath.AnchoredUnixPath("package.json").ToSystemPath(),
- content: []byte("{ \"workspaces\": [ \"exists\" ] }"),
- },
- },
- executionDirectory: turbopath.AnchoredUnixPath("execution/path/subdir").ToSystemPath(),
- rootPath: turbopath.AnchoredUnixPath("").ToSystemPath(),
- packageMode: Multi,
- },
- {
- name: "turbo.json at parent dir, has package.json, has pnpm workspaces",
- fs: []file{
- {path: turbopath.AnchoredUnixPath("execution/path/subdir/.file").ToSystemPath()},
- {path: turbopath.AnchoredUnixPath("turbo.json").ToSystemPath()},
- {
- path: turbopath.AnchoredUnixPath("package.json").ToSystemPath(),
- content: []byte("{}"),
- },
- {
- path: turbopath.AnchoredUnixPath("pnpm-workspace.yaml").ToSystemPath(),
- content: []byte("packages:\n - docs"),
- },
- },
- executionDirectory: turbopath.AnchoredUnixPath("execution/path/subdir").ToSystemPath(),
- rootPath: turbopath.AnchoredUnixPath("").ToSystemPath(),
- packageMode: Multi,
- },
- // Scenario 1A aware of the weird thing we do for packages.
- {
- name: "turbo.json at current dir, has package.json, has packages key",
- fs: []file{
- {path: turbopath.AnchoredUnixPath("turbo.json").ToSystemPath()},
- {
- path: turbopath.AnchoredUnixPath("package.json").ToSystemPath(),
- content: []byte("{ \"packages\": [ \"exists\" ] }"),
- },
- },
- executionDirectory: turbopath.AnchoredUnixPath("").ToSystemPath(),
- rootPath: turbopath.AnchoredUnixPath("").ToSystemPath(),
- packageMode: Single,
- },
- {
- name: "turbo.json at parent dir, has package.json, has packages key",
- fs: []file{
- {path: turbopath.AnchoredUnixPath("execution/path/subdir/.file").ToSystemPath()},
- {path: turbopath.AnchoredUnixPath("turbo.json").ToSystemPath()},
- {
- path: turbopath.AnchoredUnixPath("package.json").ToSystemPath(),
- content: []byte("{ \"packages\": [ \"exists\" ] }"),
- },
- },
- executionDirectory: turbopath.AnchoredUnixPath("execution/path/subdir").ToSystemPath(),
- rootPath: turbopath.AnchoredUnixPath("").ToSystemPath(),
- packageMode: Single,
- },
- // Scenario 1A aware of the the weird thing we do for packages when both methods of specification exist.
- {
- name: "turbo.json at current dir, has package.json, has workspace and packages key",
- fs: []file{
- {path: turbopath.AnchoredUnixPath("turbo.json").ToSystemPath()},
- {
- path: turbopath.AnchoredUnixPath("package.json").ToSystemPath(),
- content: []byte("{ \"workspaces\": [ \"clobbered\" ], \"packages\": [ \"exists\" ] }"),
- },
- },
- executionDirectory: turbopath.AnchoredUnixPath("").ToSystemPath(),
- rootPath: turbopath.AnchoredUnixPath("").ToSystemPath(),
- packageMode: Multi,
- },
- {
- name: "turbo.json at parent dir, has package.json, has workspace and packages key",
- fs: []file{
- {path: turbopath.AnchoredUnixPath("execution/path/subdir/.file").ToSystemPath()},
- {path: turbopath.AnchoredUnixPath("turbo.json").ToSystemPath()},
- {
- path: turbopath.AnchoredUnixPath("package.json").ToSystemPath(),
- content: []byte("{ \"workspaces\": [ \"clobbered\" ], \"packages\": [ \"exists\" ] }"),
- },
- },
- executionDirectory: turbopath.AnchoredUnixPath("execution/path/subdir").ToSystemPath(),
- rootPath: turbopath.AnchoredUnixPath("").ToSystemPath(),
- packageMode: Multi,
- },
- // Scenario 1B
- {
- name: "turbo.json at current dir, has package.json, no workspaces",
- fs: []file{
- {path: turbopath.AnchoredUnixPath("turbo.json").ToSystemPath()},
- {
- path: turbopath.AnchoredUnixPath("package.json").ToSystemPath(),
- content: []byte("{}"),
- },
- },
- executionDirectory: turbopath.AnchoredUnixPath("").ToSystemPath(),
- rootPath: turbopath.AnchoredUnixPath("").ToSystemPath(),
- packageMode: Single,
- },
- {
- name: "turbo.json at parent dir, has package.json, no workspaces",
- fs: []file{
- {path: turbopath.AnchoredUnixPath("execution/path/subdir/.file").ToSystemPath()},
- {path: turbopath.AnchoredUnixPath("turbo.json").ToSystemPath()},
- {
- path: turbopath.AnchoredUnixPath("package.json").ToSystemPath(),
- content: []byte("{}"),
- },
- },
- executionDirectory: turbopath.AnchoredUnixPath("execution/path/subdir").ToSystemPath(),
- rootPath: turbopath.AnchoredUnixPath("").ToSystemPath(),
- packageMode: Single,
- },
- {
- name: "turbo.json at parent dir, has package.json, no workspaces, includes pnpm",
- fs: []file{
- {path: turbopath.AnchoredUnixPath("execution/path/subdir/.file").ToSystemPath()},
- {path: turbopath.AnchoredUnixPath("turbo.json").ToSystemPath()},
- {
- path: turbopath.AnchoredUnixPath("package.json").ToSystemPath(),
- content: []byte("{}"),
- },
- {
- path: turbopath.AnchoredUnixPath("pnpm-workspace.yaml").ToSystemPath(),
- content: []byte(""),
- },
- },
- executionDirectory: turbopath.AnchoredUnixPath("execution/path/subdir").ToSystemPath(),
- rootPath: turbopath.AnchoredUnixPath("").ToSystemPath(),
- packageMode: Single,
- },
- // Scenario 2A
- {
- name: "no turbo.json, no package.json at current",
- fs: []file{},
- executionDirectory: turbopath.AnchoredUnixPath("").ToSystemPath(),
- rootPath: turbopath.AnchoredUnixPath("").ToSystemPath(),
- packageMode: Multi,
- },
- {
- name: "no turbo.json, no package.json at parent",
- fs: []file{
- {path: turbopath.AnchoredUnixPath("execution/path/subdir/.file").ToSystemPath()},
- },
- executionDirectory: turbopath.AnchoredUnixPath("execution/path/subdir").ToSystemPath(),
- rootPath: turbopath.AnchoredUnixPath("execution/path/subdir").ToSystemPath(),
- packageMode: Multi,
- },
- // Scenario 2B
- {
- name: "no turbo.json, has package.json with workspaces at current",
- fs: []file{
- {
- path: turbopath.AnchoredUnixPath("package.json").ToSystemPath(),
- content: []byte("{ \"workspaces\": [ \"exists\" ] }"),
- },
- },
- executionDirectory: turbopath.AnchoredUnixPath("").ToSystemPath(),
- rootPath: turbopath.AnchoredUnixPath("").ToSystemPath(),
- packageMode: Multi,
- },
- {
- name: "no turbo.json, has package.json with workspaces at parent",
- fs: []file{
- {path: turbopath.AnchoredUnixPath("execution/path/subdir/.file").ToSystemPath()},
- {
- path: turbopath.AnchoredUnixPath("package.json").ToSystemPath(),
- content: []byte("{ \"workspaces\": [ \"exists\" ] }"),
- },
- },
- executionDirectory: turbopath.AnchoredUnixPath("execution/path/subdir").ToSystemPath(),
- rootPath: turbopath.AnchoredUnixPath("execution/path/subdir").ToSystemPath(),
- packageMode: Multi,
- },
- {
- name: "no turbo.json, has package.json with pnpm workspaces at parent",
- fs: []file{
- {path: turbopath.AnchoredUnixPath("execution/path/subdir/.file").ToSystemPath()},
- {
- path: turbopath.AnchoredUnixPath("package.json").ToSystemPath(),
- content: []byte("{ \"workspaces\": [ \"exists\" ] }"),
- },
- {
- path: turbopath.AnchoredUnixPath("pnpm-workspace.yaml").ToSystemPath(),
- content: []byte("packages:\n - docs"),
- },
- },
- executionDirectory: turbopath.AnchoredUnixPath("execution/path/subdir").ToSystemPath(),
- rootPath: turbopath.AnchoredUnixPath("execution/path/subdir").ToSystemPath(),
- packageMode: Multi,
- },
- // Scenario 3A
- {
- name: "no turbo.json, lots of package.json files but no workspaces",
- fs: []file{
- {
- path: turbopath.AnchoredUnixPath("package.json").ToSystemPath(),
- content: []byte("{}"),
- },
- {
- path: turbopath.AnchoredUnixPath("one/package.json").ToSystemPath(),
- content: []byte("{}"),
- },
- {
- path: turbopath.AnchoredUnixPath("one/two/package.json").ToSystemPath(),
- content: []byte("{}"),
- },
- {
- path: turbopath.AnchoredUnixPath("one/two/three/package.json").ToSystemPath(),
- content: []byte("{}"),
- },
- },
- executionDirectory: turbopath.AnchoredUnixPath("one/two/three").ToSystemPath(),
- rootPath: turbopath.AnchoredUnixPath("one/two/three").ToSystemPath(),
- packageMode: Single,
- },
- // Scenario 3BI
- {
- name: "no turbo.json, lots of package.json files, and a workspace at the root that matches execution directory",
- fs: []file{
- {
- path: turbopath.AnchoredUnixPath("package.json").ToSystemPath(),
- content: []byte("{ \"workspaces\": [ \"one/two/three\" ] }"),
- },
- {
- path: turbopath.AnchoredUnixPath("one/package.json").ToSystemPath(),
- content: []byte("{}"),
- },
- {
- path: turbopath.AnchoredUnixPath("one/two/package.json").ToSystemPath(),
- content: []byte("{}"),
- },
- {
- path: turbopath.AnchoredUnixPath("one/two/three/package.json").ToSystemPath(),
- content: []byte("{}"),
- },
- },
- executionDirectory: turbopath.AnchoredUnixPath("one/two/three").ToSystemPath(),
- rootPath: turbopath.AnchoredUnixPath("one/two/three").ToSystemPath(),
- packageMode: Multi,
- },
- // Scenario 3BII
- {
- name: "no turbo.json, lots of package.json files, and a workspace at the root that matches execution directory",
- fs: []file{
- {
- path: turbopath.AnchoredUnixPath("package.json").ToSystemPath(),
- content: []byte("{ \"workspaces\": [ \"does-not-exist\" ] }"),
- },
- {
- path: turbopath.AnchoredUnixPath("one/package.json").ToSystemPath(),
- content: []byte("{}"),
- },
- {
- path: turbopath.AnchoredUnixPath("one/two/package.json").ToSystemPath(),
- content: []byte("{}"),
- },
- {
- path: turbopath.AnchoredUnixPath("one/two/three/package.json").ToSystemPath(),
- content: []byte("{}"),
- },
- },
- executionDirectory: turbopath.AnchoredUnixPath("one/two/three").ToSystemPath(),
- rootPath: turbopath.AnchoredUnixPath("one/two/three").ToSystemPath(),
- packageMode: Single,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- fsRoot := turbopath.AbsoluteSystemPath(t.TempDir())
- for _, file := range tt.fs {
- path := file.path.RestoreAnchor(fsRoot)
- assert.NilError(t, path.Dir().MkdirAll(0777))
- assert.NilError(t, path.WriteFile(file.content, 0777))
- }
-
- turboRoot, packageMode := InferRoot(tt.executionDirectory.RestoreAnchor(fsRoot))
- if !reflect.DeepEqual(turboRoot, tt.rootPath.RestoreAnchor(fsRoot)) {
- t.Errorf("InferRoot() turboRoot = %v, want %v", turboRoot, tt.rootPath.RestoreAnchor(fsRoot))
- }
- if packageMode != tt.packageMode {
- t.Errorf("InferRoot() packageMode = %v, want %v", packageMode, tt.packageMode)
- }
- })
- }
-}
diff --git a/cli/internal/packagemanager/npm.go b/cli/internal/packagemanager/npm.go
deleted file mode 100644
index ce2eb8c..0000000
--- a/cli/internal/packagemanager/npm.go
+++ /dev/null
@@ -1,59 +0,0 @@
-package packagemanager
-
-import (
- "fmt"
-
- "github.com/vercel/turbo/cli/internal/fs"
- "github.com/vercel/turbo/cli/internal/lockfile"
- "github.com/vercel/turbo/cli/internal/turbopath"
-)
-
-var nodejsNpm = PackageManager{
- Name: "nodejs-npm",
- Slug: "npm",
- Command: "npm",
- Specfile: "package.json",
- Lockfile: "package-lock.json",
- 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, fmt.Errorf("package.json: no workspaces found. Turborepo requires npm 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:
- // function: https://github.com/npm/map-workspaces/blob/a46503543982cb35f51cc2d6253d4dcc6bca9b32/lib/index.js#L73
- // key code: https://github.com/npm/map-workspaces/blob/a46503543982cb35f51cc2d6253d4dcc6bca9b32/lib/index.js#L90-L96
- // call site: https://github.com/npm/cli/blob/7a858277171813b37d46a032e49db44c8624f78f/lib/workspaces/get-workspaces.js#L14
- return []string{
- "**/node_modules/**",
- }, nil
- },
-
- Matches: func(manager string, version string) (bool, error) {
- return manager == "npm", nil
- },
-
- detect: func(projectDirectory turbopath.AbsoluteSystemPath, packageManager *PackageManager) (bool, error) {
- specfileExists := projectDirectory.UntypedJoin(packageManager.Specfile).FileExists()
- lockfileExists := projectDirectory.UntypedJoin(packageManager.Lockfile).FileExists()
-
- return (specfileExists && lockfileExists), nil
- },
-
- canPrune: func(cwd turbopath.AbsoluteSystemPath) (bool, error) {
- return true, nil
- },
-
- UnmarshalLockfile: func(_rootPackageJSON *fs.PackageJSON, contents []byte) (lockfile.Lockfile, error) {
- return lockfile.DecodeNpmLockfile(contents)
- },
-}
diff --git a/cli/internal/packagemanager/packagemanager.go b/cli/internal/packagemanager/packagemanager.go
deleted file mode 100644
index dc5b966..0000000
--- a/cli/internal/packagemanager/packagemanager.go
+++ /dev/null
@@ -1,197 +0,0 @@
-// Adapted from https://github.com/replit/upm
-// Copyright (c) 2019 Neoreason d/b/a Repl.it. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package packagemanager
-
-import (
- "fmt"
- "path/filepath"
- "regexp"
- "strings"
-
- "github.com/pkg/errors"
- "github.com/vercel/turbo/cli/internal/fs"
- "github.com/vercel/turbo/cli/internal/globby"
- "github.com/vercel/turbo/cli/internal/lockfile"
- "github.com/vercel/turbo/cli/internal/turbopath"
- "github.com/vercel/turbo/cli/internal/util"
-)
-
-// PackageManager is an abstraction across package managers
-type PackageManager struct {
- // The descriptive name of the Package Manager.
- Name string
-
- // The unique identifier of the Package Manager.
- Slug string
-
- // The command used to invoke the Package Manager.
- Command string
-
- // The location of the package spec file used by the Package Manager.
- Specfile string
-
- // The location of the package lock file used by the Package Manager.
- Lockfile string
-
- // The directory in which package assets are stored by the Package Manager.
- PackageDir string
-
- // The location of the file that defines the workspace. Empty if workspaces defined in package.json
- WorkspaceConfigurationPath string
-
- // The separator that the Package Manger uses to identify arguments that
- // should be passed through to the underlying script.
- ArgSeparator []string
-
- // Return the list of workspace glob
- getWorkspaceGlobs func(rootpath turbopath.AbsoluteSystemPath) ([]string, error)
-
- // Return the list of workspace ignore globs
- getWorkspaceIgnores func(pm PackageManager, rootpath turbopath.AbsoluteSystemPath) ([]string, error)
-
- // Detect if Turbo knows how to produce a pruned workspace for the project
- canPrune func(cwd turbopath.AbsoluteSystemPath) (bool, error)
-
- // Test a manager and version tuple to see if it is the Package Manager.
- Matches func(manager string, version string) (bool, error)
-
- // Detect if the project is using the Package Manager by inspecting the system.
- detect func(projectDirectory turbopath.AbsoluteSystemPath, packageManager *PackageManager) (bool, error)
-
- // Read a lockfile for a given package manager
- UnmarshalLockfile func(rootPackageJSON *fs.PackageJSON, contents []byte) (lockfile.Lockfile, error)
-
- // Prune the given pkgJSON to only include references to the given patches
- prunePatches func(pkgJSON *fs.PackageJSON, patches []turbopath.AnchoredUnixPath) error
-}
-
-var packageManagers = []PackageManager{
- nodejsYarn,
- nodejsBerry,
- nodejsNpm,
- nodejsPnpm,
- nodejsPnpm6,
-}
-
-var (
- packageManagerPattern = `(npm|pnpm|yarn)@(\d+)\.\d+\.\d+(-.+)?`
- packageManagerRegex = regexp.MustCompile(packageManagerPattern)
-)
-
-// ParsePackageManagerString takes a package manager version string parses it into consituent components
-func ParsePackageManagerString(packageManager string) (manager string, version string, err error) {
- match := packageManagerRegex.FindString(packageManager)
- if len(match) == 0 {
- return "", "", fmt.Errorf("We could not parse packageManager field in package.json, expected: %s, received: %s", packageManagerPattern, packageManager)
- }
-
- return strings.Split(match, "@")[0], strings.Split(match, "@")[1], nil
-}
-
-// GetPackageManager attempts all methods for identifying the package manager in use.
-func GetPackageManager(projectDirectory turbopath.AbsoluteSystemPath, pkg *fs.PackageJSON) (packageManager *PackageManager, err error) {
- result, _ := readPackageManager(pkg)
- if result != nil {
- return result, nil
- }
-
- return detectPackageManager(projectDirectory)
-}
-
-// readPackageManager attempts to read the package manager from the package.json.
-func readPackageManager(pkg *fs.PackageJSON) (packageManager *PackageManager, err error) {
- if pkg.PackageManager != "" {
- manager, version, err := ParsePackageManagerString(pkg.PackageManager)
- if err != nil {
- return nil, err
- }
-
- for _, packageManager := range packageManagers {
- isResponsible, err := packageManager.Matches(manager, version)
- if isResponsible && (err == nil) {
- return &packageManager, nil
- }
- }
- }
-
- return nil, errors.New(util.Sprintf("We did not find a package manager specified in your root package.json. Please set the \"packageManager\" property in your root package.json (${UNDERLINE}https://nodejs.org/api/packages.html#packagemanager)${RESET} or run `npx @turbo/codemod add-package-manager` in the root of your monorepo."))
-}
-
-// detectPackageManager attempts to detect the package manager by inspecting the project directory state.
-func detectPackageManager(projectDirectory turbopath.AbsoluteSystemPath) (packageManager *PackageManager, err error) {
- for _, packageManager := range packageManagers {
- isResponsible, err := packageManager.detect(projectDirectory, &packageManager)
- if err != nil {
- return nil, err
- }
- if isResponsible {
- return &packageManager, nil
- }
- }
-
- return nil, errors.New(util.Sprintf("We did not detect an in-use package manager for your project. Please set the \"packageManager\" property in your root package.json (${UNDERLINE}https://nodejs.org/api/packages.html#packagemanager)${RESET} or run `npx @turbo/codemod add-package-manager` in the root of your monorepo."))
-}
-
-// GetWorkspaces returns the list of package.json files for the current repository.
-func (pm PackageManager) GetWorkspaces(rootpath turbopath.AbsoluteSystemPath) ([]string, error) {
- globs, err := pm.getWorkspaceGlobs(rootpath)
- if err != nil {
- return nil, err
- }
-
- justJsons := make([]string, len(globs))
- for i, space := range globs {
- justJsons[i] = filepath.Join(space, "package.json")
- }
-
- ignores, err := pm.getWorkspaceIgnores(pm, rootpath)
- if err != nil {
- return nil, err
- }
-
- f, err := globby.GlobFiles(rootpath.ToStringDuringMigration(), justJsons, ignores)
- if err != nil {
- return nil, err
- }
-
- return f, nil
-}
-
-// GetWorkspaceIgnores returns an array of globs not to search for workspaces.
-func (pm PackageManager) GetWorkspaceIgnores(rootpath turbopath.AbsoluteSystemPath) ([]string, error) {
- return pm.getWorkspaceIgnores(pm, rootpath)
-}
-
-// CanPrune returns if turbo can produce a pruned workspace. Can error if fs issues occur
-func (pm PackageManager) CanPrune(projectDirectory turbopath.AbsoluteSystemPath) (bool, error) {
- if pm.canPrune != nil {
- return pm.canPrune(projectDirectory)
- }
- return false, nil
-}
-
-// ReadLockfile will read the applicable lockfile into memory
-func (pm PackageManager) ReadLockfile(projectDirectory turbopath.AbsoluteSystemPath, rootPackageJSON *fs.PackageJSON) (lockfile.Lockfile, error) {
- if pm.UnmarshalLockfile == nil {
- return nil, nil
- }
- contents, err := projectDirectory.UntypedJoin(pm.Lockfile).ReadFile()
- if err != nil {
- return nil, fmt.Errorf("reading %s: %w", pm.Lockfile, err)
- }
- lf, err := pm.UnmarshalLockfile(rootPackageJSON, contents)
- if err != nil {
- return nil, errors.Wrapf(err, "error in %v", pm.Lockfile)
- }
- return lf, nil
-}
-
-// PrunePatchedPackages will alter the provided pkgJSON to only reference the provided patches
-func (pm PackageManager) PrunePatchedPackages(pkgJSON *fs.PackageJSON, patches []turbopath.AnchoredUnixPath) error {
- if pm.prunePatches != nil {
- return pm.prunePatches(pkgJSON, patches)
- }
- return nil
-}
diff --git a/cli/internal/packagemanager/packagemanager_test.go b/cli/internal/packagemanager/packagemanager_test.go
deleted file mode 100644
index a5dc472..0000000
--- a/cli/internal/packagemanager/packagemanager_test.go
+++ /dev/null
@@ -1,411 +0,0 @@
-package packagemanager
-
-import (
- "os"
- "path/filepath"
- "reflect"
- "sort"
- "testing"
-
- "github.com/vercel/turbo/cli/internal/fs"
- "github.com/vercel/turbo/cli/internal/turbopath"
- "gotest.tools/v3/assert"
-)
-
-func TestParsePackageManagerString(t *testing.T) {
- tests := []struct {
- name string
- packageManager string
- wantManager string
- wantVersion string
- wantErr bool
- }{
- {
- name: "errors with a tag version",
- packageManager: "npm@latest",
- wantManager: "",
- wantVersion: "",
- wantErr: true,
- },
- {
- name: "errors with no version",
- packageManager: "npm",
- wantManager: "",
- wantVersion: "",
- wantErr: true,
- },
- {
- name: "requires fully-qualified semver versions (one digit)",
- packageManager: "npm@1",
- wantManager: "",
- wantVersion: "",
- wantErr: true,
- },
- {
- name: "requires fully-qualified semver versions (two digits)",
- packageManager: "npm@1.2",
- wantManager: "",
- wantVersion: "",
- wantErr: true,
- },
- {
- name: "supports custom labels",
- packageManager: "npm@1.2.3-alpha.1",
- wantManager: "npm",
- wantVersion: "1.2.3-alpha.1",
- wantErr: false,
- },
- {
- name: "only supports specified package managers",
- packageManager: "pip@1.2.3",
- wantManager: "",
- wantVersion: "",
- wantErr: true,
- },
- {
- name: "supports npm",
- packageManager: "npm@0.0.1",
- wantManager: "npm",
- wantVersion: "0.0.1",
- wantErr: false,
- },
- {
- name: "supports pnpm",
- packageManager: "pnpm@0.0.1",
- wantManager: "pnpm",
- wantVersion: "0.0.1",
- wantErr: false,
- },
- {
- name: "supports yarn",
- packageManager: "yarn@111.0.1",
- wantManager: "yarn",
- wantVersion: "111.0.1",
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- gotManager, gotVersion, err := ParsePackageManagerString(tt.packageManager)
- if (err != nil) != tt.wantErr {
- t.Errorf("ParsePackageManagerString() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- if gotManager != tt.wantManager {
- t.Errorf("ParsePackageManagerString() got manager = %v, want manager %v", gotManager, tt.wantManager)
- }
- if gotVersion != tt.wantVersion {
- t.Errorf("ParsePackageManagerString() got version = %v, want version %v", gotVersion, tt.wantVersion)
- }
- })
- }
-}
-
-func TestGetPackageManager(t *testing.T) {
- cwdRaw, err := os.Getwd()
- assert.NilError(t, err, "os.Getwd")
- cwd, err := fs.GetCwd(cwdRaw)
- assert.NilError(t, err, "GetCwd")
- tests := []struct {
- name string
- projectDirectory turbopath.AbsoluteSystemPath
- pkg *fs.PackageJSON
- want string
- wantErr bool
- }{
- {
- name: "finds npm from a package manager string",
- projectDirectory: cwd,
- pkg: &fs.PackageJSON{PackageManager: "npm@1.2.3"},
- want: "nodejs-npm",
- wantErr: false,
- },
- {
- name: "finds pnpm6 from a package manager string",
- projectDirectory: cwd,
- pkg: &fs.PackageJSON{PackageManager: "pnpm@1.2.3"},
- want: "nodejs-pnpm6",
- wantErr: false,
- },
- {
- name: "finds pnpm from a package manager string",
- projectDirectory: cwd,
- pkg: &fs.PackageJSON{PackageManager: "pnpm@7.8.9"},
- want: "nodejs-pnpm",
- wantErr: false,
- },
- {
- name: "finds yarn from a package manager string",
- projectDirectory: cwd,
- pkg: &fs.PackageJSON{PackageManager: "yarn@1.2.3"},
- want: "nodejs-yarn",
- wantErr: false,
- },
- {
- name: "finds berry from a package manager string",
- projectDirectory: cwd,
- pkg: &fs.PackageJSON{PackageManager: "yarn@2.3.4"},
- want: "nodejs-berry",
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- gotPackageManager, err := GetPackageManager(tt.projectDirectory, tt.pkg)
- if (err != nil) != tt.wantErr {
- t.Errorf("GetPackageManager() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- if gotPackageManager.Name != tt.want {
- t.Errorf("GetPackageManager() = %v, want %v", gotPackageManager.Name, tt.want)
- }
- })
- }
-}
-
-func Test_readPackageManager(t *testing.T) {
- tests := []struct {
- name string
- pkg *fs.PackageJSON
- want string
- wantErr bool
- }{
- {
- name: "finds npm from a package manager string",
- pkg: &fs.PackageJSON{PackageManager: "npm@1.2.3"},
- want: "nodejs-npm",
- wantErr: false,
- },
- {
- name: "finds pnpm6 from a package manager string",
- pkg: &fs.PackageJSON{PackageManager: "pnpm@1.2.3"},
- want: "nodejs-pnpm6",
- wantErr: false,
- },
- {
- name: "finds pnpm from a package manager string",
- pkg: &fs.PackageJSON{PackageManager: "pnpm@7.8.9"},
- want: "nodejs-pnpm",
- wantErr: false,
- },
- {
- name: "finds yarn from a package manager string",
- pkg: &fs.PackageJSON{PackageManager: "yarn@1.2.3"},
- want: "nodejs-yarn",
- wantErr: false,
- },
- {
- name: "finds berry from a package manager string",
- pkg: &fs.PackageJSON{PackageManager: "yarn@2.3.4"},
- want: "nodejs-berry",
- wantErr: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- gotPackageManager, err := readPackageManager(tt.pkg)
- if (err != nil) != tt.wantErr {
- t.Errorf("readPackageManager() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- if gotPackageManager.Name != tt.want {
- t.Errorf("readPackageManager() = %v, want %v", gotPackageManager.Name, tt.want)
- }
- })
- }
-}
-
-func Test_GetWorkspaces(t *testing.T) {
- type test struct {
- name string
- pm PackageManager
- rootPath turbopath.AbsoluteSystemPath
- want []string
- wantErr bool
- }
-
- cwd, _ := os.Getwd()
-
- repoRoot, err := fs.GetCwd(cwd)
- assert.NilError(t, err, "GetCwd")
- rootPath := map[string]turbopath.AbsoluteSystemPath{
- "nodejs-npm": repoRoot.UntypedJoin("../../../examples/with-yarn"),
- "nodejs-berry": repoRoot.UntypedJoin("../../../examples/with-yarn"),
- "nodejs-yarn": repoRoot.UntypedJoin("../../../examples/with-yarn"),
- "nodejs-pnpm": repoRoot.UntypedJoin("../../../examples/basic"),
- "nodejs-pnpm6": repoRoot.UntypedJoin("../../../examples/basic"),
- }
-
- want := map[string][]string{
- "nodejs-npm": {
- filepath.ToSlash(filepath.Join(cwd, "../../../examples/with-yarn/apps/docs/package.json")),
- filepath.ToSlash(filepath.Join(cwd, "../../../examples/with-yarn/apps/web/package.json")),
- filepath.ToSlash(filepath.Join(cwd, "../../../examples/with-yarn/packages/eslint-config-custom/package.json")),
- filepath.ToSlash(filepath.Join(cwd, "../../../examples/with-yarn/packages/tsconfig/package.json")),
- filepath.ToSlash(filepath.Join(cwd, "../../../examples/with-yarn/packages/ui/package.json")),
- },
- "nodejs-berry": {
- filepath.ToSlash(filepath.Join(cwd, "../../../examples/with-yarn/apps/docs/package.json")),
- filepath.ToSlash(filepath.Join(cwd, "../../../examples/with-yarn/apps/web/package.json")),
- filepath.ToSlash(filepath.Join(cwd, "../../../examples/with-yarn/packages/eslint-config-custom/package.json")),
- filepath.ToSlash(filepath.Join(cwd, "../../../examples/with-yarn/packages/tsconfig/package.json")),
- filepath.ToSlash(filepath.Join(cwd, "../../../examples/with-yarn/packages/ui/package.json")),
- },
- "nodejs-yarn": {
- filepath.ToSlash(filepath.Join(cwd, "../../../examples/with-yarn/apps/docs/package.json")),
- filepath.ToSlash(filepath.Join(cwd, "../../../examples/with-yarn/apps/web/package.json")),
- filepath.ToSlash(filepath.Join(cwd, "../../../examples/with-yarn/packages/eslint-config-custom/package.json")),
- filepath.ToSlash(filepath.Join(cwd, "../../../examples/with-yarn/packages/tsconfig/package.json")),
- filepath.ToSlash(filepath.Join(cwd, "../../../examples/with-yarn/packages/ui/package.json")),
- },
- "nodejs-pnpm": {
- filepath.ToSlash(filepath.Join(cwd, "../../../examples/basic/apps/docs/package.json")),
- filepath.ToSlash(filepath.Join(cwd, "../../../examples/basic/apps/web/package.json")),
- filepath.ToSlash(filepath.Join(cwd, "../../../examples/basic/packages/eslint-config-custom/package.json")),
- filepath.ToSlash(filepath.Join(cwd, "../../../examples/basic/packages/tsconfig/package.json")),
- filepath.ToSlash(filepath.Join(cwd, "../../../examples/basic/packages/ui/package.json")),
- },
- "nodejs-pnpm6": {
- filepath.ToSlash(filepath.Join(cwd, "../../../examples/basic/apps/docs/package.json")),
- filepath.ToSlash(filepath.Join(cwd, "../../../examples/basic/apps/web/package.json")),
- filepath.ToSlash(filepath.Join(cwd, "../../../examples/basic/packages/eslint-config-custom/package.json")),
- filepath.ToSlash(filepath.Join(cwd, "../../../examples/basic/packages/tsconfig/package.json")),
- filepath.ToSlash(filepath.Join(cwd, "../../../examples/basic/packages/ui/package.json")),
- },
- }
-
- tests := make([]test, len(packageManagers))
- for i, packageManager := range packageManagers {
- tests[i] = test{
- name: packageManager.Name,
- pm: packageManager,
- rootPath: rootPath[packageManager.Name],
- want: want[packageManager.Name],
- wantErr: false,
- }
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- gotWorkspaces, err := tt.pm.GetWorkspaces(tt.rootPath)
-
- gotToSlash := make([]string, len(gotWorkspaces))
- for index, workspace := range gotWorkspaces {
- gotToSlash[index] = filepath.ToSlash(workspace)
- }
-
- if (err != nil) != tt.wantErr {
- t.Errorf("GetWorkspaces() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- sort.Strings(gotToSlash)
- if !reflect.DeepEqual(gotToSlash, tt.want) {
- t.Errorf("GetWorkspaces() = %v, want %v", gotToSlash, tt.want)
- }
- })
- }
-}
-
-func Test_GetWorkspaceIgnores(t *testing.T) {
- type test struct {
- name string
- pm PackageManager
- rootPath turbopath.AbsoluteSystemPath
- want []string
- wantErr bool
- }
-
- cwdRaw, err := os.Getwd()
- assert.NilError(t, err, "os.Getwd")
- cwd, err := fs.GetCwd(cwdRaw)
- assert.NilError(t, err, "GetCwd")
- want := map[string][]string{
- "nodejs-npm": {"**/node_modules/**"},
- "nodejs-berry": {"**/node_modules", "**/.git", "**/.yarn"},
- "nodejs-yarn": {"apps/*/node_modules/**", "packages/*/node_modules/**"},
- "nodejs-pnpm": {"**/node_modules/**", "**/bower_components/**", "packages/skip"},
- "nodejs-pnpm6": {"**/node_modules/**", "**/bower_components/**", "packages/skip"},
- }
-
- tests := make([]test, len(packageManagers))
- for i, packageManager := range packageManagers {
- tests[i] = test{
- name: packageManager.Name,
- pm: packageManager,
- rootPath: cwd.UntypedJoin("fixtures"),
- want: want[packageManager.Name],
- wantErr: false,
- }
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- gotWorkspaceIgnores, err := tt.pm.GetWorkspaceIgnores(tt.rootPath)
-
- gotToSlash := make([]string, len(gotWorkspaceIgnores))
- for index, ignore := range gotWorkspaceIgnores {
- gotToSlash[index] = filepath.ToSlash(ignore)
- }
-
- if (err != nil) != tt.wantErr {
- t.Errorf("GetWorkspaceIgnores() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- if !reflect.DeepEqual(gotToSlash, tt.want) {
- t.Errorf("GetWorkspaceIgnores() = %v, want %v", gotToSlash, tt.want)
- }
- })
- }
-}
-
-func Test_CanPrune(t *testing.T) {
- type test struct {
- name string
- pm PackageManager
- rootPath turbopath.AbsoluteSystemPath
- want bool
- wantErr bool
- }
-
- type want struct {
- want bool
- wantErr bool
- }
-
- cwdRaw, err := os.Getwd()
- assert.NilError(t, err, "os.Getwd")
- cwd, err := fs.GetCwd(cwdRaw)
- assert.NilError(t, err, "GetCwd")
- wants := map[string]want{
- "nodejs-npm": {true, false},
- "nodejs-berry": {false, true},
- "nodejs-yarn": {true, false},
- "nodejs-pnpm": {true, false},
- "nodejs-pnpm6": {true, false},
- }
-
- tests := make([]test, len(packageManagers))
- for i, packageManager := range packageManagers {
- tests[i] = test{
- name: packageManager.Name,
- pm: packageManager,
- rootPath: cwd.UntypedJoin("../../../examples/with-yarn"),
- want: wants[packageManager.Name].want,
- wantErr: wants[packageManager.Name].wantErr,
- }
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- canPrune, err := tt.pm.CanPrune(tt.rootPath)
-
- if (err != nil) != tt.wantErr {
- t.Errorf("CanPrune() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- if canPrune != tt.want {
- t.Errorf("CanPrune() = %v, want %v", canPrune, tt.want)
- }
- })
- }
-}
diff --git a/cli/internal/packagemanager/pnpm.go b/cli/internal/packagemanager/pnpm.go
deleted file mode 100644
index e65a4dc..0000000
--- a/cli/internal/packagemanager/pnpm.go
+++ /dev/null
@@ -1,168 +0,0 @@
-package packagemanager
-
-import (
- "fmt"
- "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"
- "github.com/vercel/turbo/cli/internal/yaml"
-)
-
-// PnpmWorkspaces is a representation of workspace package globs found
-// in pnpm-workspace.yaml
-type PnpmWorkspaces struct {
- Packages []string `yaml:"packages,omitempty"`
-}
-
-func readPnpmWorkspacePackages(workspaceFile turbopath.AbsoluteSystemPath) ([]string, error) {
- bytes, err := workspaceFile.ReadFile()
- if err != nil {
- return nil, fmt.Errorf("%v: %w", workspaceFile, err)
- }
- var pnpmWorkspaces PnpmWorkspaces
- if err := yaml.Unmarshal(bytes, &pnpmWorkspaces); err != nil {
- return nil, fmt.Errorf("%v: %w", workspaceFile, err)
- }
- return pnpmWorkspaces.Packages, nil
-}
-
-func getPnpmWorkspaceGlobs(rootpath turbopath.AbsoluteSystemPath) ([]string, error) {
- pkgGlobs, err := readPnpmWorkspacePackages(rootpath.UntypedJoin("pnpm-workspace.yaml"))
- if err != nil {
- return nil, err
- }
-
- if len(pkgGlobs) == 0 {
- return nil, fmt.Errorf("pnpm-workspace.yaml: no packages found. Turborepo requires pnpm workspaces and thus packages to be defined in the root pnpm-workspace.yaml")
- }
-
- filteredPkgGlobs := []string{}
- for _, pkgGlob := range pkgGlobs {
- if !strings.HasPrefix(pkgGlob, "!") {
- filteredPkgGlobs = append(filteredPkgGlobs, pkgGlob)
- }
- }
- return filteredPkgGlobs, nil
-}
-
-func getPnpmWorkspaceIgnores(pm PackageManager, rootpath turbopath.AbsoluteSystemPath) ([]string, error) {
- // Matches upstream values:
- // function: https://github.com/pnpm/pnpm/blob/d99daa902442e0c8ab945143ebaf5cdc691a91eb/packages/find-packages/src/index.ts#L27
- // key code: https://github.com/pnpm/pnpm/blob/d99daa902442e0c8ab945143ebaf5cdc691a91eb/packages/find-packages/src/index.ts#L30
- // call site: https://github.com/pnpm/pnpm/blob/d99daa902442e0c8ab945143ebaf5cdc691a91eb/packages/find-workspace-packages/src/index.ts#L32-L39
- ignores := []string{
- "**/node_modules/**",
- "**/bower_components/**",
- }
- pkgGlobs, err := readPnpmWorkspacePackages(rootpath.UntypedJoin("pnpm-workspace.yaml"))
- if err != nil {
- return nil, err
- }
- for _, pkgGlob := range pkgGlobs {
- if strings.HasPrefix(pkgGlob, "!") {
- ignores = append(ignores, pkgGlob[1:])
- }
- }
- return ignores, nil
-}
-
-var nodejsPnpm = PackageManager{
- Name: "nodejs-pnpm",
- Slug: "pnpm",
- Command: "pnpm",
- Specfile: "package.json",
- Lockfile: "pnpm-lock.yaml",
- PackageDir: "node_modules",
- // pnpm v7+ changed their handling of '--'. We no longer need to pass it to pass args to
- // the script being run, and in fact doing so will cause the '--' to be passed through verbatim,
- // potentially breaking scripts that aren't expecting it.
- // We are allowed to use nil here because ArgSeparator already has a type, so it's a typed nil,
- // This could just as easily be []string{}, but the style guide says to prefer
- // nil for empty slices.
- ArgSeparator: nil,
- WorkspaceConfigurationPath: "pnpm-workspace.yaml",
-
- getWorkspaceGlobs: getPnpmWorkspaceGlobs,
-
- getWorkspaceIgnores: getPnpmWorkspaceIgnores,
-
- Matches: func(manager string, version string) (bool, error) {
- if manager != "pnpm" {
- return false, nil
- }
-
- v, err := semver.NewVersion(version)
- if err != nil {
- return false, fmt.Errorf("could not parse pnpm version: %w", err)
- }
- c, err := semver.NewConstraint(">=7.0.0")
- if err != nil {
- return false, fmt.Errorf("could not create constraint: %w", err)
- }
-
- return c.Check(v), nil
- },
-
- detect: func(projectDirectory turbopath.AbsoluteSystemPath, packageManager *PackageManager) (bool, error) {
- specfileExists := projectDirectory.UntypedJoin(packageManager.Specfile).FileExists()
- lockfileExists := projectDirectory.UntypedJoin(packageManager.Lockfile).FileExists()
-
- return (specfileExists && lockfileExists), nil
- },
-
- canPrune: func(cwd turbopath.AbsoluteSystemPath) (bool, error) {
- return true, nil
- },
-
- UnmarshalLockfile: func(_rootPackageJSON *fs.PackageJSON, contents []byte) (lockfile.Lockfile, error) {
- return lockfile.DecodePnpmLockfile(contents)
- },
-
- prunePatches: func(pkgJSON *fs.PackageJSON, patches []turbopath.AnchoredUnixPath) error {
- return pnpmPrunePatches(pkgJSON, patches)
- },
-}
-
-func pnpmPrunePatches(pkgJSON *fs.PackageJSON, patches []turbopath.AnchoredUnixPath) error {
- pkgJSON.Mu.Lock()
- defer pkgJSON.Mu.Unlock()
-
- keysToDelete := []string{}
- pnpmConfig, ok := pkgJSON.RawJSON["pnpm"].(map[string]interface{})
- if !ok {
- return fmt.Errorf("Invalid structure for pnpm field in package.json")
- }
- patchedDependencies, ok := pnpmConfig["patchedDependencies"].(map[string]interface{})
- if !ok {
- return fmt.Errorf("Invalid structure for patchedDependencies field in package.json")
- }
-
- for dependency, untypedPatch := range patchedDependencies {
- patch, ok := untypedPatch.(string)
- if !ok {
- return fmt.Errorf("Expected only strings in patchedDependencies. Got %v", untypedPatch)
- }
-
- inPatches := false
-
- for _, wantedPatch := range patches {
- if wantedPatch.ToString() == patch {
- inPatches = true
- break
- }
- }
-
- if !inPatches {
- keysToDelete = append(keysToDelete, dependency)
- }
- }
-
- for _, key := range keysToDelete {
- delete(patchedDependencies, key)
- }
-
- return nil
-}
diff --git a/cli/internal/packagemanager/pnpm6.go b/cli/internal/packagemanager/pnpm6.go
deleted file mode 100644
index 6039966..0000000
--- a/cli/internal/packagemanager/pnpm6.go
+++ /dev/null
@@ -1,63 +0,0 @@
-package packagemanager
-
-import (
- "fmt"
-
- "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"
-)
-
-// Pnpm6Workspaces is a representation of workspace package globs found
-// in pnpm-workspace.yaml
-type Pnpm6Workspaces struct {
- Packages []string `yaml:"packages,omitempty"`
-}
-
-var nodejsPnpm6 = PackageManager{
- Name: "nodejs-pnpm6",
- Slug: "pnpm",
- Command: "pnpm",
- Specfile: "package.json",
- Lockfile: "pnpm-lock.yaml",
- PackageDir: "node_modules",
- ArgSeparator: []string{"--"},
- WorkspaceConfigurationPath: "pnpm-workspace.yaml",
-
- getWorkspaceGlobs: getPnpmWorkspaceGlobs,
-
- getWorkspaceIgnores: getPnpmWorkspaceIgnores,
-
- Matches: func(manager string, version string) (bool, error) {
- if manager != "pnpm" {
- return false, nil
- }
-
- v, err := semver.NewVersion(version)
- if err != nil {
- return false, fmt.Errorf("could not parse pnpm version: %w", err)
- }
- c, err := semver.NewConstraint("<7.0.0")
- if err != nil {
- return false, fmt.Errorf("could not create constraint: %w", err)
- }
-
- return c.Check(v), nil
- },
-
- detect: func(projectDirectory turbopath.AbsoluteSystemPath, packageManager *PackageManager) (bool, error) {
- specfileExists := projectDirectory.UntypedJoin(packageManager.Specfile).FileExists()
- lockfileExists := projectDirectory.UntypedJoin(packageManager.Lockfile).FileExists()
-
- return (specfileExists && lockfileExists), nil
- },
-
- canPrune: func(cwd turbopath.AbsoluteSystemPath) (bool, error) {
- return true, nil
- },
-
- UnmarshalLockfile: func(_rootPackageJSON *fs.PackageJSON, contents []byte) (lockfile.Lockfile, error) {
- return lockfile.DecodePnpmLockfile(contents)
- },
-}
diff --git a/cli/internal/packagemanager/pnpm_test.go b/cli/internal/packagemanager/pnpm_test.go
deleted file mode 100644
index c05bc43..0000000
--- a/cli/internal/packagemanager/pnpm_test.go
+++ /dev/null
@@ -1,57 +0,0 @@
-package packagemanager
-
-import (
- "os"
- "testing"
-
- "github.com/vercel/turbo/cli/internal/fs"
- "github.com/vercel/turbo/cli/internal/turbopath"
- "gotest.tools/v3/assert"
-)
-
-func pnpmPatchesSection(t *testing.T, pkgJSON *fs.PackageJSON) map[string]interface{} {
- t.Helper()
- pnpmSection, ok := pkgJSON.RawJSON["pnpm"].(map[string]interface{})
- assert.Assert(t, ok)
- patchesSection, ok := pnpmSection["patchedDependencies"].(map[string]interface{})
- assert.Assert(t, ok)
- return patchesSection
-}
-
-func getPnpmPackageJSON(t *testing.T) *fs.PackageJSON {
- t.Helper()
- rawCwd, err := os.Getwd()
- assert.NilError(t, err)
- cwd, err := fs.CheckedToAbsoluteSystemPath(rawCwd)
- assert.NilError(t, err)
- pkgJSONPath := cwd.Join("fixtures", "pnpm-patches.json")
- pkgJSON, err := fs.ReadPackageJSON(pkgJSONPath)
- assert.NilError(t, err)
- return pkgJSON
-}
-
-func Test_PnpmPrunePatches_KeepsNecessary(t *testing.T) {
- pkgJSON := getPnpmPackageJSON(t)
- initialPatches := pnpmPatchesSection(t, pkgJSON)
-
- assert.DeepEqual(t, initialPatches, map[string]interface{}{"is-odd@3.0.1": "patches/is-odd@3.0.1.patch"})
-
- err := pnpmPrunePatches(pkgJSON, []turbopath.AnchoredUnixPath{turbopath.AnchoredUnixPath("patches/is-odd@3.0.1.patch")})
- assert.NilError(t, err)
-
- newPatches := pnpmPatchesSection(t, pkgJSON)
- assert.DeepEqual(t, newPatches, map[string]interface{}{"is-odd@3.0.1": "patches/is-odd@3.0.1.patch"})
-}
-
-func Test_PnpmPrunePatches_RemovesExtra(t *testing.T) {
- pkgJSON := getPnpmPackageJSON(t)
- initialPatches := pnpmPatchesSection(t, pkgJSON)
-
- assert.DeepEqual(t, initialPatches, map[string]interface{}{"is-odd@3.0.1": "patches/is-odd@3.0.1.patch"})
-
- err := pnpmPrunePatches(pkgJSON, nil)
- assert.NilError(t, err)
-
- newPatches := pnpmPatchesSection(t, pkgJSON)
- assert.DeepEqual(t, newPatches, map[string]interface{}{})
-}
diff --git a/cli/internal/packagemanager/yarn.go b/cli/internal/packagemanager/yarn.go
deleted file mode 100644
index 8779c5f..0000000
--- a/cli/internal/packagemanager/yarn.go
+++ /dev/null
@@ -1,116 +0,0 @@
-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)
- },
-}