Repository: gruntwork-io/terragrunt

Issue:
Add `--disable-dependency-prompt` flag to terragrunt scaffold
## Describe the enhancement

When running `terragrunt scaffold` command, the user always gets prompted to answer whether the boilerplate will be run for base and leaf file templates or not:

```
This boilerplate template has a dependency! Run boilerplate on dependency base with template folder ../.boilerplate-files/base-file and output folder {{ .baseFilePath }}? (y/n) : y
```

The `--disable-dependency-prompt` flag is already made available in the [boilerplate](https://github.com/gruntwork-io/boilerplate?tab=readme-ov-file#boilerplate-command-line-options) package. The enhancement would in this case mean the flag is made available when running `terragrunt scaffold`.

## Additional context

If there are base and leaf file boilerplate templates, the user will most likely want to use them, hence the idea of adding the `--disable-dependency-prompt`. Setting its default value to `false` will also mean the flag stays transparent for the users and they can changed it when needed.



Discussion:

Solution patch:
diff --git a/cli/commands/scaffold/cli.go b/cli/commands/scaffold/cli.go
index 391aeb6a..e1d645bb 100644
--- a/cli/commands/scaffold/cli.go
+++ b/cli/commands/scaffold/cli.go
@@ -23,6 +23,7 @@ const (
 	OutputFolderFlagName  = "output-folder"
 	VarFlagName           = "var"
 	VarFileFlagName       = "var-file"
+	NoDependencyPrompt    = "no-dependency-prompt"
 )
 
 func NewFlags(opts *options.TerragruntOptions, prefix flags.Prefix) cli.Flags {
@@ -79,6 +80,13 @@ func NewFlags(opts *options.TerragruntOptions, prefix flags.Prefix) cli.Flags {
 			Destination: &opts.ScaffoldVarFiles,
 			Usage:       "Files with variables to be used in unit scaffolding.",
 		}),
+
+		flags.NewFlag(&cli.BoolFlag{
+			Name:        NoDependencyPrompt,
+			EnvVars:     tgPrefix.EnvVars(NoDependencyPrompt),
+			Destination: &opts.NoDependencyPrompt,
+			Usage:       "Do not prompt for confirmation to include dependencies.",
+		}),
 	}
 }
 
diff --git a/cli/commands/scaffold/scaffold.go b/cli/commands/scaffold/scaffold.go
index b8c32f52..5c00169c 100644
--- a/cli/commands/scaffold/scaffold.go
+++ b/cli/commands/scaffold/scaffold.go
@@ -197,14 +197,15 @@ func Run(ctx context.Context, l log.Logger, opts *options.TerragruntOptions, mod
 
 	l.Infof("Running boilerplate generation to %s", outputDir)
 	boilerplateOpts := &boilerplate_options.BoilerplateOptions{
-		OutputFolder:    outputDir,
-		OnMissingKey:    boilerplate_options.DefaultMissingKeyAction,
-		OnMissingConfig: boilerplate_options.DefaultMissingConfigAction,
-		Vars:            vars,
-		DisableShell:    true,
-		DisableHooks:    true,
-		NonInteractive:  opts.NonInteractive,
-		TemplateFolder:  boilerplateDir,
+		OutputFolder:            outputDir,
+		OnMissingKey:            boilerplate_options.DefaultMissingKeyAction,
+		OnMissingConfig:         boilerplate_options.DefaultMissingConfigAction,
+		Vars:                    vars,
+		DisableShell:            true,
+		DisableHooks:            true,
+		NonInteractive:          opts.NonInteractive,
+		DisableDependencyPrompt: opts.NoDependencyPrompt,
+		TemplateFolder:          boilerplateDir,
 	}
 
 	emptyDep := variables.Dependency{}
diff --git a/config/stack.go b/config/stack.go
index a7dee6d0..01ea09d4 100644
--- a/config/stack.go
+++ b/config/stack.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"os"
 	"path/filepath"
+	"sort"
 	"strings"
 
 	"github.com/gruntwork-io/terragrunt/pkg/log"
@@ -797,8 +798,19 @@ func writeValues(l log.Logger, values *cty.Value, directory string) error {
 		},
 	})
 
-	for key, val := range values.AsValueMap() {
-		body.SetAttributeValue(key, val)
+	// Sort keys for deterministic output
+	valueMap := values.AsValueMap()
+
+	keys := make([]string, 0, len(valueMap))
+	for key := range valueMap {
+		keys = append(keys, key)
+	}
+
+	// Sort keys alphabetically
+	sort.Strings(keys)
+
+	for _, key := range keys {
+		body.SetAttributeValue(key, valueMap[key])
 	}
 
 	if err := os.WriteFile(filePath, file.Bytes(), valueFilePerm); err != nil {
diff --git a/docs-starlight/src/content/docs/02-features/07-scaffold.md b/docs-starlight/src/content/docs/02-features/07-scaffold.md
index 6c039fc1..8dda5853 100644
--- a/docs-starlight/src/content/docs/02-features/07-scaffold.md
+++ b/docs-starlight/src/content/docs/02-features/07-scaffold.md
@@ -11,7 +11,7 @@ Terragrunt scaffolding can generate files for you automatically using [boilerpla
 Currently, one boilerplate template is supported out-of-the-box, which you can use to generate a best-practices `terragrunt.hcl` that configures an OpenTofu/Terraform module for deployment:
 
 ```bash
-terragrunt scaffold <MODULE_URL> [TEMPLATE_URL] [--var] [--var-file] [--no-include-root] [--root-file-name]
+terragrunt scaffold <MODULE_URL> [TEMPLATE_URL] [--var] [--var-file] [--no-include-root] [--root-file-name] [--no-dependency-prompt]
 ```
 
 Description:
@@ -102,6 +102,7 @@ Optional variables which can be passed to `scaffold` command:
 
 - `--no-include-root` - Disable inclusion of the root module in the generated `terragrunt.hcl` file (equivalent to using `--var=EnableRootInclude=false`, and will be overridden if the corresponding `var` value is set).
 - `--root-file-name` - Set the name of the root configuration file to include in the generated `terragrunt.hcl` file (equivalent to using `--var=RootFileName=<name>`, and will be overridden if the corresponding `var` value is set).
+- `--no-dependency-prompt` - Disable dependency confirmation, but keep the interactive mode enabled (skip asking for confirmation about including dependencies defined in the boilerplate template).
 
 \* **NOTE**: `RootFileName` is set to `terragrunt.hcl` by default to ensure backwards compatibility, but the pattern of using a `terragrunt.hcl` file at the root of Terragrunt projects has since been deprecated.
 
diff --git a/docs/_docs/02_features/07-scaffold.md b/docs/_docs/02_features/07-scaffold.md
index 8fc2e2ba..1f9eb848 100644
--- a/docs/_docs/02_features/07-scaffold.md
+++ b/docs/_docs/02_features/07-scaffold.md
@@ -16,7 +16,7 @@ Terragrunt scaffolding can generate files for you automatically using [boilerpla
 Currently, one boilerplate template is supported out-of-the-box, which you can use to generate a best-practices `terragrunt.hcl` that configures a OpenTofu/Terraform module for deployment:
 
 ```bash
-terragrunt scaffold <MODULE_URL> [TEMPLATE_URL] [--var] [--var-file] [--no-include-root] [--root-file-name]
+terragrunt scaffold <MODULE_URL> [TEMPLATE_URL] [--var] [--var-file] [--no-include-root] [--root-file-name] [--no-dependency-prompt]
 ```
 
 Description:
@@ -105,6 +105,7 @@ Optional variables which can be passed to `scaffold` command:
 
 - `--no-include-root` - Disable inclusion of the root module in the generated `terragrunt.hcl` file (equivalent to using `--var=EnableRootInclude=false`, and will be overridden if the corresponding `var` value is set).
 - `--root-file-name` - Set the name of the root configuration file to include in the generated `terragrunt.hcl` file (equivalent to using `--var=RootFileName=<name>`, and will be overridden if the corresponding `var` value is set).
+- `--no-dependency-prompt` - Disable dependency confirmation, but keep the interactive mode enabled (skip asking for confirmation about including dependencies defined in the boilerplate template).
 
 \* **NOTE**: `RootFileName` is set to `terragrunt.hcl` by default to ensure backwards compatibility, but the pattern of using a `terragrunt.hcl` file at the root of Terragrunt projects has since been deprecated.
 
diff --git a/options/options.go b/options/options.go
index 6b44c598..ce42645b 100644
--- a/options/options.go
+++ b/options/options.go
@@ -327,6 +327,8 @@ type TerragruntOptions struct {
 	TFPathExplicitlySet bool
 	// FailFast is a flag to stop execution on the first error in apply of units.
 	FailFast bool
+	// NoDependencyPrompt disables prompt requiring confirmation for base and leaf file dependencies when using scaffolding.
+	NoDependencyPrompt bool
 }
 
 // TerragruntOptionsFunc is a functional option type used to pass options in certain integration tests
@@ -435,6 +437,7 @@ func NewTerragruntOptionsWithWriters(stdout, stderr io.Writer) *TerragruntOption
 		NoStackGenerate:            false,
 		VersionManagerFileName:     defaultVersionManagerFileName,
 		NoAutoProviderCacheDir:     false,
+		NoDependencyPrompt:         false,
 	}
 }
 



Test patch:
diff --git a/cli/commands/scaffold/scaffold_test.go b/cli/commands/scaffold/scaffold_test.go
index 7bfc7552..be7258c5 100644
--- a/cli/commands/scaffold/scaffold_test.go
+++ b/cli/commands/scaffold/scaffold_test.go
@@ -62,14 +62,15 @@ func TestDefaultTemplateVariables(t *testing.T) {
 	require.NoError(t, err)
 
 	boilerplateOpts := &boilerplateoptions.BoilerplateOptions{
-		OutputFolder:    outputDir,
-		OnMissingKey:    boilerplateoptions.DefaultMissingKeyAction,
-		OnMissingConfig: boilerplateoptions.DefaultMissingConfigAction,
-		Vars:            vars,
-		DisableShell:    true,
-		DisableHooks:    true,
-		NonInteractive:  true,
-		TemplateFolder:  templateDir,
+		OutputFolder:            outputDir,
+		OnMissingKey:            boilerplateoptions.DefaultMissingKeyAction,
+		OnMissingConfig:         boilerplateoptions.DefaultMissingConfigAction,
+		Vars:                    vars,
+		DisableShell:            true,
+		DisableHooks:            true,
+		NonInteractive:          true,
+		DisableDependencyPrompt: false,
+		TemplateFolder:          templateDir,
 	}
 
 	emptyDep := variables.Dependency{}
diff --git a/config/stack_test.go b/config/stack_test.go
index 85a7177c..abbcc52d 100644
--- a/config/stack_test.go
+++ b/config/stack_test.go
@@ -1,9 +1,13 @@
 package config_test
 
 import (
+	"os"
+	"path/filepath"
+	"strings"
 	"testing"
 
 	"github.com/gruntwork-io/terragrunt/config"
+	"github.com/gruntwork-io/terragrunt/test/helpers"
 	"github.com/gruntwork-io/terragrunt/test/helpers/logger"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
@@ -165,3 +169,158 @@ locals {
 	require.Error(t, err)
 	assert.Contains(t, err.Error(), "Invalid multi-line string")
 }
+
+func TestWriteValuesSortsKeys(t *testing.T) {
+	t.Parallel()
+
+	tmpDir := t.TempDir()
+	valuesFilePath := setupTestFiles(t, tmpDir)
+
+	// Helper function to read and return the values file content
+	readValuesFile := func() string {
+		content, err := os.ReadFile(valuesFilePath)
+		require.NoError(t, err)
+
+		return string(content)
+	}
+
+	// Run multiple generations to test for deterministic behavior
+	const numIterations = 5
+	generationContents := make([]string, 0, numIterations)
+
+	for iteration := range numIterations {
+		// Clean up any existing stack directory
+		stackDir := filepath.Join(tmpDir, ".terragrunt-stack")
+		os.RemoveAll(stackDir)
+
+		// Generate the stack
+		_, _, err := helpers.RunTerragruntCommandWithOutput(t, "terragrunt stack generate --working-dir "+tmpDir)
+		require.NoError(t, err)
+		require.FileExists(t, valuesFilePath)
+
+		content := readValuesFile()
+		generationContents = append(generationContents, content)
+
+		t.Logf("Generation %d content:\n%s\n", iteration+1, content)
+	}
+
+	// Extract only the complex verification logic to reduce cyclomatic complexity
+	verifyDeterministicSortedOutput(t, generationContents)
+}
+
+// setupTestFiles creates the test environment and returns the values file path.
+func setupTestFiles(t *testing.T, tmpDir string) string {
+	t.Helper()
+
+	// Create a test stack configuration with more values in non-alphabetical order
+	stackConfig := `
+unit "test_unit" {
+	source = "./unit"
+	path   = "test_unit"
+	values = {
+		zzz_last    = "should be last"
+		aaa_first   = "should be first"
+		mmm_middle  = "should be middle"
+		beta        = 42
+		gamma       = true
+		delta       = ["a", "b"]
+		zebra       = "animal"
+		alpha       = "letter"
+		omega       = "end"
+		charlie     = "nato"
+	}
+}
+`
+
+	// Create the stack file
+	stackFilePath := filepath.Join(tmpDir, "terragrunt.stack.hcl")
+	err := os.WriteFile(stackFilePath, []byte(stackConfig), 0644)
+	require.NoError(t, err)
+
+	// Create a simple unit directory with minimal terragrunt config
+	unitDir := filepath.Join(tmpDir, "unit")
+	err = os.MkdirAll(unitDir, 0755)
+	require.NoError(t, err)
+
+	unitConfig := `
+terraform {
+	source = "."
+}
+`
+	unitConfigPath := filepath.Join(unitDir, "terragrunt.hcl")
+	err = os.WriteFile(unitConfigPath, []byte(unitConfig), 0644)
+	require.NoError(t, err)
+
+	// Create a minimal main.tf in the unit
+	mainTf := `
+resource "local_file" "test" {
+	content  = "test"
+	filename = "test.txt"
+}
+`
+	mainTfPath := filepath.Join(unitDir, "main.tf")
+	err = os.WriteFile(mainTfPath, []byte(mainTf), 0644)
+	require.NoError(t, err)
+
+	return filepath.Join(tmpDir, ".terragrunt-stack", "test_unit", "terragrunt.values.hcl")
+}
+
+// verifyDeterministicSortedOutput checks that all generations are identical and sorted.
+func verifyDeterministicSortedOutput(t *testing.T, generationContents []string) {
+	t.Helper()
+
+	// Check if all generations produced identical output
+	allIdentical := true
+	for i := 1; i < len(generationContents); i++ {
+		if generationContents[i] != generationContents[0] {
+			allIdentical = false
+
+			break
+		}
+	}
+
+	if !allIdentical {
+		t.Logf("Non-deterministic behavior detected! Generations produced different output:")
+		for i, content := range generationContents {
+			t.Logf("Generation %d:\n%s\n", i+1, content)
+		}
+		assert.True(t, allIdentical, "Stack generation should be deterministic - all runs should produce identical values files")
+
+		return
+	}
+
+	t.Logf("All generations produced identical output - checking if it's sorted...")
+
+	// Now test the actual content and ordering using the first generation
+	contentStr := generationContents[0]
+
+	// Check if the keys appear in alphabetical order
+	keys := []string{"aaa_first", "alpha", "beta", "charlie", "delta", "gamma", "mmm_middle", "omega", "zebra", "zzz_last"}
+
+	positions := make([]int, len(keys))
+	for i, key := range keys {
+		positions[i] = strings.Index(contentStr, key)
+		if positions[i] == -1 {
+			t.Fatalf("Key %s not found in generated content", key)
+		}
+	}
+
+	// Check if positions are in ascending order (alphabetical)
+	keysInOrder := true
+	for i := 1; i < len(positions); i++ {
+		if positions[i] < positions[i-1] {
+			keysInOrder = false
+
+			break
+		}
+	}
+
+	t.Logf("Key positions: %v", positions)
+	t.Logf("Keys in alphabetical order: %v", keysInOrder)
+
+	if !keysInOrder {
+		assert.True(t, keysInOrder, "Keys should appear in alphabetical order for deterministic output")
+	} else {
+		t.Logf("Keys are in alphabetical order - sorting implementation is working!")
+	}
+}
diff --git a/test/fixtures/scaffold/dependency-prompt-template/.boilerplate/boilerplate.yml b/test/fixtures/scaffold/dependency-prompt-template/.boilerplate/boilerplate.yml
new file mode 100644
index 00000000..ddf3b5dd
--- /dev/null
+++ b/test/fixtures/scaffold/dependency-prompt-template/.boilerplate/boilerplate.yml
@@ -0,0 +1,10 @@
+# boilerplate config
+
+dependencies:
+  - name: base
+    template-url: ../base
+    output-folder: base/
+
+  - name: leaf
+    template-url: ../leaf
+    output-folder: leaf/
diff --git a/test/fixtures/scaffold/dependency-prompt-template/base/boilerplate.yml b/test/fixtures/scaffold/dependency-prompt-template/base/boilerplate.yml
new file mode 100644
index 00000000..e69de29b
diff --git a/test/fixtures/scaffold/dependency-prompt-template/base/test.hcl b/test/fixtures/scaffold/dependency-prompt-template/base/test.hcl
new file mode 100644
index 00000000..e69de29b
diff --git a/test/fixtures/scaffold/dependency-prompt-template/leaf/boilerplate.yml b/test/fixtures/scaffold/dependency-prompt-template/leaf/boilerplate.yml
new file mode 100644
index 00000000..e69de29b
diff --git a/test/fixtures/scaffold/dependency-prompt-template/leaf/terragrunt.hcl b/test/fixtures/scaffold/dependency-prompt-template/leaf/terragrunt.hcl
new file mode 100644
index 00000000..9ac60921
--- /dev/null
+++ b/test/fixtures/scaffold/dependency-prompt-template/leaf/terragrunt.hcl
@@ -0,0 +1,8 @@
+# Project template dir
+terraform {
+  source = "{{ .sourceUrl }}"
+}
+
+inputs = {
+  project_name = "template"
+}
diff --git a/test/integration_scaffold_test.go b/test/integration_scaffold_test.go
index 8e210471..cb4b4b06 100644
--- a/test/integration_scaffold_test.go
+++ b/test/integration_scaffold_test.go
@@ -19,6 +19,7 @@ const (
 	testScaffoldLocalModulePath    = "fixtures/scaffold/scaffold-module"
 	testScaffoldWithRootHCL        = "fixtures/scaffold/root-hcl"
 	testScaffold3rdPartyModulePath = "git::https://github.com/Azure/terraform-azurerm-avm-res-compute-virtualmachine.git//.?ref=v0.15.0"
+	testScaffoldNoDependencyPrompt = "fixtures/scaffold/dependency-prompt-template"
 )
 
 func TestScaffoldModule(t *testing.T) {
@@ -154,3 +155,19 @@ func TestScaffoldWithRootHCL(t *testing.T) {
 	require.NoError(t, err)
 	assert.Contains(t, content, `path = find_in_parent_folders("root.hcl")`)
 }
+
+func TestScaffoldNoDependencyPrompt(t *testing.T) {
+	t.Parallel()
+
+	tmpEnvPath := t.TempDir()
+	workingDir, err := os.Getwd()
+	localBoilerplateModuleDir := fmt.Sprintf("%s/%s//.", workingDir, testScaffoldNoDependencyPrompt)
+
+	outputFolder := tmpEnvPath + "/foo/bar"
+	_, stderr, err := helpers.RunTerragruntCommandWithOutput(t, fmt.Sprintf("terragrunt scaffold --non-interactive --working-dir %s --no-dependency-prompt %s --output-folder %s", tmpEnvPath, localBoilerplateModuleDir, outputFolder))
+	require.NoError(t, err)
+	assert.NotContains(t, stderr, "This boilerplate template has a dependency!")
+	assert.FileExists(t, outputFolder+"/base/test.hcl")
+	assert.FileExists(t, outputFolder+"/leaf/terragrunt.hcl")
+	assert.Contains(t, stderr, "Scaffolding completed")
+}