Repository: gohugoio/hugo (go)

Issue:
Allow cascading of `outputs` front matter field to pages created by content adapters
The ability to set the `outputs` front matter field in pages created by a content adapter was added in https://github.com/gohugoio/hugo/issues/13689, but you cannot cascade values into these fields. 

This is not a case of trying to cascade a value into a field that has already been set.

Test case to follow.

Pull request:
Fix/implement cascade for content adapters
Fixes #13692


Patch:
diff --git a/config/allconfig/alldecoders.go b/config/allconfig/alldecoders.go
index 1cfa1afc4..035349790 100644
--- a/config/allconfig/alldecoders.go
+++ b/config/allconfig/alldecoders.go
@@ -330,7 +330,7 @@ var allDecoderSetups = map[string]decodeWeight{
 		key: "cascade",
 		decode: func(d decodeWeight, p decodeConfig) error {
 			var err error
-			p.c.Cascade, err = page.DecodeCascadeConfig(nil, p.p.Get(d.key))
+			p.c.Cascade, err = page.DecodeCascadeConfig(nil, true, p.p.Get(d.key))
 			return err
 		},
 	},
diff --git a/hugolib/content_map.go b/hugolib/content_map.go
index 454d36df7..596a8f7f9 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.OutputFormats.Config, s.conf.MediaTypes.Config); err != nil {
+					if err := pc.CompileForPagesFromDataPre(pt.GoTmplFi.Meta().PathInfo.Base(), m.s.Log, s.conf.MediaTypes.Config); err != nil {
 						return err
 					}
 
diff --git a/hugolib/content_map_page.go b/hugolib/content_map_page.go
index be834fbb6..9e67fbb1b 100644
--- a/hugolib/content_map_page.go
+++ b/hugolib/content_map_page.go
@@ -1695,7 +1695,9 @@ func (sa *sitePagesAssembler) assembleTermsAndTranslations() error {
 							pathInfo: pi,
 							pageMetaParams: &pageMetaParams{
 								pageConfig: &pagemeta.PageConfig{
-									Kind: kinds.KindTerm,
+									PageConfigEarly: pagemeta.PageConfigEarly{
+										Kind: kinds.KindTerm,
+									},
 								},
 							},
 						}
@@ -1956,7 +1958,9 @@ func (sa *sitePagesAssembler) addStandalonePages() error {
 			pathInfo: s.Conf.PathParser().Parse(files.ComponentFolderContent, key+f.MediaType.FirstSuffix.FullSuffix),
 			pageMetaParams: &pageMetaParams{
 				pageConfig: &pagemeta.PageConfig{
-					Kind: kind,
+					PageConfigEarly: pagemeta.PageConfigEarly{
+						Kind: kind,
+					},
 				},
 			},
 			standaloneOutputFormat: f,
@@ -2082,7 +2086,9 @@ func (sa *sitePagesAssembler) addMissingRootSections() error {
 			pathInfo: p,
 			pageMetaParams: &pageMetaParams{
 				pageConfig: &pagemeta.PageConfig{
-					Kind: kinds.KindHome,
+					PageConfigEarly: pagemeta.PageConfigEarly{
+						Kind: kinds.KindHome,
+					},
 				},
 			},
 		}
@@ -2115,7 +2121,9 @@ func (sa *sitePagesAssembler) addMissingTaxonomies() error {
 				pathInfo: sa.Conf.PathParser().Parse(files.ComponentFolderContent, key+"/_index.md"),
 				pageMetaParams: &pageMetaParams{
 					pageConfig: &pagemeta.PageConfig{
-						Kind: kinds.KindTaxonomy,
+						PageConfigEarly: pagemeta.PageConfigEarly{
+							Kind: kinds.KindTaxonomy,
+						},
 					},
 				},
 				singular: viewName.singular,
diff --git a/hugolib/page__meta.go b/hugolib/page__meta.go
index 94dbbab3e..e8bce20d1 100644
--- a/hugolib/page__meta.go
+++ b/hugolib/page__meta.go
@@ -72,12 +72,7 @@ type pageMeta struct {
 // Prepare for a rebuild of the data passed in from front matter.
 func (m *pageMeta) setMetaPostPrepareRebuild() {
 	params := xmaps.Clone(m.paramsOriginal)
-	m.pageMetaParams.pageConfig = &pagemeta.PageConfig{
-		Kind:   m.pageConfig.Kind,
-		Lang:   m.pageConfig.Lang,
-		Path:   m.pageConfig.Path,
-		Params: params,
-	}
+	m.pageMetaParams.pageConfig = pagemeta.ClonePageConfigForRebuild(m.pageMetaParams.pageConfig, params)
 }
 
 type pageMetaParams struct {
@@ -94,7 +89,11 @@ type pageMetaParams struct {
 
 func (m *pageMetaParams) init(preserveOriginal bool) {
 	if preserveOriginal {
-		m.paramsOriginal = xmaps.Clone[maps.Params](m.pageConfig.Params)
+		if m.pageConfig.IsFromContentAdapter {
+			m.paramsOriginal = xmaps.Clone(m.pageConfig.ContentAdapterData)
+		} else {
+			m.paramsOriginal = xmaps.Clone(m.pageConfig.Params)
+		}
 		m.cascadeOriginal = m.pageConfig.CascadeCompiled.Clone()
 	}
 }
@@ -254,7 +253,7 @@ func (p *pageMeta) setMetaPre(pi *contentParseInfo, logger loggers.Logger, conf
 		// Check for any cascade define on itself.
 		if cv, found := frontmatter["cascade"]; found {
 			var err error
-			cascade, err := page.DecodeCascade(logger, cv)
+			cascade, err := page.DecodeCascade(logger, true, cv)
 			if err != nil {
 				return err
 			}
@@ -341,18 +340,29 @@ func (ps *pageState) setMetaPost(cascade *maps.Ordered[page.PageMatcher, maps.Pa
 	}
 
 	// Cascade is also applied to itself.
+	var err error
 	cascade.Range(func(k page.PageMatcher, v maps.Params) bool {
 		if !k.Matches(ps) {
 			return true
 		}
 		for kk, vv := range v {
-			if _, found := ps.m.pageConfig.Params[kk]; !found {
-				ps.m.pageConfig.Params[kk] = vv
+			if ps.m.pageConfig.IsFromContentAdapter {
+				if _, found := ps.m.pageConfig.ContentAdapterData[kk]; !found {
+					ps.m.pageConfig.ContentAdapterData[kk] = vv
+				}
+			} else {
+				if _, found := ps.m.pageConfig.Params[kk]; !found {
+					ps.m.pageConfig.Params[kk] = vv
+				}
 			}
 		}
 		return true
 	})
 
+	if err != nil {
+		return err
+	}
+
 	if err := ps.setMetaPostParams(); err != nil {
 		return err
 	}
@@ -398,6 +408,12 @@ func (p *pageState) setMetaPostParams() error {
 		PathOrTitle:   p.pathOrTitle(),
 	}
 
+	if isContentAdapter {
+		if err := pm.pageConfig.Compile(ext, p.s.Log, p.s.conf.OutputFormats.Config, p.s.conf.MediaTypes.Config); err != nil {
+			return err
+		}
+	}
+
 	// Handle the date separately
 	// TODO(bep) we need to "do more" in this area so this can be split up and
 	// more easily tested without the Page, but the coupling is strong.
@@ -656,7 +672,7 @@ params:
 		return err
 	}
 
-	if err := pcfg.Compile("", false, ext, p.s.Log, p.s.conf.OutputFormats.Config, p.s.conf.MediaTypes.Config); err != nil {
+	if err := pcfg.Compile(ext, p.s.Log, p.s.conf.OutputFormats.Config, p.s.conf.MediaTypes.Config); err != nil {
 		return err
 	}
 
diff --git a/hugolib/pagesfromdata/pagesfromgotmpl.go b/hugolib/pagesfromdata/pagesfromgotmpl.go
index a704b39ee..72909a40b 100644
--- a/hugolib/pagesfromdata/pagesfromgotmpl.go
+++ b/hugolib/pagesfromdata/pagesfromgotmpl.go
@@ -91,17 +91,20 @@ func (p *pagesFromDataTemplateContext) AddPage(v any) (string, error) {
 
 	pd := pagemeta.DefaultPageConfig
 	pd.IsFromContentAdapter = true
+	pd.ContentAdapterData = m
 
-	if err := mapstructure.WeakDecode(m, &pd); err != nil {
-		return "", fmt.Errorf("failed to decode page map: %w", err)
+	// The rest will be handled after the cascade is calculated and applied.
+	if err := mapstructure.WeakDecode(pd.ContentAdapterData, &pd.PageConfigEarly); err != nil {
+		err = fmt.Errorf("failed to decode page map: %w", err)
+		return "", err
 	}
 
-	p.p.buildState.NumPagesAdded++
-
 	if err := pd.Validate(true); err != nil {
 		return "", err
 	}
 
+	p.p.buildState.NumPagesAdded++
+
 	return "", p.p.HandlePage(p.p, &pd)
 }
 
diff --git a/resources/page/page_matcher.go b/resources/page/page_matcher.go
index 1e98b0836..27a7c7e9e 100644
--- a/resources/page/page_matcher.go
+++ b/resources/page/page_matcher.go
@@ -16,6 +16,7 @@ package page
 import (
 	"fmt"
 	"path/filepath"
+	"slices"
 	"strings"
 
 	"github.com/gohugoio/hugo/common/loggers"
@@ -24,7 +25,6 @@ import (
 	"github.com/gohugoio/hugo/hugofs/glob"
 	"github.com/gohugoio/hugo/resources/kinds"
 	"github.com/mitchellh/mapstructure"
-	"slices"
 )
 
 // A PageMatcher can be used to match a Page with Glob patterns.
@@ -105,7 +105,7 @@ func CheckCascadePattern(logger loggers.Logger, m PageMatcher) {
 	}
 }
 
-func DecodeCascadeConfig(logger loggers.Logger, in any) (*config.ConfigNamespace[[]PageMatcherParamsConfig, *maps.Ordered[PageMatcher, maps.Params]], error) {
+func DecodeCascadeConfig(logger loggers.Logger, handleLegacyFormat bool, in any) (*config.ConfigNamespace[[]PageMatcherParamsConfig, *maps.Ordered[PageMatcher, maps.Params]], error) {
 	buildConfig := func(in any) (*maps.Ordered[PageMatcher, maps.Params], any, error) {
 		cascade := maps.NewOrdered[PageMatcher, maps.Params]()
 		if in == nil {
@@ -120,7 +120,15 @@ func DecodeCascadeConfig(logger loggers.Logger, in any) (*config.ConfigNamespace
 
 		for _, m := range ms {
 			m = maps.CleanConfigStringMap(m)
-			c, err := mapToPageMatcherParamsConfig(m)
+			var (
+				c   PageMatcherParamsConfig
+				err error
+			)
+			if handleLegacyFormat {
+				c, err = mapToPageMatcherParamsConfigLegacy(m)
+			} else {
+				c, err = mapToPageMatcherParamsConfig(m)
+			}
 			if err != nil {
 				return nil, nil, err
 			}
@@ -155,8 +163,8 @@ func DecodeCascadeConfig(logger loggers.Logger, in any) (*config.ConfigNamespace
 }
 
 // DecodeCascade decodes in which could be either a map or a slice of maps.
-func DecodeCascade(logger loggers.Logger, in any) (*maps.Ordered[PageMatcher, maps.Params], error) {
-	conf, err := DecodeCascadeConfig(logger, in)
+func DecodeCascade(logger loggers.Logger, handleLegacyFormat bool, in any) (*maps.Ordered[PageMatcher, maps.Params], error) {
+	conf, err := DecodeCascadeConfig(logger, handleLegacyFormat, in)
 	if err != nil {
 		return nil, err
 	}
@@ -164,6 +172,26 @@ func DecodeCascade(logger loggers.Logger, in any) (*maps.Ordered[PageMatcher, ma
 }
 
 func mapToPageMatcherParamsConfig(m map[string]any) (PageMatcherParamsConfig, error) {
+	var pcfg PageMatcherParamsConfig
+	for k, v := range m {
+		switch strings.ToLower(k) {
+		case "_target", "target":
+			var target PageMatcher
+			if err := decodePageMatcher(v, &target); err != nil {
+				return pcfg, err
+			}
+			pcfg.Target = target
+		default:
+			if pcfg.Params == nil {
+				pcfg.Params = make(maps.Params)
+			}
+			pcfg.Params[k] = v
+		}
+	}
+	return pcfg, pcfg.init()
+}
+
+func mapToPageMatcherParamsConfigLegacy(m map[string]any) (PageMatcherParamsConfig, error) {
 	var pcfg PageMatcherParamsConfig
 	for k, v := range m {
 		switch strings.ToLower(k) {
@@ -190,7 +218,6 @@ func mapToPageMatcherParamsConfig(m map[string]any) (PageMatcherParamsConfig, er
 			}
 			pcfg.Target = target
 		default:
-			// Legacy config.
 			if pcfg.Params == nil {
 				pcfg.Params = make(maps.Params)
 			}
diff --git a/resources/page/pagemeta/page_frontmatter.go b/resources/page/pagemeta/page_frontmatter.go
index eb52e5055..7dea7ca6b 100644
--- a/resources/page/pagemeta/page_frontmatter.go
+++ b/resources/page/pagemeta/page_frontmatter.go
@@ -33,6 +33,7 @@ import (
 	"github.com/gohugoio/hugo/resources/kinds"
 	"github.com/gohugoio/hugo/resources/page"
 	"github.com/gohugoio/hugo/resources/resource"
+	"github.com/mitchellh/mapstructure"
 
 	"github.com/gohugoio/hugo/helpers"
 
@@ -75,35 +76,43 @@ func (d Dates) IsAllDatesZero() bool {
 	return d.Date.IsZero() && d.Lastmod.IsZero() && d.PublishDate.IsZero() && d.ExpiryDate.IsZero()
 }
 
+// Page config that needs to be set early. These cannot be modified by cascade.
+type PageConfigEarly struct {
+	Kind    string // The kind of page, e.g. "page", "section", "home" etc. This is usually derived from the content path.
+	Path    string // The canonical path to the page, e.g. /sect/mypage. Note: Leading slash, no trailing slash, no extensions or language identifiers.
+	Lang    string // The language code for this page. This is usually derived from the module mount or filename.
+	Cascade []map[string]any
+
+	// Content holds the content for this page.
+	Content Source
+}
+
 // PageConfig configures a Page, typically from front matter.
 // Note that all the top level fields are reserved Hugo keywords.
 // Any custom configuration needs to be set in the Params map.
 type PageConfig struct {
 	Dates Dates `json:"-"` // Dates holds the four core dates for this page.
 	DatesStrings
-	Title          string   // The title of the page.
-	LinkTitle      string   // The link title of the page.
-	Type           string   // The content type of the page.
-	Layout         string   // The layout to use for to render this page.
-	Weight         int      // The weight of the page, used in sorting if set to a non-zero value.
-	Kind           string   // The kind of page, e.g. "page", "section", "home" etc. This is usually derived from the content path.
-	Path           string   // The canonical path to the page, e.g. /sect/mypage. Note: Leading slash, no trailing slash, no extensions or language identifiers.
-	Lang           string   // The language code for this page. This is usually derived from the module mount or filename.
-	URL            string   // The URL to the rendered page, e.g. /sect/mypage.html.
-	Slug           string   // The slug for this page.
-	Description    string   // The description for this page.
-	Summary        string   // The summary for this page.
-	Draft          bool     // Whether or not the content is a draft.
-	Headless       bool     `json:"-"` // Whether or not the page should be rendered.
-	IsCJKLanguage  bool     // Whether or not the content is in a CJK language.
-	TranslationKey string   // The translation key for this page.
-	Keywords       []string // The keywords for this page.
-	Aliases        []string // The aliases for this page.
-	Outputs        []string // The output formats to render this page in. If not set, the site's configured output formats for this page kind will be used.
+	PageConfigEarly `mapstructure:",squash"`
+	Title           string   // The title of the page.
+	LinkTitle       string   // The link title of the page.
+	Type            string   // The content type of the page.
+	Layout          string   // The layout to use for to render this page.
+	Weight          int      // The weight of the page, used in sorting if set to a non-zero value.
+	URL             string   // The URL to the rendered page, e.g. /sect/mypage.html.
+	Slug            string   // The slug for this page.
+	Description     string   // The description for this page.
+	Summary         string   // The summary for this page.
+	Draft           bool     // Whether or not the content is a draft.
+	Headless        bool     `json:"-"` // Whether or not the page should be rendered.
+	IsCJKLanguage   bool     // Whether or not the content is in a CJK language.
+	TranslationKey  string   // The translation key for this page.
+	Keywords        []string // The keywords for this page.
+	Aliases         []string // The aliases for this page.
+	Outputs         []string // The output formats to render this page in. If not set, the site's configured output formats for this page kind will be used.
 
 	FrontMatterOnlyValues `mapstructure:"-" json:"-"`
 
-	Cascade []map[string]any
 	Sitemap config.SitemapConfig
 	Build   BuildConfig
 	Menus   any // Can be a string, []string or map[string]any.
@@ -111,8 +120,9 @@ type PageConfig struct {
 	// User defined params.
 	Params maps.Params
 
-	// Content holds the content for this page.
-	Content Source
+	// The raw data from the content adapter.
+	// TODO(bep) clean up the ContentAdapterData vs Params.
+	ContentAdapterData map[string]any `mapstructure:"-" json:"-"`
 
 	// Compiled values.
 	CascadeCompiled         *maps.Ordered[page.PageMatcher, maps.Params] `mapstructure:"-" json:"-"`
@@ -121,6 +131,20 @@ type PageConfig struct {
 	IsFromContentAdapter    bool                                         `mapstructure:"-" json:"-"`
 }
 
+func ClonePageConfigForRebuild(p *PageConfig, params map[string]any) *PageConfig {
+	pp := &PageConfig{
+		PageConfigEarly:      p.PageConfigEarly,
+		IsFromContentAdapter: p.IsFromContentAdapter,
+	}
+	if pp.IsFromContentAdapter {
+		pp.ContentAdapterData = params
+	} else {
+		pp.Params = params
+	}
+
+	return pp
+}
+
 var DefaultPageConfig = PageConfig{
 	Build: DefaultBuildConfig,
 }
@@ -151,8 +175,7 @@ func (p *PageConfig) Validate(pagesFromData bool) error {
 	return nil
 }
 
-// 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, outputFormats output.Formats, mediaTypes media.Types) error {
+func (p *PageConfig) CompileForPagesFromDataPre(basePath string, logger loggers.Logger, mediaTypes media.Types) error {
 	// In content adapters, we always get relative paths.
 	if basePath != "" {
 		p.Path = path.Join(basePath, p.Path)
@@ -160,12 +183,32 @@ func (p *PageConfig) Compile(basePath string, pagesFromData bool, ext string, lo
 
 	if p.Params == nil {
 		p.Params = make(maps.Params)
-	} else if pagesFromData {
-		p.Params = maps.PrepareParamsClone(p.Params)
 	} else {
-		maps.PrepareParams(p.Params)
+		p.Params = maps.PrepareParamsClone(p.Params)
 	}
 
+	if p.Kind == "" {
+		p.Kind = kinds.KindPage
+	}
+
+	if p.Cascade != nil {
+		cascade, err := page.DecodeCascade(logger, false, p.Cascade)
+		if err != nil {
+			return fmt.Errorf("failed to decode cascade: %w", err)
+		}
+		p.CascadeCompiled = cascade
+	}
+
+	// Note that NormalizePathStringBasic will make sure that we don't preserve the unnormalized path.
+	// We do that when we create pages from the file system; mostly for backward compatibility,
+	// 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)
+
+	return p.compilePrePost("", mediaTypes)
+}
+
+func (p *PageConfig) compilePrePost(ext string, mediaTypes media.Types) error {
 	if p.Content.Markup == "" && p.Content.MediaType == "" {
 		if ext == "" {
 			ext = "md"
@@ -196,35 +239,37 @@ func (p *PageConfig) Compile(basePath string, pagesFromData bool, ext string, lo
 	if p.Content.Markup == "" {
 		p.Content.Markup = p.ContentMediaType.SubType
 	}
+	return nil
+}
 
-	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
+// Compile sets up the page configuration after all fields have been set.
+func (p *PageConfig) Compile(ext string, logger loggers.Logger, outputFormats output.Formats, mediaTypes media.Types) error {
+	if p.IsFromContentAdapter {
+		if err := mapstructure.WeakDecode(p.ContentAdapterData, p); err != nil {
+			err = fmt.Errorf("failed to decode page map: %w", err)
+			return err
 		}
+		// Not needed anymore.
+		p.ContentAdapterData = nil
 	}
 
-	if pagesFromData {
-		if p.Kind == "" {
-			p.Kind = kinds.KindPage
-		}
-
-		// Note that NormalizePathStringBasic will make sure that we don't preserve the unnormalized path.
-		// We do that when we create pages from the file system; mostly for backward compatibility,
-		// 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.Params == nil {
+		p.Params = make(maps.Params)
+	} else {
+		maps.PrepareParams(p.Params)
+	}
 
+	if err := p.compilePrePost(ext, mediaTypes); err != nil {
+		return err
 	}
 
-	if p.Cascade != nil {
-		cascade, err := page.DecodeCascade(logger, p.Cascade)
+	if len(p.Outputs) > 0 {
+		outFormats, err := outputFormats.GetByNames(p.Outputs...)
 		if err != nil {
-			return fmt.Errorf("failed to decode cascade: %w", err)
+			return fmt.Errorf("failed to resolve output formats %v: %w", p.Outputs, err)
+		} else {
+			p.ConfiguredOutputFormats = outFormats
 		}
-		p.CascadeCompiled = cascade
 	}
 
 	return nil


Test patch:
diff --git a/hugolib/pagesfromdata/pagesfromgotmpl_integration_test.go b/hugolib/pagesfromdata/pagesfromgotmpl_integration_test.go
index f6e4221a0..db06fb4a4 100644
--- a/hugolib/pagesfromdata/pagesfromgotmpl_integration_test.go
+++ b/hugolib/pagesfromdata/pagesfromgotmpl_integration_test.go
@@ -821,3 +821,97 @@ outputs:
 	b.AssertFileExists("public/p4/index.html", true)
 	b.AssertFileExists("public/p4/index.json", false) // currently returns true
 }
+
+func TestContentAdapterOutputsIssue13692(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- hugo.toml --
+disableKinds = ['page','home','sitemap','taxonomy','term']
+[[cascade]]
+outputs = ['html','json']
+[cascade.target]
+path = '{/s2,/s4}'
+-- layouts/section.html --
+html: {{ .Title }}
+-- layouts/section.json --
+json: {{ .Title }}
+-- content/s1/_index.md --
+---
+title: s1
+---
+-- content/s2/_index.md --
+---
+title: s2
+---
+-- content/_content.gotmpl --
+{{ $page := dict "path" "s3" "title" "s3" "kind" "section" }}
+{{ $.AddPage $page }}
+
+{{ $page := dict "path" "s4" "title" "s4" "kind" "section" }}
+{{ $.AddPage $page }}
+
+{{ $page := dict "path" "s5" "title" "s5" "kind" "section" "outputs" (slice "html") }}
+ {{ $.AddPage $page }}
+`
+
+	b := hugolib.Test(t, files)
+
+	b.AssertFileExists("public/s1/index.html", true)
+	b.AssertFileExists("public/s1/index.json", false)
+	b.AssertFileExists("public/s1/index.xml", true)
+
+	b.AssertFileExists("public/s2/index.html", true)
+	b.AssertFileExists("public/s2/index.json", true)
+	b.AssertFileExists("public/s2/index.xml", false)
+
+	b.AssertFileExists("public/s3/index.html", true)
+	b.AssertFileExists("public/s3/index.json", false)
+	b.AssertFileExists("public/s3/index.xml", true)
+
+	b.AssertFileExists("public/s4/index.html", true)
+	b.AssertFileExists("public/s4/index.json", true)
+	b.AssertFileExists("public/s4/index.xml", false)
+
+	b.AssertFileExists("public/s5/index.html", true)
+	b.AssertFileExists("public/s5/index.json", false)
+	b.AssertFileExists("public/s5/index.xml", false)
+}
+
+func TestContentAdapterCascadeBasic(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- hugo.toml --
+disableLiveReload = true
+-- content/_index.md --
+---
+cascade:
+  - title: foo
+    target:
+      path: "**"
+---
+-- layouts/all.html --
+Title: {{ .Title }}|Content: {{ .Content }}|
+-- content/_content.gotmpl --
+{{ $content := dict
+  "mediaType" "text/markdown"
+  "value" "The _Hunchback of Notre Dame_ was written by Victor Hugo."
+}}
+
+{{ $page := dict "path" "s1"  "kind" "page" }}
+{{ $.AddPage $page }}
+ {{ $page := dict "path" "s2"  "kind" "page" "title" "bar" "content" $content }}
+{{ $.AddPage $page }}
+
+`
+
+	b := hugolib.TestRunning(t, files)
+
+	b.AssertFileContent("public/s1/index.html", "Title: foo|")
+	b.AssertFileContent("public/s2/index.html", "Title: bar|", "Content: <p>The <em>Hunchback of Notre Dame</em> was written by Victor Hugo.</p>")
+
+	b.EditFileReplaceAll("content/_index.md", "foo", "baz").Build()
+
+	b.AssertFileContent("public/s1/index.html", "Title: baz|")
+}
diff --git a/resources/page/page_matcher_test.go b/resources/page/page_matcher_test.go
index bc072ce15..2fe6ccc89 100644
--- a/resources/page/page_matcher_test.go
+++ b/resources/page/page_matcher_test.go
@@ -84,7 +84,7 @@ func TestPageMatcher(t *testing.T) {
 
 	c.Run("mapToPageMatcherParamsConfig", func(c *qt.C) {
 		fn := func(m map[string]any) PageMatcherParamsConfig {
-			v, err := mapToPageMatcherParamsConfig(m)
+			v, err := mapToPageMatcherParamsConfigLegacy(m)
 			c.Assert(err, qt.IsNil)
 			return v
 		}
@@ -129,7 +129,7 @@ func TestDecodeCascadeConfig(t *testing.T) {
 		},
 	}
 
-	got, err := DecodeCascadeConfig(loggers.NewDefault(), in)
+	got, err := DecodeCascadeConfig(loggers.NewDefault(), true, in)
 
 	c.Assert(err, qt.IsNil)
 	c.Assert(got, qt.IsNotNil)
@@ -143,7 +143,7 @@ func TestDecodeCascadeConfig(t *testing.T) {
 		{Params: maps.Params{"b": string("bv")}, Target: PageMatcher{Kind: "page"}},
 	})
 
-	got, err = DecodeCascadeConfig(loggers.NewDefault(), nil)
+	got, err = DecodeCascadeConfig(loggers.NewDefault(), true, nil)
 	c.Assert(err, qt.IsNil)
 	c.Assert(got, qt.IsNotNil)
 }
diff --git a/resources/page/pagemeta/page_frontmatter_test.go b/resources/page/pagemeta/page_frontmatter_test.go
index de2932391..8d50f9b57 100644
--- a/resources/page/pagemeta/page_frontmatter_test.go
+++ b/resources/page/pagemeta/page_frontmatter_test.go
@@ -176,7 +176,7 @@ func TestContentMediaTypeFromMarkup(t *testing.T) {
 	} {
 		var pc pagemeta.PageConfig
 		pc.Content.Markup = test.in
-		c.Assert(pc.Compile("", true, "", logger, output.DefaultFormats, media.DefaultTypes), qt.IsNil)
+		c.Assert(pc.Compile("", logger, output.DefaultFormats, media.DefaultTypes), qt.IsNil)
 		c.Assert(pc.ContentMediaType.Type, qt.Equals, test.expected)
 	}
 }
diff --git a/tpl/tplimpl/shortcodes_integration_test.go b/tpl/tplimpl/shortcodes_integration_test.go
index 838dc16d7..665760dec 100644
--- a/tpl/tplimpl/shortcodes_integration_test.go
+++ b/tpl/tplimpl/shortcodes_integration_test.go
@@ -460,6 +460,7 @@ title: p1 (de)
 }
 
 func TestVimeoShortcode(t *testing.T) {
+	t.Skip("Fix me: Upstream API changes")
 	t.Parallel()
 
 	files := `