From fc8c5fdce62fb229202659408798a7b6c98f6e8b Mon Sep 17 00:00:00 2001 From: 简律纯 Date: Fri, 28 Apr 2023 01:36:55 +0800 Subject: --- cli/internal/packagemanager/berry.go | 156 -------- cli/internal/packagemanager/fixtures/package.json | 7 - .../packagemanager/fixtures/pnpm-patches.json | 11 - .../packagemanager/fixtures/pnpm-workspace.yaml | 3 - cli/internal/packagemanager/infer_root.go | 146 -------- cli/internal/packagemanager/infer_root_test.go | 347 ----------------- cli/internal/packagemanager/npm.go | 59 --- cli/internal/packagemanager/packagemanager.go | 197 ---------- cli/internal/packagemanager/packagemanager_test.go | 411 --------------------- cli/internal/packagemanager/pnpm.go | 168 --------- cli/internal/packagemanager/pnpm6.go | 63 ---- cli/internal/packagemanager/pnpm_test.go | 57 --- cli/internal/packagemanager/yarn.go | 116 ------ 13 files changed, 1741 deletions(-) delete mode 100644 cli/internal/packagemanager/berry.go delete mode 100644 cli/internal/packagemanager/fixtures/package.json delete mode 100644 cli/internal/packagemanager/fixtures/pnpm-patches.json delete mode 100644 cli/internal/packagemanager/fixtures/pnpm-workspace.yaml delete mode 100644 cli/internal/packagemanager/infer_root.go delete mode 100644 cli/internal/packagemanager/infer_root_test.go delete mode 100644 cli/internal/packagemanager/npm.go delete mode 100644 cli/internal/packagemanager/packagemanager.go delete mode 100644 cli/internal/packagemanager/packagemanager_test.go delete mode 100644 cli/internal/packagemanager/pnpm.go delete mode 100644 cli/internal/packagemanager/pnpm6.go delete mode 100644 cli/internal/packagemanager/pnpm_test.go delete mode 100644 cli/internal/packagemanager/yarn.go (limited to 'cli/internal/packagemanager') 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) - }, -} -- cgit v1.2.3-70-g09d2