From: Alexandre Vaz Date: Mon, 23 Mar 2026 16:49:13 +0000 (-0300) Subject: commands: Preserve non-content files in convert output X-Git-Url: http://git.maquefel.me/?a=commitdiff_plain;h=c4fb61d9dfb7a2a638ef69ef8ba4e4dc3ecd93ef;p=brevno-suite%2Fhugo commands: Preserve non-content files in convert output When running `hugo convert` with `--output`, copy the content tree first so non-content bundle resources are kept in the destination, then overwrite converted content files. Also avoid recursive self-copy when the output path points inside the content tree by skipping output directories during copy. Fixes #4621 --- diff --git a/commands/convert.go b/commands/convert.go index ebf81cfb3..069829849 100644 --- a/commands/convert.go +++ b/commands/convert.go @@ -18,10 +18,12 @@ import ( "context" "fmt" "path/filepath" + "slices" "strings" "time" "github.com/bep/simplecobra" + "github.com/gohugoio/hugo/common/hugio" "github.com/gohugoio/hugo/config" "github.com/gohugoio/hugo/helpers" "github.com/gohugoio/hugo/hugofs" @@ -200,6 +202,56 @@ func (c *convertCommand) convertAndSavePage(p page.Page, site *hugolib.Site, tar return nil } +func (c *convertCommand) copyContentDirsForOutput(pagesBackedByFile page.Pages) error { + contentDirs := make(map[string]bool) + for _, p := range pagesBackedByFile { + filename := p.File().Filename() + contentDir := strings.TrimSuffix(filename, p.File().Path()) + if contentDir == filename { + continue + } + contentDirs[filepath.Clean(contentDir)] = true + } + + var contentDirList []string + for contentDir := range contentDirs { + contentDirList = append(contentDirList, contentDir) + } + slices.Sort(contentDirList) + + outputDirAbs, err := filepath.Abs(c.outputDir) + if err != nil { + return fmt.Errorf("failed to resolve output path %q: %w", c.outputDir, err) + } + + for _, contentDir := range contentDirList { + outputContentDirAbs := filepath.Join(outputDirAbs, filepath.Base(contentDir)) + + skipDirs := make(map[string]bool) + relToOutputDir, err := filepath.Rel(contentDir, outputDirAbs) + if err == nil && relToOutputDir != ".." && !strings.HasPrefix(relToOutputDir, ".."+string(filepath.Separator)) { + skipDirs[filepath.Clean(outputDirAbs)] = true + } + relToOutputContentDir, err := filepath.Rel(contentDir, outputContentDirAbs) + if err == nil && relToOutputContentDir != ".." && !strings.HasPrefix(relToOutputContentDir, ".."+string(filepath.Separator)) { + skipDirs[filepath.Clean(outputContentDirAbs)] = true + } + + var shouldCopy func(filename string) bool + if len(skipDirs) > 0 { + shouldCopy = func(filename string) bool { + return !skipDirs[filepath.Clean(filename)] + } + } + + if err := hugio.CopyDir(hugofs.Os, contentDir, outputContentDirAbs, shouldCopy); err != nil { + return fmt.Errorf("failed to copy %q to %q: %w", contentDir, outputContentDirAbs, err) + } + } + + return nil +} + func (c *convertCommand) convertContents(format metadecoders.Format) error { if c.outputDir == "" && !c.unsafe { return newUserError("Unsafe operation not allowed, use --unsafe or set a different output path") @@ -219,6 +271,12 @@ func (c *convertCommand) convertContents(format metadecoders.Format) error { pagesBackedByFile = append(pagesBackedByFile, p) } + if c.outputDir != "" { + if err := c.copyContentDirsForOutput(pagesBackedByFile); err != nil { + return err + } + } + site.Log.Println("processing", len(pagesBackedByFile), "content files") for _, p := range site.AllPages() { if err := c.convertAndSavePage(p, site, format); err != nil { diff --git a/testscripts/commands/convert.txt b/testscripts/commands/convert.txt index 811aeecc8..4c325f17b 100644 --- a/testscripts/commands/convert.txt +++ b/testscripts/commands/convert.txt @@ -10,14 +10,21 @@ hugo convert toYAML -h stdout 'to use YAML for the front matter' hugo convert toJSON -o myjsoncontent -stdout 'processing 3 content files' +stdout 'processing 4 content files' grep '^{' myjsoncontent/content/mytoml.md grep '^{' myjsoncontent/content/myjson.md grep '^{' myjsoncontent/content/myyaml.md +grep '^{' myjsoncontent/content/bundle/index.md +exists myjsoncontent/content/bundle/data.txt +exists myjsoncontent/content/bundle/nested/asset.dat hugo convert toYAML -o myyamlcontent -stdout 'processing 3 content files' +stdout 'processing 4 content files' +exists myyamlcontent/content/bundle/data.txt +exists myyamlcontent/content/bundle/nested/asset.dat hugo convert toTOML -o mytomlcontent -stdout 'processing 3 content files' +stdout 'processing 4 content files' +exists mytomlcontent/content/bundle/data.txt +exists mytomlcontent/content/bundle/nested/asset.dat @@ -40,3 +47,12 @@ JSON content title: YAML --- YAML content +-- content/bundle/index.md -- +--- +title: Bundle +--- +Bundle content +-- content/bundle/data.txt -- +bundle resource +-- content/bundle/nested/asset.dat -- +nested resource