From c4deda35d6be0008ae1a231fcb69f76faa6a6c2d Mon Sep 17 00:00:00 2001 From: CJ Enright Date: Tue, 1 Nov 2022 23:54:25 -0400 Subject: [PATCH 1/5] Use scope options in prune flags --- cli/internal/prune/prune.go | 99 ++++++++++++++++++++++++++----------- 1 file changed, 70 insertions(+), 29 deletions(-) diff --git a/cli/internal/prune/prune.go b/cli/internal/prune/prune.go index e1c8ca593f9d7..43bab938b4cf6 100644 --- a/cli/internal/prune/prune.go +++ b/cli/internal/prune/prune.go @@ -3,14 +3,18 @@ package prune import ( "bufio" "fmt" + "strings" "github.com/spf13/cobra" "github.com/spf13/pflag" - "github.com/vercel/turbo/cli/internal/cmdutil" - "github.com/vercel/turbo/cli/internal/context" - "github.com/vercel/turbo/cli/internal/fs" - "github.com/vercel/turbo/cli/internal/turbopath" - "github.com/vercel/turbo/cli/internal/ui" + "github.com/vercel/turborepo/cli/internal/cmdutil" + "github.com/vercel/turborepo/cli/internal/context" + "github.com/vercel/turborepo/cli/internal/fs" + "github.com/vercel/turborepo/cli/internal/packagemanager" + "github.com/vercel/turborepo/cli/internal/scm" + "github.com/vercel/turborepo/cli/internal/scope" + "github.com/vercel/turborepo/cli/internal/turbopath" + "github.com/vercel/turborepo/cli/internal/ui" "github.com/fatih/color" "github.com/hashicorp/go-hclog" @@ -19,13 +23,14 @@ import ( ) type opts struct { - scope string - docker bool - outputDir string + scopeOpts scope.Opts + singlePackage bool + docker bool + outputDir string } func addPruneFlags(opts *opts, flags *pflag.FlagSet) { - flags.StringVar(&opts.scope, "scope", "", "Specify package to act as entry point for pruned monorepo (required).") + scope.AddFlags(&opts.scopeOpts, flags) flags.BoolVar(&opts.docker, "docker", false, "Output pruned workspace into 'full' and 'json' directories optimized for Docker layer caching.") flags.StringVar(&opts.outputDir, "out-dir", "out", "Set the root directory for files output by this command") // No-op the cwd flag while the root level command is not yet cobra @@ -40,7 +45,7 @@ func addPruneFlags(opts *opts, flags *pflag.FlagSet) { func GetCmd(helper *cmdutil.Helper) *cobra.Command { opts := &opts{} cmd := &cobra.Command{ - Use: "prune --scope= []", + Use: "prune []", Short: "Prepare a subset of your monorepo.", SilenceUsage: true, SilenceErrors: true, @@ -50,11 +55,8 @@ func GetCmd(helper *cmdutil.Helper) *cobra.Command { if err != nil { return err } - if opts.scope == "" { - err := errors.New("at least one target must be specified") - base.LogError(err.Error()) - return err - } + _, packageMode := packagemanager.InferRoot(base.RepoRoot) + opts.singlePackage = packageMode == packagemanager.Single p := &prune{ base, } @@ -90,10 +92,34 @@ func (p *prune) prune(opts *opts) error { if err != nil { return errors.Wrap(err, "could not construct graph") } - p.base.Logger.Trace("scope", "value", opts.scope) - target, scopeIsValid := ctx.PackageInfos[opts.scope] - if !scopeIsValid { - return errors.Errorf("invalid scope: package %v not found", opts.scope) + + var pkgDepGraph *context.Context + if opts.singlePackage { + pkgDepGraph, err = context.SinglePackageGraph(p.base.RepoRoot, rootPackageJSON) + } else { + pkgDepGraph, err = context.BuildPackageGraph(p.base.RepoRoot, rootPackageJSON) + } + if err != nil { + var warnings *context.Warnings + if errors.As(err, &warnings) { + p.base.LogWarning("Issues occurred when constructing package graph. Turbo will function, but some features may not be available", err) + } else { + return err + } + } + + scmInstance, err := scm.FromInRepo(p.base.RepoRoot) + if err != nil { + if errors.Is(err, scm.ErrFallback) { + p.base.LogWarning("", err) + } else { + return errors.Wrap(err, "failed to create SCM") + } + } + filteredPkgs, _, err := scope.ResolvePackages(&opts.scopeOpts, p.base.RepoRoot.ToStringDuringMigration(), scmInstance, pkgDepGraph, p.base.UI, p.base.Logger) + + if err != nil { + return errors.Wrap(err, "failed to resolve packages to run") } outDir := p.base.RepoRoot.UntypedJoin(opts.outputDir) fullDir := outDir @@ -101,13 +127,22 @@ func (p *prune) prune(opts *opts) error { fullDir = fullDir.UntypedJoin("full") } - p.base.Logger.Trace("target", "value", target.Name) - p.base.Logger.Trace("directory", "value", target.Dir) - p.base.Logger.Trace("external deps", "value", target.UnresolvedExternalDeps) - p.base.Logger.Trace("internal deps", "value", target.InternalDeps) + p.base.Logger.Trace("filtered packages", "value", strings.Join(filteredPkgs.UnsafeListOfStrings(), ", ")) p.base.Logger.Trace("docker", "value", opts.docker) p.base.Logger.Trace("out dir", "value", outDir.ToString()) + for _, pkg := range filteredPkgs { + p.base.Logger.Trace("package", "value", pkg) + target, scopeIsValid := ctx.PackageInfos[pkg] + if !scopeIsValid { + return errors.Errorf("invalid scope: package %v not found", pkg) + } + p.base.Logger.Trace("target", "value", target.Name) + p.base.Logger.Trace("directory", "value", target.Dir) + p.base.Logger.Trace("external deps", "value", target.UnresolvedExternalDeps) + p.base.Logger.Trace("internal deps", "value", target.InternalDeps) + } + canPrune, err := ctx.PackageManager.CanPrune(p.base.RepoRoot) if err != nil { return err @@ -119,7 +154,7 @@ func (p *prune) prune(opts *opts) error { return errors.New("Cannot prune without parsed lockfile") } - p.base.UI.Output(fmt.Sprintf("Generating pruned monorepo for %v in %v", ui.Bold(opts.scope), ui.Bold(outDir.ToString()))) + p.base.UI.Output(fmt.Sprintf("Generating pruned monorepo for %v in %v", ui.Bold(strings.Join(filteredPkgs.UnsafeListOfStrings(), ", ")), ui.Bold(outDir.ToString()))) packageJSONPath := outDir.UntypedJoin("package.json") if err := packageJSONPath.EnsureDir(); err != nil { @@ -140,12 +175,18 @@ func (p *prune) prune(opts *opts) error { } } workspaces := []turbopath.AnchoredSystemPath{} - targets := []interface{}{opts.scope} - internalDeps, err := ctx.TopologicalGraph.Ancestors(opts.scope) - if err != nil { - return errors.Wrap(err, "could find traverse the dependency graph to find topological dependencies") + + targets := filteredPkgs.Copy() + for _, pkg := range filteredPkgs { + internalDeps, err := ctx.TopologicalGraph.Ancestors(pkg) + if err != nil { + return errors.Wrap(err, "could find traverse the dependency graph to find topological dependencies") + } + for _, internalDep := range internalDeps { + targets.Add(internalDep) + } } - targets = append(targets, internalDeps.List()...) + p.base.Logger.Trace("targets", "value", targets) lockfileKeys := make([]string, 0, len(rootPackageJSON.TransitiveDeps)) lockfileKeys = append(lockfileKeys, rootPackageJSON.TransitiveDeps...) From 39aa02c5abd139adeea982cf2f4704157a5c2f5c Mon Sep 17 00:00:00 2001 From: CJ Enright Date: Sat, 19 Nov 2022 11:22:28 -0500 Subject: [PATCH 2/5] Revert "Use scope options in prune flags" This reverts commit c4deda35d6be0008ae1a231fcb69f76faa6a6c2d. --- cli/internal/prune/prune.go | 99 +++++++++++-------------------------- 1 file changed, 29 insertions(+), 70 deletions(-) diff --git a/cli/internal/prune/prune.go b/cli/internal/prune/prune.go index 43bab938b4cf6..e1c8ca593f9d7 100644 --- a/cli/internal/prune/prune.go +++ b/cli/internal/prune/prune.go @@ -3,18 +3,14 @@ package prune import ( "bufio" "fmt" - "strings" "github.com/spf13/cobra" "github.com/spf13/pflag" - "github.com/vercel/turborepo/cli/internal/cmdutil" - "github.com/vercel/turborepo/cli/internal/context" - "github.com/vercel/turborepo/cli/internal/fs" - "github.com/vercel/turborepo/cli/internal/packagemanager" - "github.com/vercel/turborepo/cli/internal/scm" - "github.com/vercel/turborepo/cli/internal/scope" - "github.com/vercel/turborepo/cli/internal/turbopath" - "github.com/vercel/turborepo/cli/internal/ui" + "github.com/vercel/turbo/cli/internal/cmdutil" + "github.com/vercel/turbo/cli/internal/context" + "github.com/vercel/turbo/cli/internal/fs" + "github.com/vercel/turbo/cli/internal/turbopath" + "github.com/vercel/turbo/cli/internal/ui" "github.com/fatih/color" "github.com/hashicorp/go-hclog" @@ -23,14 +19,13 @@ import ( ) type opts struct { - scopeOpts scope.Opts - singlePackage bool - docker bool - outputDir string + scope string + docker bool + outputDir string } func addPruneFlags(opts *opts, flags *pflag.FlagSet) { - scope.AddFlags(&opts.scopeOpts, flags) + flags.StringVar(&opts.scope, "scope", "", "Specify package to act as entry point for pruned monorepo (required).") flags.BoolVar(&opts.docker, "docker", false, "Output pruned workspace into 'full' and 'json' directories optimized for Docker layer caching.") flags.StringVar(&opts.outputDir, "out-dir", "out", "Set the root directory for files output by this command") // No-op the cwd flag while the root level command is not yet cobra @@ -45,7 +40,7 @@ func addPruneFlags(opts *opts, flags *pflag.FlagSet) { func GetCmd(helper *cmdutil.Helper) *cobra.Command { opts := &opts{} cmd := &cobra.Command{ - Use: "prune []", + Use: "prune --scope= []", Short: "Prepare a subset of your monorepo.", SilenceUsage: true, SilenceErrors: true, @@ -55,8 +50,11 @@ func GetCmd(helper *cmdutil.Helper) *cobra.Command { if err != nil { return err } - _, packageMode := packagemanager.InferRoot(base.RepoRoot) - opts.singlePackage = packageMode == packagemanager.Single + if opts.scope == "" { + err := errors.New("at least one target must be specified") + base.LogError(err.Error()) + return err + } p := &prune{ base, } @@ -92,34 +90,10 @@ func (p *prune) prune(opts *opts) error { if err != nil { return errors.Wrap(err, "could not construct graph") } - - var pkgDepGraph *context.Context - if opts.singlePackage { - pkgDepGraph, err = context.SinglePackageGraph(p.base.RepoRoot, rootPackageJSON) - } else { - pkgDepGraph, err = context.BuildPackageGraph(p.base.RepoRoot, rootPackageJSON) - } - if err != nil { - var warnings *context.Warnings - if errors.As(err, &warnings) { - p.base.LogWarning("Issues occurred when constructing package graph. Turbo will function, but some features may not be available", err) - } else { - return err - } - } - - scmInstance, err := scm.FromInRepo(p.base.RepoRoot) - if err != nil { - if errors.Is(err, scm.ErrFallback) { - p.base.LogWarning("", err) - } else { - return errors.Wrap(err, "failed to create SCM") - } - } - filteredPkgs, _, err := scope.ResolvePackages(&opts.scopeOpts, p.base.RepoRoot.ToStringDuringMigration(), scmInstance, pkgDepGraph, p.base.UI, p.base.Logger) - - if err != nil { - return errors.Wrap(err, "failed to resolve packages to run") + p.base.Logger.Trace("scope", "value", opts.scope) + target, scopeIsValid := ctx.PackageInfos[opts.scope] + if !scopeIsValid { + return errors.Errorf("invalid scope: package %v not found", opts.scope) } outDir := p.base.RepoRoot.UntypedJoin(opts.outputDir) fullDir := outDir @@ -127,22 +101,13 @@ func (p *prune) prune(opts *opts) error { fullDir = fullDir.UntypedJoin("full") } - p.base.Logger.Trace("filtered packages", "value", strings.Join(filteredPkgs.UnsafeListOfStrings(), ", ")) + p.base.Logger.Trace("target", "value", target.Name) + p.base.Logger.Trace("directory", "value", target.Dir) + p.base.Logger.Trace("external deps", "value", target.UnresolvedExternalDeps) + p.base.Logger.Trace("internal deps", "value", target.InternalDeps) p.base.Logger.Trace("docker", "value", opts.docker) p.base.Logger.Trace("out dir", "value", outDir.ToString()) - for _, pkg := range filteredPkgs { - p.base.Logger.Trace("package", "value", pkg) - target, scopeIsValid := ctx.PackageInfos[pkg] - if !scopeIsValid { - return errors.Errorf("invalid scope: package %v not found", pkg) - } - p.base.Logger.Trace("target", "value", target.Name) - p.base.Logger.Trace("directory", "value", target.Dir) - p.base.Logger.Trace("external deps", "value", target.UnresolvedExternalDeps) - p.base.Logger.Trace("internal deps", "value", target.InternalDeps) - } - canPrune, err := ctx.PackageManager.CanPrune(p.base.RepoRoot) if err != nil { return err @@ -154,7 +119,7 @@ func (p *prune) prune(opts *opts) error { return errors.New("Cannot prune without parsed lockfile") } - p.base.UI.Output(fmt.Sprintf("Generating pruned monorepo for %v in %v", ui.Bold(strings.Join(filteredPkgs.UnsafeListOfStrings(), ", ")), ui.Bold(outDir.ToString()))) + p.base.UI.Output(fmt.Sprintf("Generating pruned monorepo for %v in %v", ui.Bold(opts.scope), ui.Bold(outDir.ToString()))) packageJSONPath := outDir.UntypedJoin("package.json") if err := packageJSONPath.EnsureDir(); err != nil { @@ -175,18 +140,12 @@ func (p *prune) prune(opts *opts) error { } } workspaces := []turbopath.AnchoredSystemPath{} - - targets := filteredPkgs.Copy() - for _, pkg := range filteredPkgs { - internalDeps, err := ctx.TopologicalGraph.Ancestors(pkg) - if err != nil { - return errors.Wrap(err, "could find traverse the dependency graph to find topological dependencies") - } - for _, internalDep := range internalDeps { - targets.Add(internalDep) - } + targets := []interface{}{opts.scope} + internalDeps, err := ctx.TopologicalGraph.Ancestors(opts.scope) + if err != nil { + return errors.Wrap(err, "could find traverse the dependency graph to find topological dependencies") } - p.base.Logger.Trace("targets", "value", targets) + targets = append(targets, internalDeps.List()...) lockfileKeys := make([]string, 0, len(rootPackageJSON.TransitiveDeps)) lockfileKeys = append(lockfileKeys, rootPackageJSON.TransitiveDeps...) From e457ce27d0642ed35aae86711f701d1ceeb98010 Mon Sep 17 00:00:00 2001 From: CJ Enright Date: Sat, 19 Nov 2022 11:31:28 -0500 Subject: [PATCH 3/5] Make prune scope flag a string array --- cli/internal/prune/prune.go | 48 +++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/cli/internal/prune/prune.go b/cli/internal/prune/prune.go index 1e34a4c5f00bf..868083857397c 100644 --- a/cli/internal/prune/prune.go +++ b/cli/internal/prune/prune.go @@ -3,6 +3,7 @@ package prune import ( "bufio" "fmt" + "strings" "github.com/vercel/turbo/cli/internal/config" @@ -13,6 +14,7 @@ import ( "github.com/vercel/turbo/cli/internal/fs" "github.com/vercel/turbo/cli/internal/turbopath" "github.com/vercel/turbo/cli/internal/ui" + "github.com/vercel/turbo/cli/internal/util" "github.com/fatih/color" "github.com/hashicorp/go-hclog" @@ -21,13 +23,13 @@ import ( ) type opts struct { - scope string + scope []string docker bool outputDir string } func addPruneFlags(opts *opts, flags *pflag.FlagSet) { - flags.StringVar(&opts.scope, "scope", "", "Specify package to act as entry point for pruned monorepo (required).") + flags.StringArrayVar(&opts.scope, "scope", nil, "Specify package(s) to act as entry points for pruned monorepo (required).") flags.BoolVar(&opts.docker, "docker", false, "Output pruned workspace into 'full' and 'json' directories optimized for Docker layer caching.") flags.StringVar(&opts.outputDir, "out-dir", "out", "Set the root directory for files output by this command") // No-op the cwd flag while the root level command is not yet cobra @@ -53,7 +55,7 @@ func GetCmd(helper *cmdutil.Helper) *cobra.Command { if err != nil { return err } - if opts.scope == "" { + if len(opts.scope) == 0 { err := errors.New("at least one target must be specified") base.LogError(err.Error()) return err @@ -93,24 +95,28 @@ func (p *prune) prune(opts *opts) error { if err != nil { return errors.Wrap(err, "could not construct graph") } - p.base.Logger.Trace("scope", "value", opts.scope) - target, scopeIsValid := ctx.WorkspaceInfos[opts.scope] - if !scopeIsValid { - return errors.Errorf("invalid scope: package %v not found", opts.scope) - } outDir := p.base.RepoRoot.UntypedJoin(opts.outputDir) fullDir := outDir if opts.docker { fullDir = fullDir.UntypedJoin("full") } - p.base.Logger.Trace("target", "value", target.Name) - p.base.Logger.Trace("directory", "value", target.Dir) - p.base.Logger.Trace("external deps", "value", target.UnresolvedExternalDeps) - p.base.Logger.Trace("internal deps", "value", target.InternalDeps) + p.base.Logger.Trace("scope", "value", strings.Join(opts.scope, ", ")) p.base.Logger.Trace("docker", "value", opts.docker) p.base.Logger.Trace("out dir", "value", outDir.ToString()) + for _, scope := range opts.scope { + p.base.Logger.Trace("scope", "value", scope) + target, scopeIsValid := ctx.WorkspaceInfos[scope] + if !scopeIsValid { + return errors.Errorf("invalid scope: package %v not found", scope) + } + p.base.Logger.Trace("target", "value", target.Name) + p.base.Logger.Trace("directory", "value", target.Dir) + p.base.Logger.Trace("external deps", "value", target.UnresolvedExternalDeps) + p.base.Logger.Trace("internal deps", "value", target.InternalDeps) + } + canPrune, err := ctx.PackageManager.CanPrune(p.base.RepoRoot) if err != nil { return err @@ -122,7 +128,7 @@ func (p *prune) prune(opts *opts) error { return errors.New("Cannot prune without parsed lockfile") } - p.base.UI.Output(fmt.Sprintf("Generating pruned monorepo for %v in %v", ui.Bold(opts.scope), ui.Bold(outDir.ToString()))) + p.base.UI.Output(fmt.Sprintf("Generating pruned monorepo for %v in %v", ui.Bold(strings.Join(opts.scope, ", ")), ui.Bold(outDir.ToString()))) packageJSONPath := outDir.UntypedJoin("package.json") if err := packageJSONPath.EnsureDir(); err != nil { @@ -143,16 +149,22 @@ func (p *prune) prune(opts *opts) error { } } workspaces := []turbopath.AnchoredSystemPath{} - targets := []string{opts.scope} - internalDeps, err := ctx.WorkspaceGraph.Ancestors(opts.scope) - if err != nil { - return errors.Wrap(err, "could find traverse the dependency graph to find topological dependencies") + targetSet := make(util.Set) + for _, pkg := range opts.scope { + internalDeps, err := ctx.WorkspaceGraph.Ancestors(pkg) + if err != nil { + return errors.Wrap(err, "could not find traverse the dependency graph to find topological dependencies") + } + for _, internalDep := range internalDeps { + targetSet.Add(internalDep) + } } // Use for loop so we can coerce to string // .List() returns a list of interface{} types, but // we know they are strings. - for _, dep := range internalDeps.List() { + targets := opts.scope + for _, dep := range targetSet.List() { targets = append(targets, dep.(string)) } From ea3a90376487c9c576a8baf316a22d50c83641ec Mon Sep 17 00:00:00 2001 From: CJ Enright Date: Sat, 19 Nov 2022 12:36:20 -0500 Subject: [PATCH 4/5] Prevent rewalking parts of prune graph --- cli/internal/context/context.go | 27 +++++++++++++++++++++++++++ cli/internal/prune/prune.go | 22 ++++------------------ 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/cli/internal/context/context.go b/cli/internal/context/context.go index 23b504f917805..f01b73224ce1e 100644 --- a/cli/internal/context/context.go +++ b/cli/internal/context/context.go @@ -419,3 +419,30 @@ func (c *Context) resolveDepGraph(wg *errgroup.Group, workspace *fs.PackageJSON, }) } } + +// TopologicalGraphAncestors returns the ancestors of a set of nodes in the workspace graph. +func (c *Context) TopologicalGraphAncestors(start []string) ([]string, error) { + vertices := make(dag.Set) + for _, v := range start { + vertices.Add(v) + } + s := make(dag.Set) + memoFunc := func(v dag.Vertex, d int) error { + s.Add(v) + return nil + } + + if err := c.WorkspaceGraph.DepthFirstWalk(vertices, memoFunc); err != nil { + return nil, err + } + + // Use for loop so we can coerce to string + // .List() returns a list of interface{} types, but + // we know they are strings. + targets := make([]string, 0, s.Len()) + for _, dep := range s.List() { + targets = append(targets, dep.(string)) + } + + return targets, nil +} diff --git a/cli/internal/prune/prune.go b/cli/internal/prune/prune.go index 868083857397c..aeddfbc43e5d1 100644 --- a/cli/internal/prune/prune.go +++ b/cli/internal/prune/prune.go @@ -14,7 +14,6 @@ import ( "github.com/vercel/turbo/cli/internal/fs" "github.com/vercel/turbo/cli/internal/turbopath" "github.com/vercel/turbo/cli/internal/ui" - "github.com/vercel/turbo/cli/internal/util" "github.com/fatih/color" "github.com/hashicorp/go-hclog" @@ -149,24 +148,11 @@ func (p *prune) prune(opts *opts) error { } } workspaces := []turbopath.AnchoredSystemPath{} - targetSet := make(util.Set) - for _, pkg := range opts.scope { - internalDeps, err := ctx.WorkspaceGraph.Ancestors(pkg) - if err != nil { - return errors.Wrap(err, "could not find traverse the dependency graph to find topological dependencies") - } - for _, internalDep := range internalDeps { - targetSet.Add(internalDep) - } - } - - // Use for loop so we can coerce to string - // .List() returns a list of interface{} types, but - // we know they are strings. - targets := opts.scope - for _, dep := range targetSet.List() { - targets = append(targets, dep.(string)) + targets, err := ctx.TopologicalGraphAncestors(opts.scope) + if err != nil { + return errors.Wrap(err, "could not traverse the dependency graph to find topological dependencies") } + p.base.Logger.Trace("targets", "value", targets) lockfileKeys := make([]string, 0, len(rootPackageJSON.TransitiveDeps)) lockfileKeys = append(lockfileKeys, rootPackageJSON.TransitiveDeps...) From c811fbd0dbfde4f5f431a2ec160cb463bcafb841 Mon Sep 17 00:00:00 2001 From: CJ Enright Date: Mon, 28 Nov 2022 15:14:19 -0600 Subject: [PATCH 5/5] Rename TopologicalGraphAncestors and sort targets --- cli/internal/context/context.go | 6 ++++-- cli/internal/prune/prune.go | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/cli/internal/context/context.go b/cli/internal/context/context.go index f01b73224ce1e..d0acc652b2423 100644 --- a/cli/internal/context/context.go +++ b/cli/internal/context/context.go @@ -420,8 +420,9 @@ func (c *Context) resolveDepGraph(wg *errgroup.Group, workspace *fs.PackageJSON, } } -// TopologicalGraphAncestors returns the ancestors of a set of nodes in the workspace graph. -func (c *Context) TopologicalGraphAncestors(start []string) ([]string, error) { +// InternalDependencies finds all dependencies required by the slice of starting +// packages, as well as the starting packages themselves. +func (c *Context) InternalDependencies(start []string) ([]string, error) { vertices := make(dag.Set) for _, v := range start { vertices.Add(v) @@ -443,6 +444,7 @@ func (c *Context) TopologicalGraphAncestors(start []string) ([]string, error) { for _, dep := range s.List() { targets = append(targets, dep.(string)) } + sort.Strings(targets) return targets, nil } diff --git a/cli/internal/prune/prune.go b/cli/internal/prune/prune.go index aeddfbc43e5d1..7c2770896e162 100644 --- a/cli/internal/prune/prune.go +++ b/cli/internal/prune/prune.go @@ -148,7 +148,7 @@ func (p *prune) prune(opts *opts) error { } } workspaces := []turbopath.AnchoredSystemPath{} - targets, err := ctx.TopologicalGraphAncestors(opts.scope) + targets, err := ctx.InternalDependencies(opts.scope) if err != nil { return errors.Wrap(err, "could not traverse the dependency graph to find topological dependencies") }