diff --git a/cli/internal/context/context.go b/cli/internal/context/context.go index cc29d3349e234..d790d16f581e0 100644 --- a/cli/internal/context/context.go +++ b/cli/internal/context/context.go @@ -28,6 +28,7 @@ const GLOBAL_CACHE_KEY = "snozzberries" // Context of the CLI type Context struct { + RootPackageInfo *fs.PackageJSON // TODO(gsoltis): should this be included in PackageInfos? PackageInfos map[interface{}]*fs.PackageJSON PackageNames []string TopologicalGraph dag.AcyclicGraph @@ -108,9 +109,10 @@ func WithGraph(rootpath string, config *config.Config) Option { c.Lockfile = lockfile } - if c.ResolveWorkspaceRootDeps(rootPackageJSON) != nil { + if c.resolveWorkspaceRootDeps(rootPackageJSON) != nil { return err } + c.RootPackageInfo = rootPackageJSON spaces, err := c.Backend.GetWorkspaceGlobs(rootpath) @@ -214,7 +216,7 @@ func (c *Context) loadPackageDepsHash(pkg *fs.PackageJSON) error { return nil } -func (c *Context) ResolveWorkspaceRootDeps(rootPackageJSON *fs.PackageJSON) error { +func (c *Context) resolveWorkspaceRootDeps(rootPackageJSON *fs.PackageJSON) error { seen := mapset.NewSet() var lockfileWg sync.WaitGroup pkg := rootPackageJSON @@ -234,7 +236,7 @@ func (c *Context) ResolveWorkspaceRootDeps(rootPackageJSON *fs.PackageJSON) erro } if util.IsYarn(c.Backend.Name) { pkg.SubLockfile = make(fs.YarnLockfile) - c.ResolveDepGraph(&lockfileWg, pkg.UnresolvedExternalDeps, depSet, seen, pkg) + c.resolveDepGraph(&lockfileWg, pkg.UnresolvedExternalDeps, depSet, seen, pkg) lockfileWg.Wait() pkg.ExternalDeps = make([]string, 0, depSet.Cardinality()) for _, v := range depSet.ToSlice() { @@ -254,32 +256,6 @@ func (c *Context) ResolveWorkspaceRootDeps(rootPackageJSON *fs.PackageJSON) erro return nil } -// GetTargetsFromArguments returns a list of targets from the arguments and Turbo config. -// Return targets are always unique sorted alphabetically. -func GetTargetsFromArguments(arguments []string, configJson *fs.TurboConfigJSON) ([]string, error) { - targets := make(util.Set) - for _, arg := range arguments { - if arg == "--" { - break - } - if !strings.HasPrefix(arg, "-") { - targets.Add(arg) - found := false - for task := range configJson.Pipeline { - if task == arg { - found = true - } - } - if !found { - return nil, fmt.Errorf("task `%v` not found in turbo pipeline in package.json. Are you sure you added it?", arg) - } - } - } - stringTargets := targets.UnsafeListOfStrings() - sort.Strings(stringTargets) - return stringTargets, nil -} - func (c *Context) populateTopologicGraphForPackageJson(pkg *fs.PackageJSON) error { c.mutex.Lock() defer c.mutex.Unlock() @@ -330,7 +306,7 @@ func (c *Context) populateTopologicGraphForPackageJson(pkg *fs.PackageJSON) erro pkg.SubLockfile = make(fs.YarnLockfile) seen := mapset.NewSet() var lockfileWg sync.WaitGroup - c.ResolveDepGraph(&lockfileWg, pkg.UnresolvedExternalDeps, externalDepSet, seen, pkg) + c.resolveDepGraph(&lockfileWg, pkg.UnresolvedExternalDeps, externalDepSet, seen, pkg) lockfileWg.Wait() // when there are no internal dependencies, we need to still add these leafs to the graph @@ -376,7 +352,7 @@ func (c *Context) parsePackageJSON(buildFilePath string) error { return nil } -func (c *Context) ResolveDepGraph(wg *sync.WaitGroup, unresolvedDirectDeps map[string]string, resolvedDepsSet mapset.Set, seen mapset.Set, pkg *fs.PackageJSON) { +func (c *Context) resolveDepGraph(wg *sync.WaitGroup, unresolvedDirectDeps map[string]string, resolvedDepsSet mapset.Set, seen mapset.Set, pkg *fs.PackageJSON) { if !util.IsYarn(c.Backend.Name) { return } @@ -414,10 +390,10 @@ func (c *Context) ResolveDepGraph(wg *sync.WaitGroup, unresolvedDirectDeps map[s resolvedDepsSet.Add(fmt.Sprintf("%v@%v", directDepName, entry.Version)) if len(entry.Dependencies) > 0 { - c.ResolveDepGraph(wg, entry.Dependencies, resolvedDepsSet, seen, pkg) + c.resolveDepGraph(wg, entry.Dependencies, resolvedDepsSet, seen, pkg) } if len(entry.OptionalDependencies) > 0 { - c.ResolveDepGraph(wg, entry.OptionalDependencies, resolvedDepsSet, seen, pkg) + c.resolveDepGraph(wg, entry.OptionalDependencies, resolvedDepsSet, seen, pkg) } }(directDepName, unresolvedVersion) diff --git a/cli/internal/context/context_test.go b/cli/internal/context/context_test.go index 315361b8a53c3..3fea12a01b0bd 100644 --- a/cli/internal/context/context_test.go +++ b/cli/internal/context/context_test.go @@ -3,98 +3,8 @@ package context import ( "reflect" "testing" - - "github.com/vercel/turborepo/cli/internal/fs" ) -func TestGetTargetsFromArguments(t *testing.T) { - type args struct { - arguments []string - configJson *fs.TurboConfigJSON - } - tests := []struct { - name string - args args - want []string - wantErr bool - }{ - { - name: "handles one defined target", - args: args{ - arguments: []string{"build"}, - configJson: &fs.TurboConfigJSON{ - Pipeline: map[string]fs.Pipeline{ - "build": {}, - "test": {}, - "thing#test": {}, - }, - }, - }, - want: []string{"build"}, - wantErr: false, - }, - { - name: "handles multiple targets and ignores flags", - args: args{ - arguments: []string{"build", "test", "--foo", "--bar"}, - configJson: &fs.TurboConfigJSON{ - Pipeline: map[string]fs.Pipeline{ - "build": {}, - "test": {}, - "thing#test": {}, - }, - }, - }, - want: []string{"build", "test"}, - wantErr: false, - }, - { - name: "handles pass through arguments after -- ", - args: args{ - arguments: []string{"build", "test", "--", "--foo", "build", "--cache-dir"}, - configJson: &fs.TurboConfigJSON{ - Pipeline: map[string]fs.Pipeline{ - "build": {}, - "test": {}, - "thing#test": {}, - }, - }, - }, - want: []string{"build", "test"}, - wantErr: false, - }, - { - name: "handles unknown pipeline targets ", - args: args{ - arguments: []string{"foo", "test", "--", "--foo", "build", "--cache-dir"}, - configJson: &fs.TurboConfigJSON{ - Pipeline: map[string]fs.Pipeline{ - "build": {}, - "test": {}, - "thing#test": {}, - }, - }, - }, - want: nil, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := GetTargetsFromArguments(tt.args.arguments, tt.args.configJson) - if (err != nil) != tt.wantErr { - t.Errorf("GetTargetsFromArguments() error = %v, wantErr %v", err, tt.wantErr) - return - } - - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("GetTargetsFromArguments() = %v, want %v", got, tt.want) - } - }) - } -} - func Test_getHashableTurboEnvVarsFromOs(t *testing.T) { env := []string{ "SOME_ENV_VAR=excluded", diff --git a/cli/internal/prune/prune.go b/cli/internal/prune/prune.go index 4c8941c21ee5d..b939336ed1aa2 100644 --- a/cli/internal/prune/prune.go +++ b/cli/internal/prune/prune.go @@ -9,7 +9,6 @@ import ( "os" "path/filepath" "strings" - "sync" "github.com/vercel/turborepo/cli/internal/config" "github.com/vercel/turborepo/cli/internal/context" @@ -17,7 +16,6 @@ import ( "github.com/vercel/turborepo/cli/internal/ui" "github.com/vercel/turborepo/cli/internal/util" - mapset "github.com/deckarep/golang-set" "github.com/fatih/color" "github.com/hashicorp/go-hclog" "github.com/mitchellh/cli" @@ -128,33 +126,7 @@ func (c *PruneCommand) Run(args []string) int { return 1 } workspaces := []string{} - seen := mapset.NewSet() - var lockfileWg sync.WaitGroup - pkg, err := fs.ReadPackageJSON(filepath.Join(pruneOptions.cwd, "package.json")) - if err != nil { - c.logError(c.Config.Logger, "", fmt.Errorf("could not read package.json: %w", err)) - return 1 - } - depSet := mapset.NewSet() - pkg.UnresolvedExternalDeps = make(map[string]string) - for dep, version := range pkg.Dependencies { - pkg.UnresolvedExternalDeps[dep] = version - } - for dep, version := range pkg.DevDependencies { - pkg.UnresolvedExternalDeps[dep] = version - } - for dep, version := range pkg.OptionalDependencies { - pkg.UnresolvedExternalDeps[dep] = version - } - for dep, version := range pkg.PeerDependencies { - pkg.UnresolvedExternalDeps[dep] = version - } - - pkg.SubLockfile = make(fs.YarnLockfile) - ctx.ResolveDepGraph(&lockfileWg, pkg.UnresolvedExternalDeps, depSet, seen, pkg) - - lockfileWg.Wait() - lockfile := pkg.SubLockfile + lockfile := ctx.RootPackageInfo.SubLockfile targets := []interface{}{pruneOptions.scope} internalDeps, err := ctx.TopologicalGraph.Ancestors(pruneOptions.scope) if err != nil { diff --git a/cli/internal/run/run.go b/cli/internal/run/run.go index 9a6e16ee246ca..0c5a4b8ae4c46 100644 --- a/cli/internal/run/run.go +++ b/cli/internal/run/run.go @@ -148,7 +148,7 @@ func (c *RunCommand) Run(args []string) int { c.logError(c.Config.Logger, "", err) return 1 } - targets, err := context.GetTargetsFromArguments(args, ctx.TurboConfig) + targets, err := getTargetsFromArguments(args, ctx.TurboConfig) if err != nil { c.logError(c.Config.Logger, "", fmt.Errorf("failed to resolve targets: %w", err)) return 1 @@ -1012,3 +1012,29 @@ func replayLogs(logger hclog.Logger, prefixUi cli.Ui, runOptions *RunOptions, lo } logger.Debug("finish replaying logs") } + +// GetTargetsFromArguments returns a list of targets from the arguments and Turbo config. +// Return targets are always unique sorted alphabetically. +func getTargetsFromArguments(arguments []string, configJson *fs.TurboConfigJSON) ([]string, error) { + targets := make(util.Set) + for _, arg := range arguments { + if arg == "--" { + break + } + if !strings.HasPrefix(arg, "-") { + targets.Add(arg) + found := false + for task := range configJson.Pipeline { + if task == arg { + found = true + } + } + if !found { + return nil, fmt.Errorf("task `%v` not found in turbo pipeline in package.json. Are you sure you added it?", arg) + } + } + } + stringTargets := targets.UnsafeListOfStrings() + sort.Strings(stringTargets) + return stringTargets, nil +} diff --git a/cli/internal/run/run_test.go b/cli/internal/run/run_test.go index 4ef041a180c9d..334cc8b6587b5 100644 --- a/cli/internal/run/run_test.go +++ b/cli/internal/run/run_test.go @@ -4,10 +4,12 @@ import ( "fmt" "os" "path/filepath" + "reflect" "testing" "github.com/mitchellh/cli" "github.com/vercel/turborepo/cli/internal/context" + "github.com/vercel/turborepo/cli/internal/fs" "github.com/vercel/turborepo/cli/internal/util" "github.com/stretchr/testify/assert" @@ -222,3 +224,91 @@ func TestScopedPackages(t *testing.T) { assert.Error(t, err) }) } + +func TestGetTargetsFromArguments(t *testing.T) { + type args struct { + arguments []string + configJson *fs.TurboConfigJSON + } + tests := []struct { + name string + args args + want []string + wantErr bool + }{ + { + name: "handles one defined target", + args: args{ + arguments: []string{"build"}, + configJson: &fs.TurboConfigJSON{ + Pipeline: map[string]fs.Pipeline{ + "build": {}, + "test": {}, + "thing#test": {}, + }, + }, + }, + want: []string{"build"}, + wantErr: false, + }, + { + name: "handles multiple targets and ignores flags", + args: args{ + arguments: []string{"build", "test", "--foo", "--bar"}, + configJson: &fs.TurboConfigJSON{ + Pipeline: map[string]fs.Pipeline{ + "build": {}, + "test": {}, + "thing#test": {}, + }, + }, + }, + want: []string{"build", "test"}, + wantErr: false, + }, + { + name: "handles pass through arguments after -- ", + args: args{ + arguments: []string{"build", "test", "--", "--foo", "build", "--cache-dir"}, + configJson: &fs.TurboConfigJSON{ + Pipeline: map[string]fs.Pipeline{ + "build": {}, + "test": {}, + "thing#test": {}, + }, + }, + }, + want: []string{"build", "test"}, + wantErr: false, + }, + { + name: "handles unknown pipeline targets ", + args: args{ + arguments: []string{"foo", "test", "--", "--foo", "build", "--cache-dir"}, + configJson: &fs.TurboConfigJSON{ + Pipeline: map[string]fs.Pipeline{ + "build": {}, + "test": {}, + "thing#test": {}, + }, + }, + }, + want: nil, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := getTargetsFromArguments(tt.args.arguments, tt.args.configJson) + if (err != nil) != tt.wantErr { + t.Errorf("GetTargetsFromArguments() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetTargetsFromArguments() = %v, want %v", got, tt.want) + } + }) + } +}