aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/cli/internal/packagemanager/packagemanager.go
diff options
context:
space:
mode:
author简律纯 <hsiangnianian@outlook.com>2023-04-28 01:36:44 +0800
committer简律纯 <hsiangnianian@outlook.com>2023-04-28 01:36:44 +0800
commitdd84b9d64fb98746a230cd24233ff50a562c39c9 (patch)
treeb583261ef00b3afe72ec4d6dacb31e57779a6faf /cli/internal/packagemanager/packagemanager.go
parent0b46fcd72ac34382387b2bcf9095233efbcc52f4 (diff)
downloadHydroRoll-dd84b9d64fb98746a230cd24233ff50a562c39c9.tar.gz
HydroRoll-dd84b9d64fb98746a230cd24233ff50a562c39c9.zip
Diffstat (limited to 'cli/internal/packagemanager/packagemanager.go')
-rw-r--r--cli/internal/packagemanager/packagemanager.go197
1 files changed, 197 insertions, 0 deletions
diff --git a/cli/internal/packagemanager/packagemanager.go b/cli/internal/packagemanager/packagemanager.go
new file mode 100644
index 0000000..dc5b966
--- /dev/null
+++ b/cli/internal/packagemanager/packagemanager.go
@@ -0,0 +1,197 @@
+// 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
+}