Repository: gohugoio/hugo (go)

Issue:
Honor "outputs" front matter field in content adapters


```go
func TestIssue13689(t *testing.T) {
	t.Parallel()

	files := `
-- hugo.toml --
disableKinds = ['home','rss','section','sitemap','taxonomy','term']
[outputs]
page = ['html','json']
-- layouts/page.html --
html: {{ .Title }}
-- layouts/page.json --
json: {{ .Title }}
-- content/p1.md --
---
title: p1
---
-- content/p2.md --
---
title: p2
outputs:
  - html
---
-- content/_content.gotmpl --
{{ $page := dict "path" "p3" "title" "p3" }}
{{ $.AddPage $page }}

{{ $page := dict "path" "p4" "title" "p4" "outputs" (slice "html") }}
{{ $.AddPage $page }}
`

	b := hugolib.Test(t, files)

	b.AssertFileExists("public/p1/index.html", true)
	b.AssertFileExists("public/p1/index.json", true)
	b.AssertFileExists("public/p2/index.html", true)
	b.AssertFileExists("public/p2/index.json", false)
	b.AssertFileExists("public/p3/index.html", true)
	b.AssertFileExists("public/p3/index.json", true)
	b.AssertFileExists("public/p4/index.html", true)
	b.AssertFileExists("public/p4/index.json", false) // currently returns true
}
```

Although the example above tests regular pages, I would expect the page kind to be irrelevant.

Pull request description:
Fix handling of "outputs" from content adapter pages
Fixes #13689


Patch:
diff --git a/hugolib/content_map.go b/hugolib/content_map.go
index 56a602f54..454d36df7 100644
--- a/hugolib/content_map.go
+++ b/hugolib/content_map.go
@@ -356,7 +356,7 @@ func (m *pageMap) addPagesFromGoTmplFi(fi hugofs.FileMetaInfo, buildConfig *Buil
 				Watching:          s.Conf.Watching(),
 				HandlePage: func(pt *pagesfromdata.PagesFromTemplate, pc *pagemeta.PageConfig) error {
 					s := pt.Site.(*Site)
-					if err := pc.Compile(pt.GoTmplFi.Meta().PathInfo.Base(), true, "", s.Log, s.conf.MediaTypes.Config); err != nil {
+					if err := pc.Compile(pt.GoTmplFi.Meta().PathInfo.Base(), true, "", s.Log, s.conf.OutputFormats.Config, s.conf.MediaTypes.Config); err != nil {
 						return err
 					}
 
diff --git a/hugolib/page__meta.go b/hugolib/page__meta.go
index 9516c482a..94dbbab3e 100644
--- a/hugolib/page__meta.go
+++ b/hugolib/page__meta.go
@@ -54,7 +54,6 @@ type pageMeta struct {
 
 	resource.Staler
 	*pageMetaParams
-	pageMetaFrontMatter
 
 	// Set for standalone pages, e.g. robotsTXT.
 	standaloneOutputFormat output.Format
@@ -79,7 +78,6 @@ func (m *pageMeta) setMetaPostPrepareRebuild() {
 		Path:   m.pageConfig.Path,
 		Params: params,
 	}
-	m.pageMetaFrontMatter = pageMetaFrontMatter{}
 }
 
 type pageMetaParams struct {
@@ -94,11 +92,6 @@ type pageMetaParams struct {
 	cascadeOriginal *maps.Ordered[page.PageMatcher, maps.Params] // contains the original cascade as defined in the front matter.
 }
 
-// From page front matter.
-type pageMetaFrontMatter struct {
-	configuredOutputFormats output.Formats // outputs defined in front matter.
-}
-
 func (m *pageMetaParams) init(preserveOriginal bool) {
 	if preserveOriginal {
 		m.paramsOriginal = xmaps.Clone[maps.Params](m.pageConfig.Params)
@@ -531,16 +524,7 @@ params:
 			for i, s := range o {
 				o[i] = strings.ToLower(s)
 			}
-			if len(o) > 0 {
-				// Output formats are explicitly set in front matter, use those.
-				outFormats, err := p.s.conf.OutputFormats.Config.GetByNames(o...)
-				if err != nil {
-					p.s.Log.Errorf("Failed to resolve output formats: %s", err)
-				} else {
-					pm.configuredOutputFormats = outFormats
-					params[loki] = outFormats
-				}
-			}
+			pm.pageConfig.Outputs = o
 		case "draft":
 			draft = new(bool)
 			*draft = cast.ToBool(v)
@@ -672,7 +656,7 @@ params:
 		return err
 	}
 
-	if err := pcfg.Compile("", false, ext, p.s.Log, p.s.conf.MediaTypes.Config); err != nil {
+	if err := pcfg.Compile("", false, ext, p.s.Log, p.s.conf.OutputFormats.Config, p.s.conf.MediaTypes.Config); err != nil {
 		return err
 	}
 
@@ -829,8 +813,8 @@ func (p *pageMeta) newContentConverter(ps *pageState, markup string) (converter.
 
 // The output formats this page will be rendered to.
 func (m *pageMeta) outputFormats() output.Formats {
-	if len(m.configuredOutputFormats) > 0 {
-		return m.configuredOutputFormats
+	if len(m.pageConfig.ConfiguredOutputFormats) > 0 {
+		return m.pageConfig.ConfiguredOutputFormats
 	}
 	return m.s.conf.C.KindOutputFormats[m.Kind()]
 }
diff --git a/hugolib/site.go b/hugolib/site.go
index 728b036d2..0e658086b 100644
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -804,7 +804,7 @@ func (s *Site) initRenderFormats() {
 		Tree: s.pageMap.treePages,
 		Handle: func(key string, n contentNodeI, match doctree.DimensionFlag) (bool, error) {
 			if p, ok := n.(*pageState); ok {
-				for _, f := range p.m.configuredOutputFormats {
+				for _, f := range p.m.pageConfig.ConfiguredOutputFormats {
 					if !formatSet[f.Name] {
 						formats = append(formats, f)
 						formatSet[f.Name] = true
diff --git a/resources/page/pagemeta/page_frontmatter.go b/resources/page/pagemeta/page_frontmatter.go
index 7f98b1b88..eb52e5055 100644
--- a/resources/page/pagemeta/page_frontmatter.go
+++ b/resources/page/pagemeta/page_frontmatter.go
@@ -29,6 +29,7 @@ import (
 	"github.com/gohugoio/hugo/hugofs/files"
 	"github.com/gohugoio/hugo/markup"
 	"github.com/gohugoio/hugo/media"
+	"github.com/gohugoio/hugo/output"
 	"github.com/gohugoio/hugo/resources/kinds"
 	"github.com/gohugoio/hugo/resources/page"
 	"github.com/gohugoio/hugo/resources/resource"
@@ -114,9 +115,10 @@ type PageConfig struct {
 	Content Source
 
 	// Compiled values.
-	CascadeCompiled      *maps.Ordered[page.PageMatcher, maps.Params] `mapstructure:"-" json:"-"`
-	ContentMediaType     media.Type                                   `mapstructure:"-" json:"-"`
-	IsFromContentAdapter bool                                         `mapstructure:"-" json:"-"`
+	CascadeCompiled         *maps.Ordered[page.PageMatcher, maps.Params] `mapstructure:"-" json:"-"`
+	ContentMediaType        media.Type                                   `mapstructure:"-" json:"-"`
+	ConfiguredOutputFormats output.Formats                               `mapstructure:"-" json:"-"`
+	IsFromContentAdapter    bool                                         `mapstructure:"-" json:"-"`
 }
 
 var DefaultPageConfig = PageConfig{
@@ -150,7 +152,7 @@ func (p *PageConfig) Validate(pagesFromData bool) error {
 }
 
 // Compile sets up the page configuration after all fields have been set.
-func (p *PageConfig) Compile(basePath string, pagesFromData bool, ext string, logger loggers.Logger, mediaTypes media.Types) error {
+func (p *PageConfig) Compile(basePath string, pagesFromData bool, ext string, logger loggers.Logger, outputFormats output.Formats, mediaTypes media.Types) error {
 	// In content adapters, we always get relative paths.
 	if basePath != "" {
 		p.Path = path.Join(basePath, p.Path)
@@ -195,6 +197,15 @@ func (p *PageConfig) Compile(basePath string, pagesFromData bool, ext string, lo
 		p.Content.Markup = p.ContentMediaType.SubType
 	}
 
+	if len(p.Outputs) > 0 {
+		outFormats, err := outputFormats.GetByNames(p.Outputs...)
+		if err != nil {
+			return fmt.Errorf("failed to resolve output formats %v: %w", p.Outputs, err)
+		} else {
+			p.ConfiguredOutputFormats = outFormats
+		}
+	}
+
 	if pagesFromData {
 		if p.Kind == "" {
 			p.Kind = kinds.KindPage
@@ -205,6 +216,7 @@ func (p *PageConfig) Compile(basePath string, pagesFromData bool, ext string, lo
 		// but also because people tend to use use the filename to name their resources (with spaces and all),
 		// and this isn't relevant when creating resources from an API where it's easy to add textual meta data.
 		p.Path = paths.NormalizePathStringBasic(p.Path)
+
 	}
 
 	if p.Cascade != nil {


Test patch:
diff --git a/hugolib/pagesfromdata/pagesfromgotmpl_integration_test.go b/hugolib/pagesfromdata/pagesfromgotmpl_integration_test.go
index a31b93999..f6e4221a0 100644
--- a/hugolib/pagesfromdata/pagesfromgotmpl_integration_test.go
+++ b/hugolib/pagesfromdata/pagesfromgotmpl_integration_test.go
@@ -779,3 +779,45 @@ Single.
 
 	b.AssertFileContent("public/tags/index.html", "Terms: mytag: 1|§s")
 }
+
+func TestContentAdapterOutputsIssue13689(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- hugo.toml --
+disableKinds = ['home','rss','section','sitemap','taxonomy','term']
+[outputs]
+page = ['html','json']
+-- layouts/page.html --
+html: {{ .Title }}
+-- layouts/page.json --
+json: {{ .Title }}
+-- content/p1.md --
+---
+title: p1
+---
+-- content/p2.md --
+---
+title: p2
+outputs:
+  - html
+---
+-- content/_content.gotmpl --
+{{ $page := dict "path" "p3" "title" "p3" }}
+{{ $.AddPage $page }}
+
+{{ $page := dict "path" "p4" "title" "p4" "outputs" (slice "html") }}
+{{ $.AddPage $page }}
+`
+
+	b := hugolib.Test(t, files)
+
+	b.AssertFileExists("public/p1/index.html", true)
+	b.AssertFileExists("public/p1/index.json", true)
+	b.AssertFileExists("public/p2/index.html", true)
+	b.AssertFileExists("public/p2/index.json", false)
+	b.AssertFileExists("public/p3/index.html", true)
+	b.AssertFileExists("public/p3/index.json", true)
+	b.AssertFileExists("public/p4/index.html", true)
+	b.AssertFileExists("public/p4/index.json", false) // currently returns true
+}
diff --git a/resources/page/pagemeta/page_frontmatter_test.go b/resources/page/pagemeta/page_frontmatter_test.go
index fe9d3d99c..de2932391 100644
--- a/resources/page/pagemeta/page_frontmatter_test.go
+++ b/resources/page/pagemeta/page_frontmatter_test.go
@@ -22,6 +22,7 @@ import (
 	"github.com/gohugoio/hugo/config"
 	"github.com/gohugoio/hugo/config/testconfig"
 	"github.com/gohugoio/hugo/media"
+	"github.com/gohugoio/hugo/output"
 
 	"github.com/gohugoio/hugo/resources/page/pagemeta"
 
@@ -175,7 +176,7 @@ func TestContentMediaTypeFromMarkup(t *testing.T) {
 	} {
 		var pc pagemeta.PageConfig
 		pc.Content.Markup = test.in
-		c.Assert(pc.Compile("", true, "", logger, media.DefaultTypes), qt.IsNil)
+		c.Assert(pc.Compile("", true, "", logger, output.DefaultFormats, media.DefaultTypes), qt.IsNil)
 		c.Assert(pc.ContentMediaType.Type, qt.Equals, test.expected)
 	}
 }