Repository: gruntwork-io/terragrunt

Issue:
Source map might not work in stack run
I understand that `unit.source` of `terragrunt.stack.hcl`  doesn't support source-maps, but the `terraform.source` of `terragrunt.hcl` does support the source-map. However, when running `terragrunt stack apply`, the source-map is not used for `terraform.source` in `terragrunt.hcl` . Is this also as designed?

Alternatively, if I apply the stack with `terragrunt stack generate` followed by `terragrunt run-all apply`, the source-map is respected.

I've been trying out stacks, and even though I've configured my `unit.source` to point to a local directory, I'd like to be able to substitute the module's source on the fly.

_Originally posted by @mhulscher in https://github.com/gruntwork-io/terragrunt/issues/3313#issuecomment-2676119235_

Discussion:
@denis256: Fixed handling of `source-map` in https://github.com/gruntwork-io/terragrunt/releases/tag/v0.75.8

Solution patch:
diff --git a/cli/commands/find/action.go b/cli/commands/find/action.go
index 309290e0..d2500e5f 100644
--- a/cli/commands/find/action.go
+++ b/cli/commands/find/action.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/gruntwork-io/terragrunt/internal/discovery"
 	"github.com/gruntwork-io/terragrunt/internal/errors"
+	"github.com/gruntwork-io/terragrunt/internal/queue"
 	"github.com/mgutz/ansi"
 )
 
@@ -19,16 +20,41 @@ func Run(ctx context.Context, opts *Options) error {
 		d = d.WithHidden()
 	}
 
-	configs, err := d.Discover()
+	if opts.Dependencies || opts.External || opts.Sort == SortDAG {
+		d = d.WithDiscoverDependencies()
+	}
+
+	if opts.External {
+		d = d.WithDiscoverExternalDependencies()
+	}
+
+	cfgs, err := d.Discover(ctx, opts.TerragruntOptions)
+	if err != nil {
+		return errors.New(err)
+	}
+
+	switch opts.Sort {
+	case SortAlpha:
+		cfgs = cfgs.Sort()
+	case SortDAG:
+		q, err := queue.NewQueue(cfgs)
+		if err != nil {
+			return errors.New(err)
+		}
+
+		cfgs = q.Entries()
+	}
+
+	foundCfgs, err := discoveredToFound(cfgs, opts)
 	if err != nil {
 		return errors.New(err)
 	}
 
 	switch opts.Format {
-	case "text":
-		return outputText(opts, configs)
-	case "json":
-		return outputJSON(opts, configs)
+	case FormatText:
+		return outputText(opts, foundCfgs)
+	case FormatJSON:
+		return outputJSON(opts, foundCfgs)
 	default:
 		// This should never happen, because of validation in the command.
 		// If it happens, we want to throw so we can fix the validation.
@@ -36,8 +62,63 @@ func Run(ctx context.Context, opts *Options) error {
 	}
 }
 
+type FoundConfigs []*FoundConfig
+
+type FoundConfig struct {
+	Type discovery.ConfigType `json:"type"`
+	Path string               `json:"path"`
+
+	Dependencies []string `json:"dependencies,omitempty"`
+}
+
+func discoveredToFound(configs discovery.DiscoveredConfigs, opts *Options) (FoundConfigs, error) {
+	foundCfgs := make(FoundConfigs, 0, len(configs))
+	errs := []error{}
+
+	for _, config := range configs {
+		if config.External && !opts.External {
+			continue
+		}
+
+		relPath, err := filepath.Rel(opts.WorkingDir, config.Path)
+		if err != nil {
+			errs = append(errs, errors.New(err))
+
+			continue
+		}
+
+		foundCfg := &FoundConfig{
+			Type: config.Type,
+			Path: relPath,
+		}
+
+		if !opts.Dependencies || len(config.Dependencies) == 0 {
+			foundCfgs = append(foundCfgs, foundCfg)
+
+			continue
+		}
+
+		foundCfg.Dependencies = make([]string, len(config.Dependencies))
+
+		for i, dep := range config.Dependencies {
+			relDepPath, err := filepath.Rel(opts.WorkingDir, dep.Path)
+			if err != nil {
+				errs = append(errs, errors.New(err))
+
+				continue
+			}
+
+			foundCfg.Dependencies[i] = relDepPath
+		}
+
+		foundCfgs = append(foundCfgs, foundCfg)
+	}
+
+	return foundCfgs, errors.Join(errs...)
+}
+
 // outputJSON outputs the discovered configurations in JSON format.
-func outputJSON(opts *Options, configs discovery.DiscoveredConfigs) error {
+func outputJSON(opts *Options, configs FoundConfigs) error {
 	jsonBytes, err := json.MarshalIndent(configs, "", "  ")
 	if err != nil {
 		return errors.New(err)
@@ -67,7 +148,7 @@ func NewColorizer() *Colorizer {
 	}
 }
 
-func (c *Colorizer) Colorize(config *discovery.DiscoveredConfig) string {
+func (c *Colorizer) Colorize(config *FoundConfig) string {
 	path := config.Path
 
 	// Get the directory and base name using filepath
@@ -99,7 +180,7 @@ func (c *Colorizer) Colorize(config *discovery.DiscoveredConfig) string {
 }
 
 // outputText outputs the discovered configurations in text format.
-func outputText(opts *Options, configs discovery.DiscoveredConfigs) error {
+func outputText(opts *Options, configs FoundConfigs) error {
 	if opts.TerragruntOptions.Logger.Formatter().DisabledColors() || isStdoutRedirected() {
 		for _, config := range configs {
 			_, err := opts.Writer.Write([]byte(config.Path + "\n"))
diff --git a/cli/commands/find/command.go b/cli/commands/find/command.go
index 0dbb5ae9..10ba43bf 100644
--- a/cli/commands/find/command.go
+++ b/cli/commands/find/command.go
@@ -18,6 +18,8 @@ const (
 	JSONFlagName   = "json"
 	SortFlagName   = "sort"
 	HiddenFlagName = "hidden"
+	Dependencies   = "dependencies"
+	External       = "external"
 )
 
 func NewFlags(opts *Options, prefix flags.Prefix) cli.Flags {
@@ -28,27 +30,39 @@ func NewFlags(opts *Options, prefix flags.Prefix) cli.Flags {
 			Name:        FormatFlagName,
 			EnvVars:     tgPrefix.EnvVars(FormatFlagName),
 			Destination: &opts.Format,
-			Usage:       "Output format for the find results. Valid values: text, json",
-			DefaultText: "text",
+			Usage:       "Output format for the find results. Valid values: text, json.",
+			DefaultText: FormatText,
 		}),
 		flags.NewFlag(&cli.BoolFlag{
 			Name:        JSONFlagName,
 			EnvVars:     tgPrefix.EnvVars(JSONFlagName),
 			Destination: &opts.JSON,
-			Usage:       "Output in JSON format (equivalent to --format=json)",
+			Usage:       "Output in JSON format (equivalent to --format=json).",
 		}),
 		flags.NewFlag(&cli.GenericFlag[string]{
 			Name:        SortFlagName,
 			EnvVars:     tgPrefix.EnvVars(SortFlagName),
 			Destination: &opts.Sort,
-			Usage:       "Sort order for the find results. Valid values: alpha", // TODO: add dag
-			DefaultText: "alpha",
+			Usage:       "Sort order for the find results. Valid values: alpha, dag.",
+			DefaultText: SortAlpha,
 		}),
 		flags.NewFlag(&cli.BoolFlag{
 			Name:        HiddenFlagName,
 			EnvVars:     tgPrefix.EnvVars(HiddenFlagName),
 			Destination: &opts.Hidden,
-			Usage:       "Include hidden directories in the find results.",
+			Usage:       "Include hidden directories in find results.",
+		}),
+		flags.NewFlag(&cli.BoolFlag{
+			Name:        Dependencies,
+			EnvVars:     tgPrefix.EnvVars(Dependencies),
+			Destination: &opts.Dependencies,
+			Usage:       "Include dependencies in the results.",
+		}),
+		flags.NewFlag(&cli.BoolFlag{
+			Name:        External,
+			EnvVars:     tgPrefix.EnvVars(External),
+			Destination: &opts.External,
+			Usage:       "Discover external dependencies from initial results.",
 		}),
 	}
 }
@@ -68,7 +82,7 @@ func NewCommand(opts *options.TerragruntOptions) *cli.Command {
 			}
 
 			if cmdOpts.JSON {
-				cmdOpts.Format = "json"
+				cmdOpts.Format = FormatJSON
 			}
 
 			if err := cmdOpts.Validate(); err != nil {
diff --git a/cli/commands/find/options.go b/cli/commands/find/options.go
index d7e38428..21b25607 100644
--- a/cli/commands/find/options.go
+++ b/cli/commands/find/options.go
@@ -5,6 +5,20 @@ import (
 	"github.com/gruntwork-io/terragrunt/options"
 )
 
+const (
+	// FormatText outputs the discovered configurations in text format.
+	FormatText = "text"
+
+	// FormatJSON outputs the discovered configurations in JSON format.
+	FormatJSON = "json"
+
+	// SortAlpha sorts the discovered configurations in alphabetical order.
+	SortAlpha = "alpha"
+
+	// SortDAG sorts the discovered configurations in a topological sort order.
+	SortDAG = "dag"
+)
+
 type Options struct {
 	*options.TerragruntOptions
 
@@ -20,13 +34,19 @@ type Options struct {
 
 	// Hidden determines whether to detect hidden directories.
 	Hidden bool
+
+	// Dependencies determines whether to include dependencies in the output.
+	Dependencies bool
+
+	// External determines whether to include external dependencies in the output.
+	External bool
 }
 
 func NewOptions(opts *options.TerragruntOptions) *Options {
 	return &Options{
 		TerragruntOptions: opts,
-		Format:            "text",
-		Sort:              "alpha",
+		Format:            FormatText,
+		Sort:              SortAlpha,
 		Hidden:            false,
 	}
 }
@@ -51,9 +71,9 @@ func (o *Options) Validate() error {
 
 func (o *Options) validateFormat() error {
 	switch o.Format {
-	case "text":
+	case FormatText:
 		return nil
-	case "json":
+	case FormatJSON:
 		return nil
 	default:
 		return errors.New("invalid format: " + o.Format)
@@ -62,7 +82,9 @@ func (o *Options) validateFormat() error {
 
 func (o *Options) validateSort() error {
 	switch o.Sort {
-	case "alpha":
+	case SortAlpha:
+		return nil
+	case SortDAG:
 		return nil
 	default:
 		return errors.New("invalid sort: " + o.Sort)
diff --git a/cli/commands/stack/action.go b/cli/commands/stack/action.go
index f5b769e1..46e7e0d4 100644
--- a/cli/commands/stack/action.go
+++ b/cli/commands/stack/action.go
@@ -26,7 +26,10 @@ func RunGenerate(ctx context.Context, opts *options.TerragruntOptions) error {
 		return err
 	}
 
-	return generateStack(ctx, opts)
+	opts.TerragruntStackConfigPath = filepath.Join(opts.WorkingDir, config.DefaultStackFile)
+	opts.Logger.Infof("Generating stack from %s", opts.TerragruntStackConfigPath)
+
+	return config.GenerateStacks(ctx, opts)
 }
 
 // Run execute stack command.
diff --git a/cli/commands/stack/command.go b/cli/commands/stack/command.go
index 8976c432..4302dfca 100644
--- a/cli/commands/stack/command.go
+++ b/cli/commands/stack/command.go
@@ -25,26 +25,20 @@ const (
 	jsonOutputFormat = "json"
 )
 
-// NewFlags builds the flags for stack.
-func NewFlags(_ *options.TerragruntOptions) cli.Flags {
-	return cli.Flags{}
-}
-
 // NewCommand builds the command for stack.
 func NewCommand(opts *options.TerragruntOptions) *cli.Command {
 	return &cli.Command{
 		Name:                 CommandName,
 		Usage:                "Terragrunt stack commands.",
 		ErrorOnUndefinedFlag: true,
-		Flags:                NewFlags(opts).Sort(),
 		Subcommands: cli.Commands{
 			&cli.Command{
 				Name:  generateCommandName,
 				Usage: "Generate a stack from a terragrunt.stack.hcl file",
 				Action: func(ctx *cli.Context) error {
 					return RunGenerate(ctx.Context, opts.OptionsFromContext(ctx))
-
 				},
+				Flags: defaultFlags(opts).Sort(),
 			},
 			&cli.Command{
 				Name:  runCommandName,
@@ -52,7 +46,7 @@ func NewCommand(opts *options.TerragruntOptions) *cli.Command {
 				Action: func(ctx *cli.Context) error {
 					return Run(ctx.Context, opts.OptionsFromContext(ctx))
 				},
-				Flags: runFlags(opts).Sort(),
+				Flags: defaultFlags(opts).Sort(),
 			},
 			&cli.Command{
 				Name:  outputCommandName,
@@ -81,7 +75,7 @@ func NewCommand(opts *options.TerragruntOptions) *cli.Command {
 func outputFlags(opts *options.TerragruntOptions, prefix flags.Prefix) cli.Flags {
 	tgPrefix := prefix.Prepend(flags.TgPrefix)
 
-	return cli.Flags{
+	flags := cli.Flags{
 		flags.NewFlag(&cli.GenericFlag[string]{
 			Name:        OutputFormatFlagName,
 			EnvVars:     tgPrefix.EnvVars(OutputFormatFlagName),
@@ -105,9 +99,11 @@ func outputFlags(opts *options.TerragruntOptions, prefix flags.Prefix) cli.Flags
 			},
 		}),
 	}
+
+	return append(defaultFlags(opts), flags...)
 }
 
-func runFlags(opts *options.TerragruntOptions) cli.Flags {
+func defaultFlags(opts *options.TerragruntOptions) cli.Flags {
 	flags := runall.NewFlags(opts, runCommandName, nil).Filter(runall.OutDirFlagName, runall.JSONOutDirFlagName)
 	flags = append(flags, run.NewFlags(opts, nil)...)
 
diff --git a/cli/commands/stack/generate.go b/cli/commands/stack/generate.go
deleted file mode 100644
index 90620e6f..00000000
--- a/cli/commands/stack/generate.go
+++ /dev/null
@@ -1,16 +0,0 @@
-package stack
-
-import (
-	"context"
-	"path/filepath"
-
-	"github.com/gruntwork-io/terragrunt/config"
-	"github.com/gruntwork-io/terragrunt/options"
-)
-
-func generateStack(ctx context.Context, opts *options.TerragruntOptions) error {
-	opts.TerragruntStackConfigPath = filepath.Join(opts.WorkingDir, config.DefaultStackFile)
-	opts.Logger.Infof("Generating stack from %s", opts.TerragruntStackConfigPath)
-
-	return config.GenerateStacks(ctx, opts)
-}
diff --git a/config/stack.go b/config/stack.go
index 770cd43d..7c5d600d 100644
--- a/config/stack.go
+++ b/config/stack.go
@@ -195,7 +195,7 @@ func generateUnits(ctx context.Context, opts *options.TerragruntOptions, pool *u
 		unitCopy := unit // Create a copy to avoid capturing the loop variable reference
 
 		pool.Submit(func() error {
-			item := itemToProcess{
+			item := componentToProcess{
 				sourceDir: sourceDir,
 				targetDir: targetDir,
 				name:      unitCopy.Name,
@@ -206,7 +206,7 @@ func generateUnits(ctx context.Context, opts *options.TerragruntOptions, pool *u
 
 			opts.Logger.Infof("Processing unit %s", unitCopy.Name)
 
-			if err := processItem(ctx, opts, &item); err != nil {
+			if err := processComponent(ctx, opts, &item); err != nil {
 				return err
 			}
 
@@ -224,7 +224,7 @@ func generateStacks(ctx context.Context, opts *options.TerragruntOptions, pool *
 		stackCopy := stack // Create a copy to avoid capturing the loop variable reference
 
 		pool.Submit(func() error {
-			item := itemToProcess{
+			item := componentToProcess{
 				sourceDir: sourceDir,
 				targetDir: targetDir,
 				name:      stackCopy.Name,
@@ -235,7 +235,7 @@ func generateStacks(ctx context.Context, opts *options.TerragruntOptions, pool *
 
 			opts.Logger.Infof("Processing stack %s", stackCopy.Name)
 
-			if err := processItem(ctx, opts, &item); err != nil {
+			if err := processComponent(ctx, opts, &item); err != nil {
 				return err
 			}
 
@@ -246,7 +246,10 @@ func generateStacks(ctx context.Context, opts *options.TerragruntOptions, pool *
 	return nil
 }
 
-type itemToProcess struct {
+// componentToProcess represents an item of work for processing a stack or unit.
+// It contains information about the source and target directories, the name and path of the item, the source URL or path,
+// and any associated values that need to be processed.
+type componentToProcess struct {
 	sourceDir string
 	targetDir string
 	name      string
@@ -255,24 +258,31 @@ type itemToProcess struct {
 	values    *cty.Value
 }
 
-// processItem copies files from the source directory to the target destination and generates a corresponding values file.
-func processItem(ctx context.Context, opts *options.TerragruntOptions, item *itemToProcess) error {
-	destPath := filepath.Join(item.targetDir, item.path)
-	dest, err := filepath.Abs(destPath)
+// processComponent copies files from the source directory to the target destination and generates a corresponding values file.
+func processComponent(ctx context.Context, opts *options.TerragruntOptions, cmp *componentToProcess) error {
+	source := cmp.source
+	// Adjust source path using the provided source mapping configuration if available
+	source, err := adjustSourceWithMap(opts.SourceMap, source, opts.TerragruntStackConfigPath)
 
 	if err != nil {
-		return errors.Errorf("failed to get absolute path for destination '%s': %v", dest, err)
+		return errors.Errorf("failed to adjust source %s: %v", cmp.source, err)
 	}
 
-	opts.Logger.Debugf("Processing: %s (%s) to %s", item.name, item.source, dest)
+	dest := cmp.path
+	// if destination is an absolute path, use as is
+	if !filepath.IsAbs(cmp.path) {
+		dest = filepath.Join(cmp.targetDir, cmp.path)
+	}
+
+	opts.Logger.Debugf("Processing: %s (%s) to %s", cmp.name, source, dest)
 
-	if err := copyFiles(ctx, opts, item.name, item.sourceDir, item.source, dest); err != nil {
+	if err := copyFiles(ctx, opts, cmp.name, cmp.sourceDir, source, dest); err != nil {
 		return err
 	}
 
 	// generate values file
-	if err := writeValues(opts, item.values, dest); err != nil {
-		return errors.Errorf("failed to write values %v %v", item.name, err)
+	if err := writeValues(opts, cmp.values, dest); err != nil {
+		return errors.Errorf("failed to write values %v %v", cmp.name, err)
 	}
 
 	return nil
diff --git a/docs-starlight/src/content/docs/04-reference/04-experiments.md b/docs-starlight/src/content/docs/04-reference/04-experiments.md
index 2cc6a559..29693eb0 100644
--- a/docs-starlight/src/content/docs/04-reference/04-experiments.md
+++ b/docs-starlight/src/content/docs/04-reference/04-experiments.md
@@ -70,6 +70,7 @@ The following strict mode controls are available:
 - [symlinks](#symlinks)
 - [stacks](#stacks)
 - [cli-redesign](#cli-redesign)
+- [cas](#cas)
 
 ### symlinks
 
@@ -162,9 +163,10 @@ To transition `cli-redesign` features to a stable release, the following must be
   - [x] Add support for `find` with stdout redirection detection.
   - [x] Add support for `find` with `--hidden` flag.
   - [x] Add support for `find` with `--sort=alpha` flag.
-  - [ ] Add support for `find` with `--sort=dag` flag.
+  - [x] Add support for `find` with `--sort=dag` flag.
   - [ ] Add support for `find` with the `exclude` block used to exclude units from the search.
   - [ ] Add integration with `symlinks` experiment to support finding units/stacks via symlinks.
+  - [ ] Add integration test for `find` with `--sort=dag` flag on all the fixtures in the `test/fixtures` directory.
 - [ ] Add support for the `list` command.
 
 ### `cas`
diff --git a/docs-starlight/src/data/commands/find.mdx b/docs-starlight/src/data/commands/find.mdx
index 3683a641..0cecce25 100644
--- a/docs-starlight/src/data/commands/find.mdx
+++ b/docs-starlight/src/data/commands/find.mdx
@@ -33,15 +33,25 @@ examples:
       Find all configurations and output them in JSON format (alias for --format=json).
     code: |
       terragrunt find --json
-  # - description: |
-  #     Find all units and sort them in the order they would be run in the Directed Acyclic Graph (DAG).
-  #   code: |
-  #     terragrunt find --sort 'dag'
+  - description: |
+      Sort configurations based on their dependencies (DAG order).
+    code: |
+      terragrunt find --sort 'dag'
+  - description: |
+      Include dependency information in the output.
+    code: |
+      terragrunt find --dependencies --format 'json'
+  - description: |
+      Include external dependencies in the output.
+    code: |
+      terragrunt find --dependencies --external --format 'json'
 flags:
   - find-format
   - find-json
-  # - find-sort
+  - find-sort
   - find-hidden
+  - find-dependencies
+  - find-external
 ---
 
 import { Aside, Badge } from '@astrojs/starlight/components';
@@ -74,7 +84,7 @@ The default text format displays each configuration on a new line, with color co
 You can output the results in JSON format using either:
 
 ```bash
-terragrunt find --format json
+terragrunt find --format=json
 ```
 
 or the shorter alias:
@@ -85,6 +95,68 @@ terragrunt find --json
 
 The JSON output includes additional metadata about each configuration, such as its type (unit or stack) and path.
 
+## Sorting
+
+The `find` command supports two sorting modes:
+
+- `alpha` (default): Sort configurations alphabetically by path
+- `dag`: Sort configurations based on their dependencies, ensuring dependencies are listed before their dependents
+
+When using DAG sorting, configurations with no dependencies appear first, followed by configurations that depend on them:
+
+```bash
+terragrunt find --sort=dag
+unitA           # no dependencies
+unitB           # no dependencies
+unitC           # depends on unitA
+unitD           # depends on unitA and unitB
+```
+
+If multiple configurations share common dependencies, they will be sorted in lexical order.
+
+## Dependencies
+
+You can include dependency information in the output using the `--dependencies` flag. When enabled, the JSON output will include the dependency relationships between configurations:
+
+```bash
+terragrunt find --dependencies --format=json
+[
+  {
+    "type": "unit",
+    "path": "unitA",
+    "dependencies": []
+  },
+  {
+    "type": "unit",
+    "path": "unitB",
+    "dependencies": ["../unitA", "../../external/unitC"]
+  }
+]
+```
+
+By default, external dependencies (those outside the working directory) are not part of the overall results (although, they will be mentioned in the dependency section of the JSON output). Use the `--external` flag to include them as top-level results:
+
+```bash
+terragrunt find --dependencies --external --format=json
+[
+  {
+    "type": "unit",
+    "path": "internal/unitA",
+    "dependencies": []
+  },
+  {
+    "type": "unit",
+    "path": "internal/unitB",
+    "dependencies": ["../unitA", "../../external/unitC"]
+  },
+  {
+    "type": "unit",
+    "path": "external/unitC",
+    "dependencies": []
+  }
+]
+```
+
 ## Disabling Color Output
 
 You can disable color output by using the global `--no-color` flag:
@@ -102,5 +174,5 @@ When stdout is redirected, color output is disabled automatically to prevent und
 You can change the working directory for `find` by using the global `--working-dir` flag:
 
 ```bash
-terragrunt find --working-dir /path/to/working/dir
+terragrunt find --working-dir=/path/to/working/dir
 ```
diff --git a/docs-starlight/src/data/flags/find-dependencies.mdx b/docs-starlight/src/data/flags/find-dependencies.mdx
new file mode 100644
index 00000000..0a1180f9
--- /dev/null
+++ b/docs-starlight/src/data/flags/find-dependencies.mdx
@@ -0,0 +1,27 @@
+---
+name: dependencies
+description: Include dependency information in the output.
+type: bool
+env:
+  - TG_DEPENDENCIES
+---
+
+When enabled, the output will include information about dependencies between configurations. This is particularly useful when combined with JSON output format to understand the dependency relationships in your codebase.
+
+Example:
+
+```bash
+terragrunt find --dependencies --format json
+[
+  {
+    "type": "unit",
+    "path": "unitA",
+    "dependencies": []
+  },
+  {
+    "type": "unit",
+    "path": "unitB",
+    "dependencies": ["../unitA"]
+  }
+]
+```
diff --git a/docs-starlight/src/data/flags/find-external.mdx b/docs-starlight/src/data/flags/find-external.mdx
new file mode 100644
index 00000000..e3dfb7de
--- /dev/null
+++ b/docs-starlight/src/data/flags/find-external.mdx
@@ -0,0 +1,32 @@
+---
+name: external
+description: Include external dependencies in the output.
+type: bool
+env:
+  - TG_EXTERNAL
+---
+
+When enabled, units outside the working directory can be included as part of the output, if any unit depends on them. This is useful when you need to understand all dependency relationships, including those that don't exist in the current directory.
+
+Example:
+
+```bash
+terragrunt find --dependencies --external --format json
+[
+  {
+    "type": "unit",
+    "path": "internal/unitA",
+    "dependencies": []
+  },
+  {
+    "type": "unit",
+    "path": "internal/unitB",
+    "dependencies": ["../unitA", "../../external/unitC"]
+  },
+  {
+    "type": "unit",
+    "path": "external/unitC",
+    "dependencies": []
+  }
+]
+```
diff --git a/docs-starlight/src/data/flags/find-sort.mdx b/docs-starlight/src/data/flags/find-sort.mdx
index 9531f029..acb661bf 100644
--- a/docs-starlight/src/data/flags/find-sort.mdx
+++ b/docs-starlight/src/data/flags/find-sort.mdx
@@ -1,17 +1,34 @@
 ---
 name: sort
-description: Sort results in the specified order. Supported values (dag). default dag.
+description: Sort results in the specified order. Supported values (alpha, dag). Default alpha.
 type: string
 env:
   - TG_SORT
 ---
 
-Controls how the find results are sorted. Currently, the only supported value is `dag` (Directed Acyclic Graph), which sorts units based on their dependencies.
+Controls how the find results are sorted:
 
-This is particularly useful when you need to understand the dependency order of your Terragrunt units.
+- `alpha` (default): Sort configurations alphabetically by path
+- `dag`: Sort configurations based on their dependencies, ensuring dependencies are listed before their dependents
+
+This is particularly useful when you need to understand the dependency order of your Terragrunt configurations.
 
 Example:
 
+```bash
+# Sort alphabetically (default)
+terragrunt find --sort alpha
+
+# Sort by dependencies (DAG order)
+terragrunt find --sort dag
+```
+
+When using DAG sorting, configurations with no dependencies appear first, followed by configurations that depend on them:
+
 ```bash
 terragrunt find --sort dag
+unitA           # no dependencies
+unitB           # no dependencies
+unitC           # depends on unitA
+unitD           # depends on unitA and unitB
 ```
diff --git a/docs/_docs/04_reference/02-cli-options.md b/docs/_docs/04_reference/02-cli-options.md
index 63c0a30e..32b7cefc 100644
--- a/docs/_docs/04_reference/02-cli-options.md
+++ b/docs/_docs/04_reference/02-cli-options.md
@@ -588,6 +588,8 @@ You can change the working directory used by the command by using the `--working
 terragrunt find --working-dir /path/to/working/dir
 ```
 
+##### Find Output Format
+
 In JSON format, `find` outputs a structured representation of the discovered configurations, including their types and path relative to the working directory.
 
 ```bash
@@ -614,6 +616,70 @@ Note that you can also use the `--json` flag to get the same output.
 terragrunt find --json
 ```
 
+##### Find Sorting
+
+The `find` command supports two sorting modes:
+
+- `--sort=alpha` (default): Sort configurations alphabetically by path
+- `--sort=dag`: Sort configurations based on their dependencies, ensuring dependencies are listed before their dependents
+
+When using DAG sorting, configurations with no dependencies appear first, followed by configurations that depend on them, maintaining the correct dependency order.
+
+```bash
+$ terragrunt find --sort=dag
+moduleA           # no dependencies
+moduleB           # no dependencies
+moduleC           # depends on moduleA
+moduleD           # depends on moduleA and moduleB
+```
+
+##### Find Dependencies
+
+You can include dependency information in the output using the `--dependencies` flag. When enabled, the JSON output will include the dependency relationships between configurations:
+
+```bash
+$ terragrunt find --dependencies --format=json | jq
+[
+  {
+    "type": "unit",
+    "path": "moduleA",
+    "dependencies": []
+  },
+  {
+    "type": "unit",
+    "path": "moduleB",
+    "dependencies": ["../moduleA"]
+  }
+]
+```
+
+##### Find External Dependencies
+
+By default, external dependencies (those outside the working directory) are excluded. Use the `--external` flag to include them in the output:
+
+```bash
+$ terragrunt find --dependencies --external --format=json | jq
+[
+  {
+    "type": "unit",
+    "path": "internal/moduleA",
+    "dependencies": []
+  },
+  {
+    "type": "unit",
+    "path": "internal/moduleB",
+    "dependencies": ["../moduleA", "../../external/moduleC"]
+  },
+  {
+    "type": "unit",
+    "path": "external/moduleC",
+    "dependencies": []
+  }
+]
+```
+
+##### Find Hidden Configurations
+
 By default, hidden directories (those starting with `.`) are excluded from the search. Use `--hidden` to include them.
 
 ```bash
@@ -899,6 +965,11 @@ This command will exit with an error if terragrunt detects any unused inputs or
     - [scaffold](#scaffold)
   - [Discovery commands](#discovery-commands)
     - [find](#find)
+      - [Find Output Format](#find-output-format)
+      - [Find Sorting](#find-sorting)
+      - [Find Dependencies](#find-dependencies)
+      - [Find External Dependencies](#find-external-dependencies)
+      - [Find Hidden Configurations](#find-hidden-configurations)
   - [Configuration commands](#configuration-commands)
     - [graph-dependencies](#graph-dependencies)
     - [hclfmt](#hclfmt)
@@ -971,7 +1042,6 @@ This command will exit with an error if terragrunt detects any unused inputs or
   - [feature](#feature)
   - [experiment](#experiment)
   - [experiment-mode](#experiment-mode)
-  - [feature](#feature-1)
   - [strict-control](#strict-control)
   - [strict-mode](#strict-mode)
   - [in-download-dir](#in-download-dir)
@@ -2008,57 +2078,6 @@ Enable all experimental features in Terragrunt before they're stable.
 
 For more information, see the [Experiments](/docs/reference/experiments) documentation.
 
-### feature
-
-**CLI Arg**: `--feature`<br/>
-**Environment Variable**: `TERRAGRUNT_FEATURE`<br/>
-
-Feature flags in Terragrunt allow users to dynamically control configuration behavior through CLI arguments or environment variables.
-
-These flags enable a more flexible and controlled deployment process, particularly in monorepo contexts with interdependent infrastructure units.
-
-Example HCL flags definition:
-
-```hcl
-feature "string_feature_flag" {
-  default = "test"
-}
-
-feature "int_feature_flag" {
-  default = 777
-}
-
-feature "bool_feature_flag" {
-  default = false
-}
-
-terraform {
-  before_hook "conditional_command" {
-    commands = ["apply", "plan", "destroy"]
-    execute  = feature.bool_feature_flag.value ? ["sh", "-c", "echo running conditional bool_feature_flag"] : [ "sh", "-c", "exit", "0" ]
-  }
-}
-
-inputs = {
-  string_feature_flag = feature.string_feature_flag.value
-  int_feature_flag = feature.int_feature_flag.value
-}
-
-```
-
-Setting a feature flag through the CLI:
-
-```bash
-terragrunt --feature int_feature_flag=123 --feature bool_feature_flag=true --feature string_feature_flag=app1 apply
-```
-
-Setting feature flags through environment variables:
-
-```bash
-export TERRAGRUNT_FEATURE=int_feature_flag=123,bool_feature_flag=true,string_feature_flag=app1
-terragrunt apply
-```
-
 ### strict-control
 
 **CLI Arg**: `--strict-control`<br/>
diff --git a/docs/_docs/04_reference/04-config-blocks-and-attributes.md b/docs/_docs/04_reference/04-config-blocks-and-attributes.md
index 13985801..3ea8aad0 100644
--- a/docs/_docs/04_reference/04-config-blocks-and-attributes.md
+++ b/docs/_docs/04_reference/04-config-blocks-and-attributes.md
@@ -1653,7 +1653,7 @@ The `unit` block supports the following arguments:
 
 - `name` (label): A unique identifier for the unit. This is used to reference the unit elsewhere in your configuration.
 - `source` (attribute): Specifies where to find the Terragrunt configuration files for this unit. This follows the same syntax as the `source` parameter in the `terraform` block.
-- `path` (attribute): The relative path where this unit should be deployed within the stack directory (`.terragrunt-stack`).
+- `path` (attribute): The relative path where this unit should be deployed within the stack directory (`.terragrunt-stack`). If an absolute path is provided here, Terragrunt will generate the stack in that location, instead of generating it in a path relative to the `.terragrunt-stack` directory.
 - `values` (attribute, optional): A map of values that will be passed to the unit as inputs.
 
 Example:
@@ -1703,6 +1703,9 @@ inputs = {
 }
 ```
 
+**Note:**  
+The `source` value can be updated dynamically using the `--source-map` flag, just like `terraform.source`.
+
 ### stack
 
 > **Note:**
@@ -1748,6 +1751,9 @@ In this example, the `services` stack is defined with path `services`, which wil
 The stack is also provided with custom values for `project` and `cidr`, which can be used within the stack's configuration files.
 Terragrunt will recursively generate a stack using the contents of the `.terragrunt-stack/services/terragrunt.stack.hcl` file until the entire stack is fully generated.
 
+**Note:**  
+The `source` value can be updated dynamically using the `--source-map` flag, just like `terraform.source`.
+
 ## Attributes
 
 - [Blocks](#blocks)
diff --git a/docs/_docs/04_reference/06-experiments.md b/docs/_docs/04_reference/06-experiments.md
index 2821b340..f719e3e7 100644
--- a/docs/_docs/04_reference/06-experiments.md
+++ b/docs/_docs/04_reference/06-experiments.md
@@ -68,6 +68,7 @@ The following strict mode controls are available:
 - [symlinks](#symlinks)
 - [stacks](#stacks)
 - [cli-redesign](#cli-redesign)
+- [cas](#cas)
 
 ### `symlinks`
 
@@ -162,9 +163,10 @@ To transition `cli-redesign` features to a stable release, the following must be
   - [x] Add support for `find` with stdout redirection detection.
   - [x] Add support for `find` with `--hidden` flag.
   - [x] Add support for `find` with `--sort=alpha` flag.
-  - [ ] Add support for `find` with `--sort=dag` flag.
+  - [x] Add support for `find` with `--sort=dag` flag.
   - [ ] Add support for `find` with the `exclude` block used to exclude units from the search.
   - [ ] Add integration with `symlinks` experiment to support finding units/stacks via symlinks.
+  - [ ] Add integration test for `find` with `--sort=dag` flag on all the fixtures in the `test/fixtures` directory.
 - [ ] Add support for the `list` command.
 
 ### `cas`
diff --git a/internal/discovery/discovery.go b/internal/discovery/discovery.go
index 91d46e61..850e89ae 100644
--- a/internal/discovery/discovery.go
+++ b/internal/discovery/discovery.go
@@ -2,6 +2,7 @@
 package discovery
 
 import (
+	"context"
 	"io/fs"
 	"os"
 	"path/filepath"
@@ -10,20 +11,38 @@ import (
 
 	"github.com/gruntwork-io/terragrunt/config"
 	"github.com/gruntwork-io/terragrunt/internal/errors"
+	"github.com/gruntwork-io/terragrunt/options"
 )
 
 const (
-	ConfigTypeUnit  ConfigType = "unit"
+	// ConfigTypeUnit is the type of Terragrunt configuration for a unit.
+	ConfigTypeUnit ConfigType = "unit"
+	// ConfigTypeStack is the type of Terragrunt configuration for a stack.
 	ConfigTypeStack ConfigType = "stack"
 )
 
 // ConfigType is the type of Terragrunt configuration.
 type ConfigType string
 
+// Sort is the sort order of the discovered configurations.
+type Sort string
+
+// Exclude is the exclude configuration for a discovered configuration.
+type Exclude struct {
+	If                  string
+	Actions             []string
+	ExcludeDependencies bool
+}
+
 // DiscoveredConfig represents a discovered Terragrunt configuration.
 type DiscoveredConfig struct {
-	Type ConfigType `json:"type"`
-	Path string     `json:"path"`
+	Type ConfigType
+	Path string
+
+	Dependencies DiscoveredConfigs
+	Exclude      Exclude
+
+	External bool
 }
 
 // DiscoveredConfigs is a list of discovered Terragrunt configurations.
@@ -31,11 +50,26 @@ type DiscoveredConfigs []*DiscoveredConfig
 
 // Discovery is the configuration for a Terragrunt discovery.
 type Discovery struct {
-	// WorkingDir is the directory to search for Terragrunt configurations.
-	WorkingDir string
+	// workingDir is the directory to search for Terragrunt configurations.
+	workingDir string
+
+	// hidden determines whether to detect configurations in hidden directories.
+	hidden bool
+
+	// sort determines the sort order of the discovered configurations.
+	sort Sort
+
+	// discoverDependencies determines whether to discover dependencies.
+	discoverDependencies bool
+
+	// discoverExternalDependencies determines whether to discover external dependencies.
+	discoverExternalDependencies bool
 
-	// Hidden determines whether to detect configurations in hidden directories.
-	Hidden bool
+	// maxDependencyDepth is the maximum depth of the dependency tree to discover.
+	maxDependencyDepth int
+
+	// hiddenDirMemo is a memoization of hidden directories.
+	hiddenDirMemo []string
 }
 
 // DiscoveryOption is a function that modifies a Discovery.
@@ -44,8 +78,8 @@ type DiscoveryOption func(*Discovery)
 // NewDiscovery creates a new Discovery.
 func NewDiscovery(dir string, opts ...DiscoveryOption) *Discovery {
 	discovery := &Discovery{
-		WorkingDir: dir,
-		Hidden:     false,
+		workingDir: dir,
+		hidden:     false,
 	}
 
 	for _, opt := range opts {
@@ -55,28 +89,93 @@ func NewDiscovery(dir string, opts ...DiscoveryOption) *Discovery {
 	return discovery
 }
 
-// NewDiscoverySettings creates a new Discovery with default settings.
-func NewDiscoverySettings() *Discovery {
-	return &Discovery{
-		Hidden: false,
+// WithHidden sets the Hidden flag to true.
+func (d *Discovery) WithHidden() *Discovery {
+	d.hidden = true
+
+	return d
+}
+
+// WithSort sets the Sort flag to the given sort.
+func (d *Discovery) WithSort(sort Sort) *Discovery {
+	d.sort = sort
+
+	return d
+}
+
+// WithDiscoverDependencies sets the DiscoverDependencies flag to true.
+func (d *Discovery) WithDiscoverDependencies() *Discovery {
+	d.discoverDependencies = true
+
+	if d.maxDependencyDepth == 0 {
+		d.maxDependencyDepth = 1000
 	}
+
+	return d
 }
 
-// WithHidden sets the Hidden flag to true.
-func (d *Discovery) WithHidden() *Discovery {
-	d.Hidden = true
+// WithMaxDependencyDepth sets the MaxDependencyDepth flag to the given depth.
+func (d *Discovery) WithMaxDependencyDepth(depth int) *Discovery {
+	d.maxDependencyDepth = depth
+
+	return d
+}
+
+// WithDiscoverExternalDependencies sets the DiscoverExternalDependencies flag to true.
+func (d *Discovery) WithDiscoverExternalDependencies() *Discovery {
+	d.discoverExternalDependencies = true
 
 	return d
 }
 
 // String returns a string representation of a DiscoveredConfig.
 func (c *DiscoveredConfig) String() string {
-	return string(c.Type) + ": " + c.Path
+	return c.Path
+}
+
+// ContainsDependencyInAncestry returns true if the DiscoveredConfig or any of
+// its dependencies contains the given path as a dependency.
+func (c *DiscoveredConfig) ContainsDependencyInAncestry(path string) bool {
+	for _, dep := range c.Dependencies {
+		if dep.Path == path {
+			return true
+		}
+
+		if dep.ContainsDependencyInAncestry(path) {
+			return true
+		}
+	}
+
+	return false
+}
+
+// isInHiddenDirectory returns true if the path is in a hidden directory.
+func (d *Discovery) isInHiddenDirectory(path string) bool {
+	for _, hiddenDir := range d.hiddenDirMemo {
+		if strings.HasPrefix(path, hiddenDir) {
+			return true
+		}
+	}
+
+	hiddenPath := ""
+
+	parts := strings.Split(path, string(os.PathSeparator))
+	for _, part := range parts {
+		hiddenPath = filepath.Join(hiddenPath, part)
+
+		if strings.HasPrefix(part, ".") {
+			d.hiddenDirMemo = append(d.hiddenDirMemo, hiddenPath)
+
+			return true
+		}
+	}
+
+	return false
 }
 
 // Discover discovers Terragrunt configurations in the WorkingDir.
-func (d *Discovery) Discover() (DiscoveredConfigs, error) {
-	var units DiscoveredConfigs
+func (d *Discovery) Discover(ctx context.Context, opts *options.TerragruntOptions) (DiscoveredConfigs, error) {
+	var cfgs DiscoveredConfigs
 
 	walkFn := func(path string, e fs.DirEntry, err error) error {
 		if err != nil {
@@ -87,23 +186,18 @@ func (d *Discovery) Discover() (DiscoveredConfigs, error) {
 			return nil
 		}
 
-		path, err = filepath.Rel(d.WorkingDir, path)
-		if err != nil {
-			return errors.New(err)
-		}
-
-		if !d.Hidden && isInHiddenDirectory(path) {
+		if !d.hidden && d.isInHiddenDirectory(path) {
 			return nil
 		}
 
 		switch filepath.Base(path) {
 		case config.DefaultTerragruntConfigPath:
-			units = append(units, &DiscoveredConfig{
+			cfgs = append(cfgs, &DiscoveredConfig{
 				Type: ConfigTypeUnit,
 				Path: filepath.Dir(path),
 			})
 		case config.DefaultStackFile:
-			units = append(units, &DiscoveredConfig{
+			cfgs = append(cfgs, &DiscoveredConfig{
 				Type: ConfigTypeStack,
 				Path: filepath.Dir(path),
 			})
@@ -112,11 +206,162 @@ func (d *Discovery) Discover() (DiscoveredConfigs, error) {
 		return nil
 	}
 
-	if err := filepath.WalkDir(d.WorkingDir, walkFn); err != nil {
-		return nil, errors.New(err)
+	if err := filepath.WalkDir(d.workingDir, walkFn); err != nil {
+		return cfgs, errors.New(err)
+	}
+
+	if d.discoverDependencies {
+		dependencyDiscovery := NewDependencyDiscovery(cfgs, d.maxDependencyDepth, d.discoverExternalDependencies)
+
+		err := dependencyDiscovery.DiscoverAllDependencies(ctx, opts)
+		if err != nil {
+			return dependencyDiscovery.cfgs, errors.New(err)
+		}
+
+		cfgs = dependencyDiscovery.cfgs
+
+		if err := cfgs.CycleCheck(); err != nil {
+			return cfgs, errors.New(err)
+		}
+	}
+
+	return cfgs, nil
+}
+
+type DependencyDiscovery struct {
+	cfgs             DiscoveredConfigs
+	depthRemaining   int
+	discoverExternal bool
+}
+
+func NewDependencyDiscovery(cfgs DiscoveredConfigs, depthRemaining int, discoverExternal bool) *DependencyDiscovery {
+	return &DependencyDiscovery{
+		cfgs:             cfgs,
+		depthRemaining:   depthRemaining,
+		discoverExternal: discoverExternal,
+	}
+}
+
+func (d *DependencyDiscovery) DiscoverAllDependencies(ctx context.Context, opts *options.TerragruntOptions) error {
+	errs := []error{}
+
+	for _, cfg := range d.cfgs {
+		if cfg.Type == ConfigTypeStack {
+			continue
+		}
+
+		err := d.DiscoverDependencies(ctx, opts, cfg)
+		if err != nil {
+			errs = append(errs, errors.New(err))
+		}
+	}
+
+	if len(errs) > 0 {
+		return errors.Join(errs...)
+	}
+
+	return nil
+}
+
+func (d *DependencyDiscovery) DiscoverDependencies(ctx context.Context, opts *options.TerragruntOptions, dCfg *DiscoveredConfig) error {
+	if d.depthRemaining <= 0 {
+		return errors.New("max dependency depth reached while discovering dependencies")
+	}
+
+	opts = opts.Clone()
+	opts.WorkingDir = dCfg.Path
+
+	if dCfg.Type == ConfigTypeUnit {
+		opts.TerragruntConfigPath = filepath.Join(opts.WorkingDir, config.DefaultTerragruntConfigPath)
+	}
+
+	parsingCtx := config.NewParsingContext(ctx, opts).WithDecodeList(
+		config.DependenciesBlock,
+		config.DependencyBlock,
+		config.FeatureFlagsBlock,
+		config.ExcludeBlock,
+	)
+
+	//nolint: contextcheck
+	cfg, err := config.PartialParseConfigFile(parsingCtx, opts.TerragruntConfigPath, nil)
+	if err != nil {
+		return errors.New(err)
+	}
+
+	dependencyBlocks := cfg.TerragruntDependencies
+
+	depPaths := make([]string, 0, len(dependencyBlocks))
+
+	errs := []error{}
+
+	for _, dependency := range dependencyBlocks {
+		depPath := dependency.ConfigPath.AsString()
+
+		if !filepath.IsAbs(depPath) {
+			depPath = filepath.Join(opts.WorkingDir, depPath)
+		}
+
+		depPaths = append(depPaths, depPath)
+	}
+
+	if cfg.Dependencies != nil {
+		for _, dependency := range cfg.Dependencies.Paths {
+			if !filepath.IsAbs(dependency) {
+				dependency = filepath.Join(opts.WorkingDir, dependency)
+			}
+
+			depPaths = append(depPaths, dependency)
+		}
+	}
+
+	if len(errs) > 0 {
+		return errors.Join(errs...)
 	}
 
-	return units, nil
+	deduped := make(map[string]struct{}, len(depPaths))
+
+	for _, depPath := range depPaths {
+		deduped[depPath] = struct{}{}
+	}
+
+	for depPath := range deduped {
+		external := true
+
+		for _, c := range d.cfgs {
+			if c.Path == depPath {
+				external = false
+
+				dCfg.Dependencies = append(dCfg.Dependencies, c)
+
+				continue
+			}
+		}
+
+		if external {
+			ext := &DiscoveredConfig{
+				Type:     ConfigTypeUnit,
+				Path:     depPath,
+				External: true,
+			}
+
+			dCfg.Dependencies = append(dCfg.Dependencies, ext)
+
+			if d.discoverExternal {
+				d.cfgs = append(d.cfgs, ext)
+
+				err := d.DiscoverDependencies(ctx, opts, ext)
+				if err != nil {
+					errs = append(errs, errors.New(err))
+				}
+			}
+		}
+	}
+
+	if len(errs) > 0 {
+		return errors.Join(errs...)
+	}
+
+	return nil
 }
 
 // Sort sorts the DiscoveredConfigs by path.
@@ -164,14 +409,43 @@ func (c DiscoveredConfigs) Paths() []string {
 	return paths
 }
 
-// isInHiddenDirectory returns true if the path is in a hidden directory.
-func isInHiddenDirectory(path string) bool {
-	parts := strings.Split(path, string(os.PathSeparator))
-	for _, part := range parts {
-		if strings.HasPrefix(part, ".") {
-			return true
+// CycleCheck checks for cycles in the dependency graph.
+func (c DiscoveredConfigs) CycleCheck() error {
+	visited := make(map[string]bool)
+	inPath := make(map[string]bool)
+
+	var checkCycle func(cfg *DiscoveredConfig) error
+
+	checkCycle = func(cfg *DiscoveredConfig) error {
+		if inPath[cfg.Path] {
+			return errors.New("cycle detected in dependency graph at path: " + cfg.Path)
+		}
+
+		if visited[cfg.Path] {
+			return nil
+		}
+
+		visited[cfg.Path] = true
+		inPath[cfg.Path] = true
+
+		for _, dep := range cfg.Dependencies {
+			if err := checkCycle(dep); err != nil {
+				return err
+			}
 		}
+
+		inPath[cfg.Path] = false
+
+		return nil
 	}
 
-	return false
+	for _, cfg := range c {
+		if !visited[cfg.Path] {
+			if err := checkCycle(cfg); err != nil {
+				return err
+			}
+		}
+	}
+
+	return nil
 }
diff --git a/internal/queue/queue.go b/internal/queue/queue.go
new file mode 100644
index 00000000..2fcb7528
--- /dev/null
+++ b/internal/queue/queue.go
@@ -0,0 +1,140 @@
+// Package queue provides a run queue implementation.
+// The queue is a double-ended queue (deque) that allows for efficient adding and removing of elements from both ends.
+// The queue is used to manage the order of Terragrunt runs.
+//
+// The algorithm for populating the queue is as follows:
+//  1. Given a list of discovered configurations, start with an empty queue.
+//  2. Sort configurations alphabetically to ensure deterministic ordering of independent items.
+//  3. For each discovered configuration:
+//     a. If the configuration has no dependencies, append it to the queue.
+//     b. Otherwise, find the position after its last dependency.
+//     c. Among items that depend on the same dependency, maintain alphabetical order.
+//
+// The resulting queue will have:
+// - Configurations with no dependencies at the front
+// - Configurations with dependents are ordered after their dependencies
+// - Alphabetical ordering only between items that share the same dependencies
+//
+// During operations like applies, entries will be dequeued from the front of the queue and run.
+// During operations like destroys, entries will be dequeued from the back of the queue and run.
+// This ensures that dependencies are satisfied in both cases:
+// - For applies: Dependencies (front) are run before their dependents (back)
+// - For destroys: Dependents (back) are run before their dependencies (front)
+package queue
+
+import (
+	"sort"
+
+	"github.com/gruntwork-io/terragrunt/internal/discovery"
+	"github.com/gruntwork-io/terragrunt/internal/errors"
+)
+
+type Queue struct {
+	entries discovery.DiscoveredConfigs
+}
+
+// Entries returns the queue entries. Used for testing.
+func (q *Queue) Entries() []*discovery.DiscoveredConfig {
+	return q.entries
+}
+
+// NewQueue creates a new queue from a list of discovered configurations.
+// The queue is populated with the correct Terragrunt run order.
+// Dependencies are guaranteed to come before their dependents.
+// Items with the same dependencies are sorted alphabetically.
+// Passing dependencies that that haven't been checked for cycles is unsafe.
+// If any cycles are present, the queue construction will halt after N
+// iterations, where N is the number of discovered configs, and throw an error.
+func NewQueue(discovered discovery.DiscoveredConfigs) (*Queue, error) {
+	if len(discovered) == 0 {
+		return &Queue{
+			entries: discovered,
+		}, nil
+	}
+
+	// Create a map for O(1) lookups of configs by path
+	configMap := make(map[string]*discovery.DiscoveredConfig, len(discovered))
+	for _, cfg := range discovered {
+		configMap[cfg.Path] = cfg
+	}
+
+	// Track if a given config has been processed
+	visited := make(map[string]bool, len(discovered))
+
+	// result will store configs in dependency order
+	result := make(discovery.DiscoveredConfigs, 0, len(discovered))
+
+	// depthBudget is initially the maximum dependency depth of the queue
+	// Given that a cycle-free graph has a maximum depth of N, we can
+	// use the length of the discovered configs as a safe upper bound.
+	depthBudget := len(discovered)
+
+	// Process nodes level by level, with deterministic ordering within each level
+	var processLevel func(configs discovery.DiscoveredConfigs) error
+	processLevel = func(configs discovery.DiscoveredConfigs) error {
+		// We need to check to see if we've reached
+		// the maximum allowed iterations.
+		if depthBudget < 0 {
+			return errors.New("cycle detected during queue construction")
+		}
+
+		depthBudget--
+
+		levelNodes := make([]*discovery.DiscoveredConfig, 0, len(configs))
+
+		for _, cfg := range configs {
+			if visited[cfg.Path] {
+				continue
+			}
+
+			hasUnprocessedDeps := false
+
+			// Only consider dependencies that exist in our discovered configs
+			for _, dep := range cfg.Dependencies {
+				if _, exists := configMap[dep.Path]; !exists {
+					continue // Skip dependencies that don't exist in our discovered configs
+				}
+
+				if !visited[dep.Path] {
+					hasUnprocessedDeps = true
+					break
+				}
+			}
+
+			if !hasUnprocessedDeps {
+				levelNodes = append(levelNodes, cfg)
+			}
+		}
+
+		// Sort nodes by path for deterministic ordering within each level
+		sort.SliceStable(levelNodes, func(i, j int) bool {
+			return levelNodes[i].Path < levelNodes[j].Path
+		})
+
+		// Add all nodes at this level to result
+		for _, node := range levelNodes {
+			visited[node.Path] = true
+
+			result = append(result, node)
+		}
+
+		// If every node has been visited, we're done
+		if len(visited) == len(discovered) {
+			return nil
+		}
+
+		// Process next level
+		return processLevel(configs)
+	}
+
+	// Start with all configs
+	if err := processLevel(discovered); err != nil {
+		return &Queue{
+			entries: result,
+		}, err
+	}
+
+	return &Queue{
+		entries: result,
+	}, nil
+}



Test patch:
diff --git a/cli/commands/find/action_test.go b/cli/commands/find/action_test.go
index 4eed47c7..c2c5b0cb 100644
--- a/cli/commands/find/action_test.go
+++ b/cli/commands/find/action_test.go
@@ -24,7 +24,10 @@ func TestRun(t *testing.T) {
 		setup         func(t *testing.T) string
 		expectedPaths []string
 		format        string
+		sort          string
 		hidden        bool
+		dependencies  bool
+		external      bool
 		validate      func(t *testing.T, output string, expectedPaths []string)
 	}{
 		{
@@ -66,6 +69,9 @@ func TestRun(t *testing.T) {
 			},
 			expectedPaths: []string{"unit1", "unit2", "nested/unit4", "stack1"},
 			format:        "text",
+			sort:          "alpha",
+			dependencies:  false,
+			external:      false,
 			validate: func(t *testing.T, output string, expectedPaths []string) {
 				t.Helper()
 
@@ -122,11 +128,14 @@ func TestRun(t *testing.T) {
 			},
 			expectedPaths: []string{"unit1", "unit2", "stack1"},
 			format:        "json",
+			sort:          "alpha",
+			dependencies:  false,
+			external:      false,
 			validate: func(t *testing.T, output string, expectedPaths []string) {
 				t.Helper()
 
 				// Verify the output is valid JSON
-				var configs []discovery.DiscoveredConfig
+				var configs find.FoundConfigs
 				err := json.Unmarshal([]byte(output), &configs)
 				require.NoError(t, err)
 
@@ -189,6 +198,8 @@ func TestRun(t *testing.T) {
 			expectedPaths: []string{"unit1", "unit2", "nested/unit4", "stack1", ".hidden/unit3"},
 			format:        "text",
 			hidden:        true,
+			dependencies:  false,
+			external:      false,
 			validate: func(t *testing.T, output string, expectedPaths []string) {
 				t.Helper()
 
@@ -202,10 +213,312 @@ func TestRun(t *testing.T) {
 				assert.ElementsMatch(t, expectedPaths, lines)
 			},
 		},
+		{
+			name: "dag sorting - simple dependencies",
+			setup: func(t *testing.T) string {
+				t.Helper()
+
+				tmpDir := t.TempDir()
+
+				// Create test directory structure with dependencies:
+				// unit2 -> unit1
+				// unit3 -> unit2
+				testDirs := []string{
+					"unit1",
+					"unit2",
+					"unit3",
+				}
+
+				for _, dir := range testDirs {
+					err := os.MkdirAll(filepath.Join(tmpDir, dir), 0755)
+					require.NoError(t, err)
+				}
+
+				// Create test files with dependencies
+				testFiles := map[string]string{
+					"unit1/terragrunt.hcl": "",
+					"unit2/terragrunt.hcl": `
+dependency "unit1" {
+  config_path = "../unit1"
+}`,
+					"unit3/terragrunt.hcl": `
+dependency "unit2" {
+  config_path = "../unit2"
+}`,
+				}
+
+				for path, content := range testFiles {
+					err := os.WriteFile(filepath.Join(tmpDir, path), []byte(content), 0644)
+					require.NoError(t, err)
+				}
+
+				return tmpDir
+			},
+			expectedPaths: []string{"unit1", "unit2", "unit3"},
+			format:        "text",
+			sort:          "dag",
+			dependencies:  true,
+			external:      false,
+			validate: func(t *testing.T, output string, expectedPaths []string) {
+				t.Helper()
+
+				// Split output into lines and trim whitespace
+				lines := strings.Split(strings.TrimSpace(output), "\n")
+
+				// Verify we have the expected number of lines
+				assert.Len(t, lines, len(expectedPaths))
+
+				// For DAG sorting, order matters - verify exact order
+				assert.Equal(t, expectedPaths, lines)
+			},
+		},
+		{
+			name: "dag sorting - reversed dependencies",
+			setup: func(t *testing.T) string {
+				t.Helper()
+
+				tmpDir := t.TempDir()
+
+				// Create test directory structure with dependencies:
+				// unit3 -> unit2
+				// unit2 -> unit1
+				testDirs := []string{
+					"unit1",
+					"unit2",
+					"unit3",
+				}
+
+				for _, dir := range testDirs {
+					err := os.MkdirAll(filepath.Join(tmpDir, dir), 0755)
+					require.NoError(t, err)
+				}
+
+				// Create test files with dependencies
+				testFiles := map[string]string{
+					"unit1/terragrunt.hcl": `
+dependency "unit2" {
+  config_path = "../unit2"
+}`,
+					"unit2/terragrunt.hcl": `
+dependency "unit3" {
+  config_path = "../unit3"
+}`,
+					"unit3/terragrunt.hcl": "",
+				}
+
+				for path, content := range testFiles {
+					err := os.WriteFile(filepath.Join(tmpDir, path), []byte(content), 0644)
+					require.NoError(t, err)
+				}
+
+				return tmpDir
+			},
+			expectedPaths: []string{"unit3", "unit2", "unit1"},
+			format:        "text",
+			sort:          "dag",
+			dependencies:  true,
+			external:      false,
+			validate: func(t *testing.T, output string, expectedPaths []string) {
+				t.Helper()
+
+				// Split output into lines and trim whitespace
+				lines := strings.Split(strings.TrimSpace(output), "\n")
+
+				// Verify we have the expected number of lines
+				assert.Len(t, lines, len(expectedPaths))
+
+				// For DAG sorting, order matters - verify exact order
+				assert.Equal(t, expectedPaths, lines)
+
+				// Helper to find index of a path
+				findIndex := func(path string) int {
+					for i, line := range lines {
+						if line == path {
+							return i
+						}
+					}
+					return -1
+				}
+
+				// Verify dependency ordering
+				unit1Index := findIndex("unit1")
+				unit2Index := findIndex("unit2")
+				unit3Index := findIndex("unit3")
+
+				assert.Less(t, unit3Index, unit2Index, "unit3 (no deps) should come before unit2 (depends on unit3)")
+				assert.Less(t, unit2Index, unit1Index, "unit2 should come before unit1 (depends on unit2)")
+			},
+		},
+		{
+			name: "dag sorting - complex dependencies",
+			setup: func(t *testing.T) string {
+				t.Helper()
+
+				tmpDir := t.TempDir()
+
+				// Create test directory structure with complex dependencies:
+				// A (no deps)
+				// B (no deps)
+				// C -> A
+				// D -> A,B
+				// E -> C
+				// F -> C
+				testDirs := []string{
+					"A", "B", "C", "D", "E", "F",
+				}
+
+				for _, dir := range testDirs {
+					err := os.MkdirAll(filepath.Join(tmpDir, dir), 0755)
+					require.NoError(t, err)
+				}
+
+				// Create test files with dependencies
+				testFiles := map[string]string{
+					"A/terragrunt.hcl": "",
+					"B/terragrunt.hcl": "",
+					"C/terragrunt.hcl": `
+dependency "A" {
+  config_path = "../A"
+}`,
+					"D/terragrunt.hcl": `
+dependency "A" {
+  config_path = "../A"
+}
+dependency "B" {
+  config_path = "../B"
+}`,
+					"E/terragrunt.hcl": `
+dependency "C" {
+  config_path = "../C"
+}`,
+					"F/terragrunt.hcl": `
+dependency "C" {
+  config_path = "../C"
+}`,
+				}
+
+				for path, content := range testFiles {
+					err := os.WriteFile(filepath.Join(tmpDir, path), []byte(content), 0644)
+					require.NoError(t, err)
+				}
+
+				return tmpDir
+			},
+			expectedPaths: []string{"A", "B", "C", "D", "E", "F"},
+			format:        "text",
+			sort:          "dag",
+			dependencies:  true,
+			external:      false,
+			validate: func(t *testing.T, output string, expectedPaths []string) {
+				t.Helper()
+
+				// Split output into lines and trim whitespace
+				lines := strings.Split(strings.TrimSpace(output), "\n")
+
+				// Verify we have the expected number of lines
+				assert.Len(t, lines, len(expectedPaths))
+
+				// For DAG sorting, order matters - verify exact order
+				// and also verify relative ordering constraints
+				assert.Equal(t, expectedPaths, lines)
+
+				// Helper to find index of a path
+				findIndex := func(path string) int {
+					for i, line := range lines {
+						if line == path {
+							return i
+						}
+					}
+					return -1
+				}
+
+				// Verify dependency ordering
+				aIndex := findIndex("A")
+				bIndex := findIndex("B")
+				cIndex := findIndex("C")
+				dIndex := findIndex("D")
+				eIndex := findIndex("E")
+				fIndex := findIndex("F")
+
+				// Level 0 items should be before their dependents
+				assert.Less(t, aIndex, cIndex, "A should come before C")
+				assert.Less(t, aIndex, dIndex, "A should come before D")
+				assert.Less(t, bIndex, dIndex, "B should come before D")
+
+				// Level 1 items should be before their dependents
+				assert.Less(t, cIndex, eIndex, "C should come before E")
+				assert.Less(t, cIndex, fIndex, "C should come before F")
+			},
+		},
+		{
+			name: "dag sorting - json output",
+			setup: func(t *testing.T) string {
+				t.Helper()
+
+				tmpDir := t.TempDir()
+
+				// Create test directory structure with dependencies
+				testDirs := []string{
+					"A", "B", "C",
+				}
+
+				for _, dir := range testDirs {
+					err := os.MkdirAll(filepath.Join(tmpDir, dir), 0755)
+					require.NoError(t, err)
+				}
+
+				// Create test files with dependencies
+				testFiles := map[string]string{
+					"A/terragrunt.hcl": "",
+					"B/terragrunt.hcl": `
+dependency "A" {
+  config_path = "../A"
+}`,
+					"C/terragrunt.hcl": `
+dependency "B" {
+  config_path = "../B"
+}`,
+				}
+
+				for path, content := range testFiles {
+					err := os.WriteFile(filepath.Join(tmpDir, path), []byte(content), 0644)
+					require.NoError(t, err)
+				}
+
+				return tmpDir
+			},
+			expectedPaths: []string{"A", "B", "C"},
+			format:        "json",
+			sort:          "dag",
+			dependencies:  true,
+			external:      false,
+			validate: func(t *testing.T, output string, expectedPaths []string) {
+				t.Helper()
+
+				// Verify the output is valid JSON
+				var configs []find.FoundConfig
+				err := json.Unmarshal([]byte(output), &configs)
+				require.NoError(t, err)
+
+				// Verify we have the expected number of configs
+				assert.Len(t, configs, len(expectedPaths))
+
+				// Extract paths and verify order
+				var paths []string
+				for _, config := range configs {
+					paths = append(paths, config.Path)
+				}
+				assert.Equal(t, expectedPaths, paths)
+
+				// Verify dependencies are correctly represented in JSON
+				assert.Empty(t, configs[0].Dependencies, "A should have no dependencies")
+				assert.Equal(t, []string{"A"}, configs[1].Dependencies, "B should depend on A")
+				assert.Equal(t, []string{"B"}, configs[2].Dependencies, "C should depend on B")
+			},
+		},
 	}
 
 	for _, tt := range tests {
-		tt := tt // capture range variable
 		t.Run(tt.name, func(t *testing.T) {
 			t.Parallel()
 
@@ -214,11 +527,15 @@ func TestRun(t *testing.T) {
 
 			tgOpts := options.NewTerragruntOptions()
 			tgOpts.WorkingDir = tmpDir
+			tgOpts.Logger.Formatter().SetDisabledColors(true)
 
 			// Create options
 			opts := find.NewOptions(tgOpts)
 			opts.Format = tt.format
 			opts.Hidden = tt.hidden
+			opts.Sort = tt.sort
+			opts.Dependencies = tt.dependencies
+			opts.External = tt.external
 
 			// Create a pipe to capture output
 			r, w, err := os.Pipe()
@@ -250,14 +567,14 @@ func TestColorizer(t *testing.T) {
 
 	tests := []struct {
 		name   string
-		config *discovery.DiscoveredConfig
+		config *find.FoundConfig
 		// We can't test exact ANSI codes as they might vary by environment,
 		// so we'll test that different types result in different outputs
 		shouldBeDifferent []discovery.ConfigType
 	}{
 		{
 			name: "unit config",
-			config: &discovery.DiscoveredConfig{
+			config: &find.FoundConfig{
 				Type: discovery.ConfigTypeUnit,
 				Path: "path/to/unit",
 			},
@@ -265,7 +582,7 @@ func TestColorizer(t *testing.T) {
 		},
 		{
 			name: "stack config",
-			config: &discovery.DiscoveredConfig{
+			config: &find.FoundConfig{
 				Type: discovery.ConfigTypeStack,
 				Path: "path/to/stack",
 			},
@@ -282,7 +599,7 @@ func TestColorizer(t *testing.T) {
 
 			// Test that different types produce different colorized outputs
 			for _, diffType := range tt.shouldBeDifferent {
-				diffConfig := &discovery.DiscoveredConfig{
+				diffConfig := &find.FoundConfig{
 					Type: diffType,
 					Path: tt.config.Path,
 				}
diff --git a/internal/discovery/discovery_test.go b/internal/discovery/discovery_test.go
index 14a9b469..abc25dbb 100644
--- a/internal/discovery/discovery_test.go
+++ b/internal/discovery/discovery_test.go
@@ -1,11 +1,13 @@
 package discovery_test
 
 import (
+	"context"
 	"os"
 	"path/filepath"
 	"testing"
 
 	"github.com/gruntwork-io/terragrunt/internal/discovery"
+	"github.com/gruntwork-io/terragrunt/options"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
@@ -17,35 +19,37 @@ func TestDiscovery(t *testing.T) {
 	tmpDir := t.TempDir()
 
 	// Create test directory structure
+	unit1Dir := filepath.Join(tmpDir, "unit1")
+	unit2Dir := filepath.Join(tmpDir, "unit2")
+	stack1Dir := filepath.Join(tmpDir, "stack1")
+	hiddenUnitDir := filepath.Join(tmpDir, ".hidden", "hidden-unit")
+	nestedUnit4Dir := filepath.Join(tmpDir, "nested", "unit4")
+
 	testDirs := []string{
-		"unit1",
-		"unit2",
-		"stack1",
-		".hidden/hidden-unit",
-		"nested/unit4",
+		unit1Dir,
+		unit2Dir,
+		stack1Dir,
+		hiddenUnitDir,
+		nestedUnit4Dir,
 	}
 
 	for _, dir := range testDirs {
-		err := os.MkdirAll(filepath.Join(tmpDir, dir), 0755)
-		if err != nil {
-			t.Fatal(err)
-		}
+		err := os.MkdirAll(dir, 0755)
+		require.NoError(t, err)
 	}
 
 	// Create test files
 	testFiles := map[string]string{
-		"unit1/terragrunt.hcl":               "",
-		"unit2/terragrunt.hcl":               "",
-		"stack1/terragrunt.stack.hcl":        "",
-		".hidden/hidden-unit/terragrunt.hcl": "",
-		"nested/unit4/terragrunt.hcl":        "",
+		filepath.Join(unit1Dir, "terragrunt.hcl"):        "",
+		filepath.Join(unit2Dir, "terragrunt.hcl"):        "",
+		filepath.Join(stack1Dir, "terragrunt.stack.hcl"): "",
+		filepath.Join(hiddenUnitDir, "terragrunt.hcl"):   "",
+		filepath.Join(nestedUnit4Dir, "terragrunt.hcl"):  "",
 	}
 
 	for path, content := range testFiles {
-		err := os.WriteFile(filepath.Join(tmpDir, path), []byte(content), 0644)
-		if err != nil {
-			t.Fatal(err)
-		}
+		err := os.WriteFile(path, []byte(content), 0644)
+		require.NoError(t, err)
 	}
 
 	tests := []struct {
@@ -58,14 +62,14 @@ func TestDiscovery(t *testing.T) {
 		{
 			name:       "basic discovery without hidden",
 			discovery:  discovery.NewDiscovery(tmpDir),
-			wantUnits:  []string{"unit1", "unit2", "nested/unit4"},
-			wantStacks: []string{"stack1"},
+			wantUnits:  []string{unit1Dir, unit2Dir, nestedUnit4Dir},
+			wantStacks: []string{stack1Dir},
 		},
 		{
 			name:       "discovery with hidden",
 			discovery:  discovery.NewDiscovery(tmpDir).WithHidden(),
-			wantUnits:  []string{"unit1", "unit2", ".hidden/hidden-unit", "nested/unit4"},
-			wantStacks: []string{"stack1"},
+			wantUnits:  []string{unit1Dir, unit2Dir, hiddenUnitDir, nestedUnit4Dir},
+			wantStacks: []string{stack1Dir},
 		},
 	}
 
@@ -73,7 +77,7 @@ func TestDiscovery(t *testing.T) {
 		t.Run(tt.name, func(t *testing.T) {
 			t.Parallel()
 
-			configs, err := tt.discovery.Discover()
+			configs, err := tt.discovery.Discover(context.Background(), nil)
 			if !tt.errorExpected {
 				require.NoError(t, err)
 			}
@@ -112,9 +116,9 @@ func TestDiscoveredConfigsFilter(t *testing.T) {
 
 	// Setup
 	configs := discovery.DiscoveredConfigs{
-		{Path: "a", Type: discovery.ConfigTypeUnit},
-		{Path: "b", Type: discovery.ConfigTypeStack},
-		{Path: "c", Type: discovery.ConfigTypeUnit},
+		{Path: "unit1", Type: discovery.ConfigTypeUnit},
+		{Path: "stack1", Type: discovery.ConfigTypeStack},
+		{Path: "unit2", Type: discovery.ConfigTypeUnit},
 	}
 
 	// Test unit filtering
@@ -125,7 +129,7 @@ func TestDiscoveredConfigsFilter(t *testing.T) {
 		require.Len(t, units, 2)
 		assert.Equal(t, discovery.ConfigTypeUnit, units[0].Type)
 		assert.Equal(t, discovery.ConfigTypeUnit, units[1].Type)
-		assert.ElementsMatch(t, []string{"a", "c"}, units.Paths())
+		assert.ElementsMatch(t, []string{"unit1", "unit2"}, units.Paths())
 	})
 
 	// Test stack filtering
@@ -135,6 +139,251 @@ func TestDiscoveredConfigsFilter(t *testing.T) {
 		stacks := configs.Filter(discovery.ConfigTypeStack)
 		require.Len(t, stacks, 1)
 		assert.Equal(t, discovery.ConfigTypeStack, stacks[0].Type)
-		assert.Equal(t, "b", stacks[0].Path)
+		assert.Equal(t, "stack1", stacks[0].Path)
 	})
 }
+
+func TestDiscoveryWithDependencies(t *testing.T) {
+	t.Parallel()
+
+	// Create a temporary directory for testing
+	tmpDir := t.TempDir()
+
+	internalDir := filepath.Join(tmpDir, "internal")
+	appDir := filepath.Join(internalDir, "app")
+	dbDir := filepath.Join(internalDir, "db")
+	vpcDir := filepath.Join(internalDir, "vpc")
+
+	externalDir := filepath.Join(tmpDir, "external")
+	externalAppDir := filepath.Join(externalDir, "app")
+
+	testDirs := []string{
+		appDir,
+		dbDir,
+		vpcDir,
+		externalAppDir,
+	}
+
+	for _, dir := range testDirs {
+		err := os.MkdirAll(dir, 0755)
+		require.NoError(t, err)
+	}
+
+	// Create test files with dependencies
+	testFiles := map[string]string{
+		filepath.Join(appDir, "terragrunt.hcl"): `
+		dependency "db" {
+			config_path = "../db"
+		}
+
+		dependency "external" {
+			config_path = "../../external/app"
+		}
+		`,
+		filepath.Join(dbDir, "terragrunt.hcl"): `
+		dependency "vpc" {
+			config_path = "../vpc"
+		}
+		`,
+		filepath.Join(vpcDir, "terragrunt.hcl"):         ``,
+		filepath.Join(externalAppDir, "terragrunt.hcl"): ``,
+	}
+
+	for path, content := range testFiles {
+		err := os.WriteFile(path, []byte(content), 0644)
+		require.NoError(t, err)
+	}
+
+	// Create base options that will be cloned for each test
+	opts := options.NewTerragruntOptions()
+	opts.WorkingDir = internalDir
+	opts.RootWorkingDir = internalDir
+
+	tests := []struct {
+		name          string
+		discovery     *discovery.Discovery
+		wantDiscovery discovery.DiscoveredConfigs
+		errorExpected bool
+	}{
+		{
+			name:      "discovery without dependencies",
+			discovery: discovery.NewDiscovery(internalDir),
+			wantDiscovery: discovery.DiscoveredConfigs{
+				{Path: appDir, Type: discovery.ConfigTypeUnit},
+				{Path: dbDir, Type: discovery.ConfigTypeUnit},
+				{Path: vpcDir, Type: discovery.ConfigTypeUnit},
+			},
+		},
+		{
+			name:      "discovery with dependencies",
+			discovery: discovery.NewDiscovery(internalDir).WithDiscoverDependencies(),
+			wantDiscovery: discovery.DiscoveredConfigs{
+				{Path: appDir, Type: discovery.ConfigTypeUnit, Dependencies: discovery.DiscoveredConfigs{
+					{Path: dbDir, Type: discovery.ConfigTypeUnit, Dependencies: discovery.DiscoveredConfigs{
+						{Path: vpcDir, Type: discovery.ConfigTypeUnit},
+					}},
+					{Path: externalAppDir, Type: discovery.ConfigTypeUnit, External: true},
+				}},
+				{Path: dbDir, Type: discovery.ConfigTypeUnit, Dependencies: discovery.DiscoveredConfigs{
+					{Path: vpcDir, Type: discovery.ConfigTypeUnit},
+				}},
+				{Path: vpcDir, Type: discovery.ConfigTypeUnit},
+			},
+		},
+		{
+			name:      "discovery with external dependencies",
+			discovery: discovery.NewDiscovery(internalDir).WithDiscoverDependencies().WithDiscoverExternalDependencies(),
+			wantDiscovery: discovery.DiscoveredConfigs{
+				{Path: appDir, Type: discovery.ConfigTypeUnit, Dependencies: discovery.DiscoveredConfigs{
+					{Path: dbDir, Type: discovery.ConfigTypeUnit, Dependencies: discovery.DiscoveredConfigs{
+						{Path: vpcDir, Type: discovery.ConfigTypeUnit},
+					}},
+					{Path: externalAppDir, Type: discovery.ConfigTypeUnit, External: true},
+				}},
+				{Path: dbDir, Type: discovery.ConfigTypeUnit, Dependencies: discovery.DiscoveredConfigs{
+					{Path: vpcDir, Type: discovery.ConfigTypeUnit},
+				}},
+				{Path: vpcDir, Type: discovery.ConfigTypeUnit},
+				{Path: externalAppDir, Type: discovery.ConfigTypeUnit, External: true},
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+
+			configs, err := tt.discovery.Discover(context.Background(), opts)
+			if tt.errorExpected {
+				require.Error(t, err)
+				return
+			}
+			require.NoError(t, err)
+
+			// Sort the configs and their dependencies to ensure consistent ordering
+			configs = configs.Sort()
+			for _, cfg := range configs {
+				cfg.Dependencies = cfg.Dependencies.Sort()
+				for _, dep := range cfg.Dependencies {
+					dep.Dependencies = dep.Dependencies.Sort()
+				}
+			}
+
+			tt.wantDiscovery = tt.wantDiscovery.Sort()
+			for _, cfg := range tt.wantDiscovery {
+				cfg.Dependencies = cfg.Dependencies.Sort()
+				for _, dep := range cfg.Dependencies {
+					dep.Dependencies = dep.Dependencies.Sort()
+				}
+			}
+
+			assert.Equal(t, tt.wantDiscovery, configs)
+		})
+	}
+}
+
+func TestDiscoveredConfigsCycleCheck(t *testing.T) {
+	t.Parallel()
+
+	tests := []struct {
+		name          string
+		configs       discovery.DiscoveredConfigs
+		errorExpected bool
+	}{
+		{
+			name: "no cycles",
+			configs: discovery.DiscoveredConfigs{
+				{
+					Path: "a",
+					Dependencies: discovery.DiscoveredConfigs{
+						{Path: "b"},
+					},
+				},
+				{Path: "b"},
+			},
+			errorExpected: false,
+		},
+		{
+			name: "direct cycle",
+			configs: discovery.DiscoveredConfigs{
+				{
+					Path: "a",
+					Dependencies: discovery.DiscoveredConfigs{
+						{
+							Path: "b",
+							Dependencies: discovery.DiscoveredConfigs{
+								{Path: "a"},
+							},
+						},
+					},
+				},
+				{Path: "b"},
+			},
+			errorExpected: true,
+		},
+		{
+			name: "indirect cycle",
+			configs: discovery.DiscoveredConfigs{
+				{
+					Path: "a",
+					Dependencies: discovery.DiscoveredConfigs{
+						{
+							Path: "b",
+							Dependencies: discovery.DiscoveredConfigs{
+								{
+									Path: "c",
+									Dependencies: discovery.DiscoveredConfigs{
+										{Path: "a"},
+									},
+								},
+							},
+						},
+					},
+				},
+				{Path: "b"},
+				{Path: "c"},
+			},
+			errorExpected: true,
+		},
+		{
+			name: "diamond dependency - no cycle",
+			configs: discovery.DiscoveredConfigs{
+				{
+					Path: "a",
+					Dependencies: discovery.DiscoveredConfigs{
+						{Path: "b"},
+						{Path: "c"},
+					},
+				},
+				{
+					Path: "b",
+					Dependencies: discovery.DiscoveredConfigs{
+						{Path: "d"},
+					},
+				},
+				{
+					Path: "c",
+					Dependencies: discovery.DiscoveredConfigs{
+						{Path: "d"},
+					},
+				},
+				{Path: "d"},
+			},
+			errorExpected: false,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+
+			err := tt.configs.CycleCheck()
+			if tt.errorExpected {
+				require.Error(t, err)
+				assert.Contains(t, err.Error(), "cycle detected")
+			} else {
+				require.NoError(t, err)
+			}
+		})
+	}
+}
diff --git a/internal/queue/queue_test.go b/internal/queue/queue_test.go
new file mode 100644
index 00000000..4fc11876
--- /dev/null
+++ b/internal/queue/queue_test.go
@@ -0,0 +1,225 @@
+package queue_test
+
+import (
+	"testing"
+
+	"github.com/gruntwork-io/terragrunt/internal/discovery"
+	"github.com/gruntwork-io/terragrunt/internal/queue"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestNewQueue(t *testing.T) {
+	t.Parallel()
+
+	t.Run("no dependencies - maintains alphabetical order", func(t *testing.T) {
+		t.Parallel()
+
+		// Create configs with no dependencies - should maintain alphabetical order at front
+		configs := []*discovery.DiscoveredConfig{
+			{Path: "c", Dependencies: []*discovery.DiscoveredConfig{}},
+			{Path: "a", Dependencies: []*discovery.DiscoveredConfig{}},
+			{Path: "b", Dependencies: []*discovery.DiscoveredConfig{}},
+		}
+
+		q, err := queue.NewQueue(configs)
+		require.NoError(t, err)
+
+		entries := q.Entries()
+
+		// Should be sorted alphabetically at front since none have dependencies
+		assert.Equal(t, "a", entries[0].Path)
+		assert.Equal(t, "b", entries[1].Path)
+		assert.Equal(t, "c", entries[2].Path)
+	})
+
+	t.Run("dependencies - ordered by dependency level", func(t *testing.T) {
+		t.Parallel()
+
+		// Create configs with dependencies - should order by dependency level
+		configs := []*discovery.DiscoveredConfig{
+			{Path: "c", Dependencies: []*discovery.DiscoveredConfig{{Path: "b"}}},
+			{Path: "b", Dependencies: []*discovery.DiscoveredConfig{{Path: "a"}}},
+			{Path: "a", Dependencies: []*discovery.DiscoveredConfig{}},
+		}
+
+		q, err := queue.NewQueue(configs)
+		require.NoError(t, err)
+
+		entries := q.Entries()
+
+		// 'a' has no deps so should be at front
+		// 'b' depends on 'a' so should be after
+		// 'c' depends on 'b' so should be at back
+		assert.Equal(t, "a", entries[0].Path)
+		assert.Equal(t, "b", entries[1].Path)
+		assert.Equal(t, "c", entries[2].Path)
+	})
+
+	t.Run("complex dag - ordered by dependency level and alphabetically", func(t *testing.T) {
+		t.Parallel()
+
+		// Create a more complex dependency graph:
+		//   A (no deps)
+		//   B (no deps)
+		//   C -> A
+		//   D -> A,B
+		//   E -> C
+		//   F -> C
+		configs := []*discovery.DiscoveredConfig{
+			{Path: "F", Dependencies: []*discovery.DiscoveredConfig{{Path: "C"}}},
+			{Path: "E", Dependencies: []*discovery.DiscoveredConfig{{Path: "C"}}},
+			{Path: "D", Dependencies: []*discovery.DiscoveredConfig{{Path: "A"}, {Path: "B"}}},
+			{Path: "C", Dependencies: []*discovery.DiscoveredConfig{{Path: "A"}}},
+			{Path: "B", Dependencies: []*discovery.DiscoveredConfig{}},
+			{Path: "A", Dependencies: []*discovery.DiscoveredConfig{}},
+		}
+
+		q, err := queue.NewQueue(configs)
+		require.NoError(t, err)
+
+		entries := q.Entries()
+
+		// Verify ordering by dependency level and alphabetically within levels:
+		// Level 0 (no deps): A, B
+		// Level 1 (depends on level 0): C, D
+		// Level 2 (depends on level 1): E, F
+		assert.Equal(t, "A", entries[0].Path)
+		assert.Equal(t, "B", entries[1].Path)
+		assert.Equal(t, "C", entries[2].Path)
+		assert.Equal(t, "D", entries[3].Path)
+		assert.Equal(t, "E", entries[4].Path)
+		assert.Equal(t, "F", entries[5].Path)
+
+		// Also verify relative ordering
+		aIndex := findIndex(entries, "A")
+		bIndex := findIndex(entries, "B")
+		cIndex := findIndex(entries, "C")
+		dIndex := findIndex(entries, "D")
+		eIndex := findIndex(entries, "E")
+		fIndex := findIndex(entries, "F")
+
+		// Level 0 items should be before their dependents
+		assert.Less(t, aIndex, cIndex, "A should come before C")
+		assert.Less(t, aIndex, dIndex, "A should come before D")
+		assert.Less(t, bIndex, dIndex, "B should come before D")
+
+		// Level 1 items should be before their dependents
+		assert.Less(t, cIndex, eIndex, "C should come before E")
+		assert.Less(t, cIndex, fIndex, "C should come before F")
+	})
+
+	t.Run("deterministic ordering of parallel dependencies", func(t *testing.T) {
+		t.Parallel()
+
+		// Create a graph with parallel dependencies that could be ordered multiple ways:
+		//   A (no deps)
+		//   B -> A
+		//   C -> A
+		//   D -> A
+		configs := []*discovery.DiscoveredConfig{
+			{Path: "D", Dependencies: []*discovery.DiscoveredConfig{{Path: "A"}}},
+			{Path: "C", Dependencies: []*discovery.DiscoveredConfig{{Path: "A"}}},
+			{Path: "B", Dependencies: []*discovery.DiscoveredConfig{{Path: "A"}}},
+			{Path: "A", Dependencies: []*discovery.DiscoveredConfig{}},
+		}
+
+		// Run multiple times to verify deterministic ordering
+		for range 5 {
+			q, err := queue.NewQueue(configs)
+			require.NoError(t, err)
+
+			entries := q.Entries()
+
+			// A should be first (no deps)
+			assert.Equal(t, "A", entries[0].Path)
+
+			// B, C, D should maintain alphabetical order since they're all at the same level
+			assert.Equal(t, "B", entries[1].Path)
+			assert.Equal(t, "C", entries[2].Path)
+			assert.Equal(t, "D", entries[3].Path)
+		}
+	})
+
+	t.Run("depth-based ordering verification", func(t *testing.T) {
+		t.Parallel()
+
+		// Create a graph where depth matters:
+		//   A (no deps, depth 0)
+		//   B (no deps, depth 0)
+		//   C -> A (depth 1)
+		//   D -> B (depth 1)
+		//   E -> C,D (depth 2)
+		configs := []*discovery.DiscoveredConfig{
+			{Path: "E", Dependencies: []*discovery.DiscoveredConfig{{Path: "C"}, {Path: "D"}}},
+			{Path: "D", Dependencies: []*discovery.DiscoveredConfig{{Path: "B"}}},
+			{Path: "C", Dependencies: []*discovery.DiscoveredConfig{{Path: "A"}}},
+			{Path: "B", Dependencies: []*discovery.DiscoveredConfig{}},
+			{Path: "A", Dependencies: []*discovery.DiscoveredConfig{}},
+		}
+
+		q, err := queue.NewQueue(configs)
+		require.NoError(t, err)
+
+		entries := q.Entries()
+
+		// Verify that items are grouped by their depth levels
+		// Level 0: A,B (no deps)
+		// Level 1: C,D (depend on level 0)
+		// Level 2: E (depends on level 1)
+
+		// First verify the basic ordering
+		assert.Len(t, entries, 5, "Should have all 5 entries")
+
+		// Find indices
+		aIndex := findIndex(entries, "A")
+		bIndex := findIndex(entries, "B")
+		cIndex := findIndex(entries, "C")
+		dIndex := findIndex(entries, "D")
+		eIndex := findIndex(entries, "E")
+
+		// Level 0 items should be at the start (indices 0 or 1)
+		assert.LessOrEqual(t, aIndex, 1, "A should be in first two positions")
+		assert.LessOrEqual(t, bIndex, 1, "B should be in first two positions")
+
+		// Level 1 items should be in the middle (indices 2 or 3)
+		assert.True(t, cIndex >= 2 && cIndex <= 3, "C should be in middle positions")
+		assert.True(t, dIndex >= 2 && dIndex <= 3, "D should be in middle positions")
+
+		// Level 2 item should be at the end (index 4)
+		assert.Equal(t, 4, eIndex, "E should be in last position")
+	})
+
+	t.Run("error handling - cycle", func(t *testing.T) {
+		t.Parallel()
+
+		// Create a cycle: A -> B -> C -> A
+		configs := []*discovery.DiscoveredConfig{
+			{Path: "C", Dependencies: []*discovery.DiscoveredConfig{{Path: "B"}}},
+			{Path: "B", Dependencies: []*discovery.DiscoveredConfig{{Path: "A"}}},
+			{Path: "A", Dependencies: []*discovery.DiscoveredConfig{{Path: "C"}}},
+		}
+
+		q, err := queue.NewQueue(configs)
+		require.Error(t, err)
+		assert.NotNil(t, q)
+	})
+
+	t.Run("error handling - empty config list", func(t *testing.T) {
+		t.Parallel()
+
+		q, err := queue.NewQueue([]*discovery.DiscoveredConfig{})
+		require.NoError(t, err)
+		assert.Empty(t, q.Entries())
+	})
+}
+
+// findIndex returns the index of the config with the given path in the slice
+func findIndex(configs []*discovery.DiscoveredConfig, path string) int {
+	for i, cfg := range configs {
+		if cfg.Path == path {
+			return i
+		}
+	}
+	return -1
+}
diff --git a/test/fixtures/find/dag/a-dependent/terragrunt.hcl b/test/fixtures/find/dag/a-dependent/terragrunt.hcl
new file mode 100644
index 00000000..acbbfb13
--- /dev/null
+++ b/test/fixtures/find/dag/a-dependent/terragrunt.hcl
@@ -0,0 +1,3 @@
+dependency "dep" {
+    config_path = "../b-dependency"
+}
diff --git a/test/fixtures/find/dag/b-dependency/terragrunt.hcl b/test/fixtures/find/dag/b-dependency/terragrunt.hcl
new file mode 100644
index 00000000..bb7b160d
--- /dev/null
+++ b/test/fixtures/find/dag/b-dependency/terragrunt.hcl
@@ -0,0 +1 @@
+# Intentionally empty
diff --git a/test/fixtures/find/internal-v-external/external/c-dependency/terragrunt.hcl b/test/fixtures/find/internal-v-external/external/c-dependency/terragrunt.hcl
new file mode 100644
index 00000000..e69de29b
diff --git a/test/fixtures/find/internal-v-external/internal/a-dependent/terragrunt.hcl b/test/fixtures/find/internal-v-external/internal/a-dependent/terragrunt.hcl
new file mode 100644
index 00000000..2948817a
--- /dev/null
+++ b/test/fixtures/find/internal-v-external/internal/a-dependent/terragrunt.hcl
@@ -0,0 +1,7 @@
+dependency "dep_b" {
+  config_path = "../b-dependency"
+}
+
+dependency "dep_c" {
+  config_path = "../../external/c-dependency"
+}
diff --git a/test/fixtures/find/internal-v-external/internal/b-dependency/terragrunt.hcl b/test/fixtures/find/internal-v-external/internal/b-dependency/terragrunt.hcl
new file mode 100644
index 00000000..bb7b160d
--- /dev/null
+++ b/test/fixtures/find/internal-v-external/internal/b-dependency/terragrunt.hcl
@@ -0,0 +1 @@
+# Intentionally empty
diff --git a/test/fixtures/stacks/absolute-path/terragrunt.stack.hcl b/test/fixtures/stacks/absolute-path/terragrunt.stack.hcl
new file mode 100644
index 00000000..8a2a95c2
--- /dev/null
+++ b/test/fixtures/stacks/absolute-path/terragrunt.stack.hcl
@@ -0,0 +1,24 @@
+
+unit "app1" {
+	source = "units/app"
+	path   = "${get_repo_root()}/app1"
+	values = {
+		input = "app1"
+	}
+}
+
+unit "app2" {
+	source = "units/app"
+	path   = "${get_repo_root()}/app2"
+	values = {
+		input = "app2"
+	}
+}
+
+unit "app3" {
+	source = "units/app"
+	path   = "app3"
+	values = {
+		input = "app3"
+	}
+}
diff --git a/test/fixtures/stacks/absolute-path/units/app/main.tf b/test/fixtures/stacks/absolute-path/units/app/main.tf
new file mode 100644
index 00000000..2cad5cf2
--- /dev/null
+++ b/test/fixtures/stacks/absolute-path/units/app/main.tf
@@ -0,0 +1,13 @@
+variable "input" {
+  description = "Project input"
+  type        = string
+}
+
+resource "local_file" "file" {
+  content  = var.input
+  filename = "${path.module}/data.txt"
+}
+
+output "result" {
+  value = var.input
+}
diff --git a/test/fixtures/stacks/absolute-path/units/app/terragrunt.hcl b/test/fixtures/stacks/absolute-path/units/app/terragrunt.hcl
new file mode 100644
index 00000000..a86ab87b
--- /dev/null
+++ b/test/fixtures/stacks/absolute-path/units/app/terragrunt.hcl
@@ -0,0 +1,4 @@
+
+inputs = {
+  input = values.input
+}
\ No newline at end of file
diff --git a/test/fixtures/stacks/source-map/terragrunt.stack.hcl b/test/fixtures/stacks/source-map/terragrunt.stack.hcl
new file mode 100644
index 00000000..0a993ded
--- /dev/null
+++ b/test/fixtures/stacks/source-map/terragrunt.stack.hcl
@@ -0,0 +1,16 @@
+
+unit "app1" {
+	source = "git::https://git-host.com/not-existing-repo.git//fixtures/stacks/source-map/units/app"
+	path   = "app1"
+	values = {
+		input = "app1"
+	}
+}
+
+unit "app2" {
+	source = "git::https://git-host.com/not-existing-repo.git//fixtures/stacks/source-map/units/app"
+	path   = "app2"
+	values = {
+		input = "app2"
+	}
+}
diff --git a/test/fixtures/stacks/source-map/tf/modules/main.tf b/test/fixtures/stacks/source-map/tf/modules/main.tf
new file mode 100644
index 00000000..92435bc8
--- /dev/null
+++ b/test/fixtures/stacks/source-map/tf/modules/main.tf
@@ -0,0 +1,15 @@
+
+
+variable "input" {
+  description = "Project input"
+  type        = string
+}
+
+resource "local_file" "file" {
+  content  = var.input
+  filename = "${path.module}/data.txt"
+}
+
+output "result" {
+  value = var.input
+}
diff --git a/test/fixtures/stacks/source-map/units/app/terragrunt.hcl b/test/fixtures/stacks/source-map/units/app/terragrunt.hcl
new file mode 100644
index 00000000..933bec48
--- /dev/null
+++ b/test/fixtures/stacks/source-map/units/app/terragrunt.hcl
@@ -0,0 +1,7 @@
+terraform {
+  source = "git::https://git-host.com/not-existing-repo.git//fixtures/stacks/source-map/tf/modules"
+}
+
+inputs = {
+  input = values.input
+}
\ No newline at end of file
diff --git a/test/integration_find_test.go b/test/integration_find_test.go
index d2df1ba1..1a2ed617 100644
--- a/test/integration_find_test.go
+++ b/test/integration_find_test.go
@@ -1,6 +1,7 @@
 package test_test
 
 import (
+	"path/filepath"
 	"testing"
 
 	"github.com/gruntwork-io/terragrunt/test/helpers"
@@ -9,8 +10,10 @@ import (
 )
 
 const (
-	testFixtureFindBasic  = "fixtures/find/basic"
-	testFixtureFindHidden = "fixtures/find/hidden"
+	testFixtureFindBasic             = "fixtures/find/basic"
+	testFixtureFindHidden            = "fixtures/find/hidden"
+	testFixtureFindDAG               = "fixtures/find/dag"
+	testFixtureFindInternalVExternal = "fixtures/find/internal-v-external"
 )
 
 func TestFindBasic(t *testing.T) {
@@ -76,3 +79,50 @@ func TestFindHidden(t *testing.T) {
 		})
 	}
 }
+
+func TestFindDAG(t *testing.T) {
+	t.Parallel()
+
+	tc := []struct {
+		name     string
+		sort     string
+		expected string
+	}{
+		{name: "alpha", sort: "alpha", expected: "a-dependent\nb-dependency\n"},
+		{name: "dag", sort: "dag", expected: "b-dependency\na-dependent\n"},
+	}
+
+	for _, tt := range tc {
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+
+			helpers.CleanupTerraformFolder(t, testFixtureFindDAG)
+
+			stdout, stderr, err := helpers.RunTerragruntCommandWithOutput(t, "terragrunt find --experiment cli-redesign --sort="+tt.sort+" --no-color --working-dir "+testFixtureFindDAG)
+			require.NoError(t, err)
+
+			assert.Empty(t, stderr)
+			assert.Equal(t, tt.expected, stdout)
+		})
+	}
+}
+
+func TestFindExternalDependencies(t *testing.T) {
+	t.Parallel()
+
+	helpers.CleanupTerraformFolder(t, testFixtureFindInternalVExternal)
+
+	internalDir := filepath.Join(testFixtureFindInternalVExternal, "internal")
+
+	stdout, stderr, err := helpers.RunTerragruntCommandWithOutput(t, "terragrunt find --experiment cli-redesign --no-color --working-dir "+internalDir+" --dependencies --external")
+	require.NoError(t, err)
+
+	assert.Empty(t, stderr)
+	assert.Equal(t, "../external/c-dependency\na-dependent\nb-dependency\n", stdout)
+
+	stdout, stderr, err = helpers.RunTerragruntCommandWithOutput(t, "terragrunt find --experiment cli-redesign --no-color --working-dir "+internalDir+" --dependencies")
+	require.NoError(t, err)
+
+	assert.Empty(t, stderr)
+	assert.Equal(t, "a-dependent\nb-dependency\n", stdout)
+}
diff --git a/test/integration_stacks_test.go b/test/integration_stacks_test.go
index c0737a33..f2f87e0e 100644
--- a/test/integration_stacks_test.go
+++ b/test/integration_stacks_test.go
@@ -6,6 +6,8 @@ import (
 	"path/filepath"
 	"testing"
 
+	"github.com/gruntwork-io/terratest/modules/files"
+
 	"github.com/gruntwork-io/terragrunt/config/hclparse"
 
 	"github.com/stretchr/testify/assert"
@@ -29,6 +31,8 @@ const (
 	testFixtureNestedStacks        = "fixtures/stacks/nested"
 	testFixtureStackValues         = "fixtures/stacks/stack-values"
 	testFixtureStackDependencies   = "fixtures/stacks/dependencies"
+	testFixtureStackAbsolutePath   = "fixtures/stacks/absolute-path"
+	testFixtureStackSourceMap      = "fixtures/stacks/source-map"
 )
 
 func TestStacksGenerateBasic(t *testing.T) {
@@ -171,9 +175,11 @@ func TestStacksApplyRemote(t *testing.T) {
 	tmpEnvPath := helpers.CopyEnvironment(t, testFixtureStacksRemote)
 	rootPath := util.JoinPath(tmpEnvPath, testFixtureStacksRemote)
 
-	stdout, _, err := helpers.RunTerragruntCommandWithOutput(t, "terragrunt stack run apply --experiment stacks --terragrunt-non-interactive --terragrunt-working-dir "+rootPath)
+	stdout, stderr, err := helpers.RunTerragruntCommandWithOutput(t, "terragrunt stack run apply --terragrunt-log-level debug --experiment stacks --terragrunt-non-interactive --terragrunt-working-dir "+rootPath)
 	require.NoError(t, err)
 
+	assert.Contains(t, stderr, "app1 (git::https://github.com/gruntwork-io/terragrunt.git//test/fixtures/stacks/basic/units/chick?ref=main&depth=1)")
+	assert.Contains(t, stderr, "app2 (git::https://github.com/gruntwork-io/terragrunt.git//test/fixtures/stacks/basic/units/chick?ref=main&depth=1)")
 	assert.Contains(t, stdout, "Apply complete! Resources: 1 added, 0 changed, 0 destroyed")
 	assert.Contains(t, stdout, "local_file.file: Creation complete")
 	path := util.JoinPath(rootPath, ".terragrunt-stack")
@@ -713,6 +719,96 @@ func TestStackApplyStrictInclude(t *testing.T) {
 	assert.True(t, util.FileNotExists(dataPath))
 }
 
+func TestStacksSourceMap(t *testing.T) {
+	t.Parallel()
+
+	// prepare local path to do override of source url
+	helpers.CleanupTerraformFolder(t, testFixtureStacksBasic)
+	localTmpEnvPath := helpers.CopyEnvironment(t, testFixtureStacksBasic)
+	localTmpTest := filepath.Join(localTmpEnvPath, "test", "fixtures")
+	if err := os.MkdirAll(localTmpTest, 0755); err != nil {
+		assert.NoError(t, err)
+	}
+
+	if err := files.CopyFolderContentsWithFilter(filepath.Join(localTmpEnvPath, "fixtures"), localTmpTest, func(path string) bool {
+		return true
+	}); err != nil {
+		assert.NoError(t, err)
+	}
+
+	// prepare local environment with remote to use source map to replace
+	helpers.CleanupTerraformFolder(t, testFixtureStacksRemote)
+	tmpEnvPath := helpers.CopyEnvironment(t, testFixtureStacksRemote)
+	rootPath := util.JoinPath(tmpEnvPath, testFixtureStacksRemote)
+
+	// generate path with replacement of local source with local path
+	_, stderr, err := helpers.RunTerragruntCommandWithOutput(t, "terragrunt stack generate --experiment stacks --source-map git::https://github.com/gruntwork-io/terragrunt.git="+localTmpEnvPath+" --terragrunt-working-dir "+rootPath)
+	require.NoError(t, err)
+
+	assert.Contains(t, stderr, "Processing unit app1")
+	assert.Contains(t, stderr, "Processing unit app2")
+
+	path := util.JoinPath(rootPath, ".terragrunt-stack")
+	validateStackDir(t, path)
+
+	_, stderr, err = helpers.RunTerragruntCommandWithOutput(t, "terragrunt stack run apply --terragrunt-log-level debug --experiment stacks --source-map git::https://github.com/gruntwork-io/terragrunt.git="+localTmpEnvPath+" --terragrunt-non-interactive --terragrunt-working-dir "+rootPath)
+	require.NoError(t, err)
+
+	// validate that the source map was used to replace the source
+	assert.NotContains(t, stderr, "app1 (git::https://github.com/gruntwork-io/terragrunt.git//test/fixtures/stacks/basic/units/chick?ref=main&depth=1)")
+	assert.NotContains(t, stderr, "app2 (git::https://github.com/gruntwork-io/terragrunt.git//test/fixtures/stacks/basic/units/chick?ref=main&depth=1)")
+
+	assert.Contains(t, stderr, "Processing unit app1")
+	assert.Contains(t, stderr, "Processing unit app2")
+}
+
+func TestStacksSourceMapModule(t *testing.T) {
+	t.Parallel()
+	// prepare local environment with remote to use source map to replace
+	helpers.CleanupTerraformFolder(t, testFixtureStackSourceMap)
+	tmpEnvPath := helpers.CopyEnvironment(t, testFixtureStackSourceMap)
+	rootPath := util.JoinPath(tmpEnvPath, testFixtureStackSourceMap)
+
+	_, stderr, err := helpers.RunTerragruntCommandWithOutput(t, "terragrunt stack generate --experiment stacks --source-map git::https://git-host.com/not-existing-repo.git="+tmpEnvPath+" --terragrunt-log-level debug --terragrunt-working-dir "+rootPath)
+	require.NoError(t, err)
+	assert.NotContains(t, stderr, "git-host.com/not-existing-repo.git")
+	path := util.JoinPath(rootPath, ".terragrunt-stack")
+	validateStackDir(t, path)
+
+	_, stderr, err = helpers.RunTerragruntCommandWithOutput(t, "terragrunt stack run apply --terragrunt-log-level debug --experiment stacks --source-map git::https://git-host.com/not-existing-repo.git="+tmpEnvPath+"  --terragrunt-non-interactive --terragrunt-working-dir "+rootPath)
+	require.NoError(t, err)
+
+	assert.NotContains(t, stderr, "git-host.com/not-existing-repo.git")
+	assert.Contains(t, stderr, "Module ./.terragrunt-stack/app1")
+	assert.Contains(t, stderr, "Module ./.terragrunt-stack/app2")
+}
+
+func TestStacksGenerateAbsolutePath(t *testing.T) {
+	t.Parallel()
+
+	helpers.CleanupTerraformFolder(t, testFixtureStackAbsolutePath)
+	tmpEnvPath := helpers.CopyEnvironment(t, testFixtureStackAbsolutePath)
+	rootPath := util.JoinPath(tmpEnvPath, testFixtureStackAbsolutePath)
+	helpers.CreateGitRepo(t, rootPath)
+	helpers.RunTerragrunt(t, "terragrunt stack generate --terragrunt-log-level debug  --experiment stacks --terragrunt-working-dir "+rootPath)
+
+	path := util.JoinPath(rootPath, ".terragrunt-stack")
+	validateStackDir(t, path)
+
+	// check that apps directories are generated
+	app3 := util.JoinPath(rootPath, ".terragrunt-stack", "app3")
+	assert.DirExists(t, app3)
+
+	app1 := util.JoinPath(rootPath, "app1")
+	assert.DirExists(t, app1)
+
+	app2 := util.JoinPath(rootPath, "app2")
+	assert.DirExists(t, app2)
+
+	app1 = util.JoinPath(rootPath, ".terragrunt-stack", "app1")
+	assert.NoDirExists(t, app1)
+}
+
 // check if the stack directory is created and contains files.
 func validateStackDir(t *testing.T, path string) {
 	t.Helper()