@@ -104,6 +104,15 @@ type Discovery struct {
104104
105105 // includeHiddenDirs is a list of hidden directory names that should be included in discovery.
106106 includeHiddenDirs []string
107+
108+ // includeDirs is a list of directory patterns to include in discovery (for strict include mode).
109+ includeDirs []string
110+
111+ // strictInclude determines whether to use strict include mode (only include directories that match includeDirs).
112+ strictInclude bool
113+
114+ // excludeByDefault determines whether to exclude configurations by default (triggered by include flags).
115+ excludeByDefault bool
107116}
108117
109118// DiscoveryOption is a function that modifies a Discovery.
@@ -117,6 +126,8 @@ func NewDiscovery(dir string, opts ...DiscoveryOption) *Discovery {
117126 discovery := & Discovery {
118127 workingDir : dir ,
119128 hidden : false ,
129+ // Include .terragrunt-stack by default, similar to the runner pool builder
130+ includeHiddenDirs : []string {config .StackDir },
120131 }
121132
122133 for _ , opt := range opts {
@@ -211,6 +222,24 @@ func (d *Discovery) WithIncludeHiddenDirs(dirs []string) *Discovery {
211222 return d
212223}
213224
225+ // WithIncludeDirs sets the includeDirs field to the given list.
226+ func (d * Discovery ) WithIncludeDirs (dirs []string ) * Discovery {
227+ d .includeDirs = dirs
228+ return d
229+ }
230+
231+ // WithStrictInclude sets the strictInclude flag to true.
232+ func (d * Discovery ) WithStrictInclude () * Discovery {
233+ d .strictInclude = true
234+ return d
235+ }
236+
237+ // WithExcludeByDefault sets the excludeByDefault flag to true.
238+ func (d * Discovery ) WithExcludeByDefault () * Discovery {
239+ d .excludeByDefault = true
240+ return d
241+ }
242+
214243// String returns a string representation of a DiscoveredConfig.
215244func (c * DiscoveredConfig ) String () string {
216245 return c .Path
@@ -311,6 +340,38 @@ func (d *Discovery) isInHiddenDirectory(path string) bool {
311340 return false
312341}
313342
343+ // matchesIncludePatterns returns true if the path matches any of the include directory patterns.
344+ func (d * Discovery ) matchesIncludePatterns (path string ) bool {
345+ if len (d .includeDirs ) == 0 {
346+ return true
347+ }
348+
349+ // Get the relative path from the working directory
350+ relPath , err := filepath .Rel (d .workingDir , path )
351+ if err != nil {
352+ // If we can't get a relative path, use the absolute path
353+ relPath = path
354+ }
355+ relPathSlash := filepath .ToSlash (relPath )
356+
357+ for _ , pattern := range d .includeDirs {
358+ // Convert pattern to slash format
359+ patternSlash := filepath .ToSlash (pattern )
360+
361+ // Check if the relative path matches the pattern as a glob
362+ if matched , err := filepath .Match (patternSlash , relPathSlash ); err == nil && matched {
363+ return true
364+ }
365+
366+ // Also check if the relative path is a subdirectory of the pattern
367+ if strings .HasPrefix (relPathSlash , patternSlash + "/" ) {
368+ return true
369+ }
370+ }
371+
372+ return false
373+ }
374+
314375// Discover discovers Terragrunt configurations in the WorkingDir.
315376func (d * Discovery ) Discover (ctx context.Context , l log.Logger , opts * options.TerragruntOptions ) (DiscoveredConfigs , error ) {
316377 var cfgs DiscoveredConfigs
@@ -337,14 +398,16 @@ func (d *Discovery) Discover(ctx context.Context, l log.Logger, opts *options.Te
337398 base := filepath .Base (path )
338399 for _ , fname := range filenames {
339400 if base == fname {
401+ configDir := filepath .Dir (path )
402+
340403 cfgType := ConfigTypeUnit
341404 if fname == config .DefaultStackFile {
342405 cfgType = ConfigTypeStack
343406 }
344407
345408 cfg := & DiscoveredConfig {
346409 Type : cfgType ,
347- Path : filepath . Dir ( path ) ,
410+ Path : configDir ,
348411 }
349412 if d .discoveryContext != nil {
350413 cfg .DiscoveryContext = d .discoveryContext
@@ -411,6 +474,14 @@ func (d *Discovery) Discover(ctx context.Context, l log.Logger, opts *options.Te
411474 dependencyDiscovery = dependencyDiscovery .WithSuppressParseErrors ()
412475 }
413476
477+ // Pass include patterns and strict mode to dependency discovery
478+ if len (d .includeDirs ) > 0 {
479+ dependencyDiscovery = dependencyDiscovery .WithIncludeDirs (d .includeDirs )
480+ }
481+ if d .strictInclude {
482+ dependencyDiscovery = dependencyDiscovery .WithStrictInclude ()
483+ }
484+
414485 err := dependencyDiscovery .DiscoverAllDependencies (ctx , l , opts )
415486 if err != nil {
416487 l .Warnf ("Parsing errors where encountered while discovering dependencies. They were suppressed, and can be found in the debug logs." )
@@ -462,6 +533,8 @@ type DependencyDiscovery struct {
462533 depthRemaining int
463534 discoverExternal bool
464535 suppressParseErrors bool
536+ includeDirs []string
537+ strictInclude bool
465538}
466539
467540// DependencyDiscoveryOption is a function that modifies a DependencyDiscovery.
@@ -494,6 +567,45 @@ func (d *DependencyDiscovery) WithDiscoveryContext(discoveryContext *DiscoveryCo
494567 return d
495568}
496569
570+ // WithIncludeDirs sets the includeDirs field to the given list.
571+ func (d * DependencyDiscovery ) WithIncludeDirs (dirs []string ) * DependencyDiscovery {
572+ d .includeDirs = dirs
573+ return d
574+ }
575+
576+ // WithStrictInclude sets the strictInclude flag to true.
577+ func (d * DependencyDiscovery ) WithStrictInclude () * DependencyDiscovery {
578+ d .strictInclude = true
579+ return d
580+ }
581+
582+ // matchesIncludePatterns returns true if the path matches any of the include directory patterns.
583+ func (d * DependencyDiscovery ) matchesIncludePatterns (path string ) bool {
584+ if len (d .includeDirs ) == 0 {
585+ return true
586+ }
587+
588+ // Convert path to slash format for consistent matching
589+ pathSlash := filepath .ToSlash (path )
590+
591+ for _ , pattern := range d .includeDirs {
592+ // Convert pattern to slash format
593+ patternSlash := filepath .ToSlash (pattern )
594+
595+ // Check if the path matches the pattern as a glob
596+ if matched , err := filepath .Match (patternSlash , pathSlash ); err == nil && matched {
597+ return true
598+ }
599+
600+ // Also check if the path is a subdirectory of the pattern
601+ if strings .HasPrefix (pathSlash , patternSlash + "/" ) {
602+ return true
603+ }
604+ }
605+
606+ return false
607+ }
608+
497609func (d * DependencyDiscovery ) DiscoverAllDependencies (ctx context.Context , l log.Logger , opts * options.TerragruntOptions ) error {
498610 errs := []error {}
499611
@@ -587,13 +699,23 @@ func (d *DependencyDiscovery) DiscoverDependencies(ctx context.Context, l log.Lo
587699 if c .Path == depPath {
588700 external = false
589701
702+ // In strict include mode, only add dependencies that match the include patterns
703+ if d .strictInclude && ! d .matchesIncludePatterns (depPath ) {
704+ continue
705+ }
706+
590707 dCfg .Dependencies = append (dCfg .Dependencies , c )
591708
592709 continue
593710 }
594711 }
595712
596713 if external {
714+ // In strict include mode, only add external dependencies that match the include patterns
715+ if d .strictInclude && ! d .matchesIncludePatterns (depPath ) {
716+ continue
717+ }
718+
597719 ext := & DiscoveredConfig {
598720 Type : ConfigTypeUnit ,
599721 Path : depPath ,
0 commit comments