diff --git a/docs/content/docs/architecture/template-engine.md b/docs/content/docs/architecture/template-engine.md index e1cbb19..1575d23 100644 --- a/docs/content/docs/architecture/template-engine.md +++ b/docs/content/docs/architecture/template-engine.md @@ -87,6 +87,22 @@ MARIADB_DATABASE: "[[ .ProjectShortName | toSnakeCase ]]_test" --- +## Always-ignored files + +The following files are silently skipped during rendering, regardless of any other +configuration. They are OS/editor metadata that should never appear in scaffolded output: + +| Filename | Origin | +|----------|--------| +| `.DS_Store` | macOS Finder | +| `Thumbs.db` | Windows Explorer | + +These files are skipped at the walk stage — they are never copied, rendered, or passed to +`.specsverbatim`. This list is fixed; use `.specsignore` if per-template suppression is +ever added in the future. + +--- + ## `.specsverbatim` — Verbatim Copy A `.specsverbatim` file at the template root lists glob patterns for files that should be diff --git a/internal/cmd/use.go b/internal/cmd/use.go index 990834b..364110f 100644 --- a/internal/cmd/use.go +++ b/internal/cmd/use.go @@ -3,7 +3,6 @@ package cmd import ( "fmt" "os" - "path/filepath" "github.com/spf13/cobra" @@ -65,11 +64,10 @@ func runUse(app *App, rawSource, targetDir string, opts executeOpts) error { } templateRoot = tmp } else { - // go-git requires the destination to not exist; use a subdirectory. - cloneDir := filepath.Join(tmp, "repo") app.Output.Info("cloning %s…", src.CloneURL) // git layer logs clone start/complete - if err := pkggit.Clone(src.CloneURL, cloneDir, pkggit.CloneOptions{Branch: src.Branch}); err != nil { + cloneDir, err := pkggit.CloneInto(tmp, "repo", src.CloneURL, pkggit.CloneOptions{Branch: src.Branch}) + if err != nil { return err } templateRoot = cloneDir diff --git a/internal/template/functions.go b/internal/template/functions.go index e0dc600..88b4f45 100644 --- a/internal/template/functions.go +++ b/internal/template/functions.go @@ -65,12 +65,13 @@ func FuncMap(cfg Config) texttemplate.FuncMap { } handler := sprout.New(sprout.WithLogger(slog.Default())) - if err := handler.AddRegistries(registries...); err != nil { - // Registry registration only fails on programmer error (duplicate names); - // panic here surfaces the issue during development rather than silently - // producing an incomplete FuncMap. + mustAddRegistries(handler, registries...) + return handler.Build() +} + +// mustAddRegistries registers regs into h, panicking on programmer error (duplicate names). +func mustAddRegistries(h *sprout.DefaultHandler, regs ...sprout.Registry) { + if err := h.AddRegistries(regs...); err != nil { panic("sprout registry registration failed: " + err.Error()) } - - return handler.Build() } diff --git a/internal/util/git/git.go b/internal/util/git/git.go index 8b908e1..5063ade 100644 --- a/internal/util/git/git.go +++ b/internal/util/git/git.go @@ -29,6 +29,17 @@ type CloneOptions struct { Depth int } +// CloneInto clones url into a new subdirectory named name inside parent. +// parent must already exist; name must not — go-git requires a non-existent destination. +// Returns the path of the created clone directory. +func CloneInto(parent, name, url string, opts CloneOptions) (string, error) { + dir := filepath.Join(parent, name) + if err := Clone(url, dir, opts); err != nil { + return "", err + } + return dir, nil +} + // Clone clones the repository at url into dir using a shallow clone (Depth 1 by default). // dir must not already exist — go-git creates it. // SSH URLs (git@host:path or ssh://host/path) are detected automatically and authenticated diff --git a/internal/util/git/git_test.go b/internal/util/git/git_test.go index 68d0667..7cc051e 100644 --- a/internal/util/git/git_test.go +++ b/internal/util/git/git_test.go @@ -75,3 +75,19 @@ func TestClone_InvalidURL(t *testing.T) { func TestClone_SSH(t *testing.T) { t.Skip("SSH clone not tested — see comment above") } + +func TestCloneInto_CreatesSubdirectory(t *testing.T) { + parent := t.TempDir() + + cloneDir, err := pkggit.CloneInto(parent, "repo", "https://github.com/specsnl/php85", pkggit.CloneOptions{Depth: 1}) + if err != nil { + t.Fatalf("CloneInto: %v", err) + } + + if cloneDir == parent { + t.Fatal("CloneInto returned parent dir instead of subdirectory") + } + if _, err := os.Stat(cloneDir + "/README.md"); os.IsNotExist(err) { + t.Error("cloned repo missing README.md") + } +}