Repository: gruntwork-io/terragrunt

Issue:
Boilerplate template cannot be used from local directory
## Describe the bug

When configuring catalog { }  in terragrunt configuration, default_template should give us the option to specify local path to the boilerplate template.  This will make testing and configuring boilerplate templates much easier. 

## Steps To Reproduce

Add catalog block to root.hcl

```hcl
catalog {
  default_template = "${get_repo_root()}/.boilerplate" ## tried with ".//.boilerplate" "./.boileplate"
  urls = [
    "url to remote repository",
  ]
}
```

Error returned when from the catalog i try to scaffold a terragrunt.hcl 

```
fatal: '{full-path}/terragrunt-root/.boilerplate' does not appear to be a git repository
fatal: Could not read from remote repository.


Please make sure you have the correct access rights
and the repository exists.
```

## Expected behavior

Scaffold to work with local boilerplate template and to actually scaffold a terragrunt.hcl file 

## Nice to haves

- [x] Terminal output
- [ ] Screenshots

## Versions

- Terragrunt version: v0.79.0
- OpenTofu/Terraform version: opentofu 1.9.0
- Environment details (Ubuntu 20.04, Windows 10, etc.): macos 15.5

## Additional context




Discussion:

Solution patch:
diff --git a/cli/commands/scaffold/scaffold.go b/cli/commands/scaffold/scaffold.go
index 85b62014..b8c32f52 100644
--- a/cli/commands/scaffold/scaffold.go
+++ b/cli/commands/scaffold/scaffold.go
@@ -440,6 +440,11 @@ func rewriteTemplateURL(ctx context.Context, l log.Logger, opts *options.Terragr
 			return nil, errors.New(err)
 		}
 
+		if rootSourceURL.Scheme == "" || rootSourceURL.Scheme == "file" {
+			l.Debugf("Skipping git tag lookup for local template path: %s", rootSourceURL)
+			return updatedTemplateURL, nil
+		}
+
 		tag, err := shell.GitLastReleaseTag(ctx, l, opts, rootSourceURL)
 		if err != nil || tag == "" {
 			l.Warnf("Failed to find last release tag for URL %s, so will not add a ref param to the URL", rootSourceURL)



Test patch:
diff --git a/test/fixtures/catalog/local-template/.boilerplate/boilerplate.yml b/test/fixtures/catalog/local-template/.boilerplate/boilerplate.yml
new file mode 100644
index 00000000..a9f82d5b
--- /dev/null
+++ b/test/fixtures/catalog/local-template/.boilerplate/boilerplate.yml
@@ -0,0 +1,8 @@
+variables:
+  - name: EnableRootInclude
+    description: Should include root module
+    type: bool
+    default: true
+  - name: RootFileName
+    description: Name of the root Terragrunt configuration file
+    type: string
diff --git a/test/fixtures/catalog/local-template/.boilerplate/custom-template.txt b/test/fixtures/catalog/local-template/.boilerplate/custom-template.txt
new file mode 100644
index 00000000..94ccf764
--- /dev/null
+++ b/test/fixtures/catalog/local-template/.boilerplate/custom-template.txt
@@ -0,0 +1 @@
+This file proves that the local template was used for scaffolding.
diff --git a/test/fixtures/catalog/local-template/.boilerplate/terragrunt.hcl b/test/fixtures/catalog/local-template/.boilerplate/terragrunt.hcl
new file mode 100644
index 00000000..f33f6d43
--- /dev/null
+++ b/test/fixtures/catalog/local-template/.boilerplate/terragrunt.hcl
@@ -0,0 +1,24 @@
+# Custom local template
+terraform {
+  source = "{{ .sourceUrl }}"
+}
+
+include "root" {
+  path = find_in_parent_folders("root.hcl")
+}
+
+inputs = {
+  # This is a custom template from a local directory
+  template_type = "local"
+
+  # Required variables would be listed here if any exist
+  {{- if .requiredVariables }}
+  {{- range .requiredVariables }}
+  # {{ if .Description }}{{ .Description }}{{ else }}Variable: {{ .Name }}{{ end }}
+  # Type: {{ .Type }}
+  {{ .Name }} = {{ .DefaultValuePlaceholder }}  # TODO: fill in value
+  {{- end }}
+  {{- else }}
+  # No required variables found for this module
+  {{- end }}
+}
diff --git a/test/fixtures/catalog/local-template/app/.gitkeep b/test/fixtures/catalog/local-template/app/.gitkeep
new file mode 100644
index 00000000..b5766c51
--- /dev/null
+++ b/test/fixtures/catalog/local-template/app/.gitkeep
@@ -0,0 +1 @@
+# This file ensures the app directory exists
diff --git a/test/fixtures/catalog/local-template/root.hcl b/test/fixtures/catalog/local-template/root.hcl
new file mode 100644
index 00000000..27b900bf
--- /dev/null
+++ b/test/fixtures/catalog/local-template/root.hcl
@@ -0,0 +1,4 @@
+catalog {
+  urls             = ["."]
+  default_template = "${get_parent_terragrunt_dir()}/.boilerplate"
+}
diff --git a/test/integration_catalog_test.go b/test/integration_catalog_test.go
index 752653db..ed7ba092 100644
--- a/test/integration_catalog_test.go
+++ b/test/integration_catalog_test.go
@@ -11,10 +11,15 @@ import (
 	"github.com/gruntwork-io/terragrunt/options"
 	"github.com/gruntwork-io/terragrunt/test/helpers"
 	"github.com/gruntwork-io/terragrunt/test/helpers/logger"
+	"github.com/gruntwork-io/terragrunt/util"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
 
+const (
+	testFixtureCatalogLocalTemplate = "fixtures/catalog/local-template"
+)
+
 func TestCatalogGitRepoUpdate(t *testing.T) {
 	t.Parallel()
 
@@ -124,6 +129,31 @@ func TestScaffoldGitModuleHttps(t *testing.T) {
 	helpers.RunTerragrunt(t, "terragrunt init --non-interactive --working-dir "+opts.WorkingDir)
 }
 
+func TestCatalogWithLocalDefaultTemplate(t *testing.T) {
+	t.Parallel()
+
+	tmpEnvPath := helpers.CopyEnvironment(t, testFixtureCatalogLocalTemplate, ".boilerplate")
+	helpers.CleanupTerraformFolder(t, tmpEnvPath)
+	rootPath := util.JoinPath(tmpEnvPath, testFixtureCatalogLocalTemplate)
+
+	targetPath := filepath.Join(rootPath, "app")
+	moduleURL := "github.com/gruntwork-io/terragrunt//test/fixtures/inputs"
+
+	_, stderr, err := helpers.RunTerragruntCommandWithOutput(
+		t,
+		"terragrunt scaffold --non-interactive --working-dir "+targetPath+" "+moduleURL,
+	)
+
+	require.NoError(t, err)
+	assert.Contains(t, stderr, "Scaffolding completed")
+	assert.FileExists(t, filepath.Join(targetPath, "terragrunt.hcl"))
+	assert.FileExists(t, filepath.Join(targetPath, "custom-template.txt"))
+
+	content, err := util.ReadFileAsString(filepath.Join(targetPath, "terragrunt.hcl"))
+	require.NoError(t, err)
+	assert.Contains(t, content, "# Custom local template")
+}
+
 func readConfig(t *testing.T, opts *options.TerragruntOptions) *config.TerragruntConfig {
 	t.Helper()
 
diff --git a/test/integration_parse_test.go b/test/integration_parse_test.go
index b64a7210..03ed2214 100644
--- a/test/integration_parse_test.go
+++ b/test/integration_parse_test.go
@@ -23,11 +23,12 @@ import (
 )
 
 var knownBadFiles = []string{
-	"fixtures/hclvalidate/second/a/terragrunt.hcl",
+	"fixtures/catalog/local-template/.boilerplate/terragrunt.hcl",
+	"fixtures/disabled/unit-disabled/terragrunt.hcl",
 	"fixtures/hclfmt-errors/dangling-attribute/terragrunt.hcl",
 	"fixtures/hclfmt-errors/invalid-character/terragrunt.hcl",
 	"fixtures/hclfmt-errors/invalid-key/terragrunt.hcl",
-	"fixtures/disabled/unit-disabled/terragrunt.hcl",
+	"fixtures/hclvalidate/second/a/terragrunt.hcl",
 }
 
 func TestParseAllFixtureFiles(t *testing.T) {