Repository: gohugoio/hugo (language: go)

Issue:
`imageConfig` not working with module's images.
Discussion: https://discourse.gohugo.io/t/imageconfig-doesnt-work-with-modules-images-but-fileexists-does/45138.



Discussion / hints:

Fix patch:
diff --git a/tpl/images/images.go b/tpl/images/images.go
index f4d9305708f..82510aaa3b2 100644
--- a/tpl/images/images.go
+++ b/tpl/images/images.go
@@ -20,6 +20,7 @@ import (
 
 	"errors"
 
+	"github.com/bep/overlayfs"
 	"github.com/gohugoio/hugo/resources/images"
 
 	// Importing image codecs for image.DecodeConfig
@@ -31,23 +32,38 @@ import (
 	_ "golang.org/x/image/webp"
 
 	"github.com/gohugoio/hugo/deps"
+	"github.com/spf13/afero"
 	"github.com/spf13/cast"
 )
 
 // New returns a new instance of the images-namespaced template functions.
-func New(deps *deps.Deps) *Namespace {
+func New(d *deps.Deps) *Namespace {
+	var readFileFs afero.Fs
+
+	// The docshelper script does not have or need all the dependencies set up.
+	if d.PathSpec != nil {
+		readFileFs = overlayfs.New(overlayfs.Options{
+			Fss: []afero.Fs{
+				d.PathSpec.BaseFs.Work,
+				d.PathSpec.BaseFs.Content.Fs,
+			},
+		})
+	}
+
 	return &Namespace{
-		Filters: &images.Filters{},
-		cache:   map[string]image.Config{},
-		deps:    deps,
+		readFileFs: readFileFs,
+		Filters:    &images.Filters{},
+		cache:      map[string]image.Config{},
+		deps:       d,
 	}
 }
 
 // Namespace provides template functions for the "images" namespace.
 type Namespace struct {
 	*images.Filters
-	cacheMu sync.RWMutex
-	cache   map[string]image.Config
+	readFileFs afero.Fs
+	cacheMu    sync.RWMutex
+	cache      map[string]image.Config
 
 	deps *deps.Deps
 }
@@ -73,7 +89,7 @@ func (ns *Namespace) Config(path any) (image.Config, error) {
 		return config, nil
 	}
 
-	f, err := ns.deps.Fs.WorkingDirReadOnly.Open(filename)
+	f, err := ns.readFileFs.Open(filename)
 	if err != nil {
 		return image.Config{}, err
 	}


Test patch:
diff --git a/tpl/images/images_test.go b/tpl/images/images_test.go
index 819c58af19d..1b70b7c70a1 100644
--- a/tpl/images/images_test.go
+++ b/tpl/images/images_test.go
@@ -24,8 +24,6 @@ import (
 	qt "github.com/frankban/quicktest"
 	"github.com/gohugoio/hugo/config"
 	"github.com/gohugoio/hugo/config/testconfig"
-	"github.com/gohugoio/hugo/deps"
-	"github.com/gohugoio/hugo/hugofs"
 	"github.com/spf13/afero"
 	"github.com/spf13/cast"
 )
@@ -86,11 +84,10 @@ func TestNSConfig(t *testing.T) {
 	afs := afero.NewMemMapFs()
 	v := config.New()
 	v.Set("workingDir", "/a/b")
-	conf := testconfig.GetTestConfig(afs, v)
-	bcfg := conf.BaseConfig()
-	fs := hugofs.NewFrom(afs, bcfg)
+	d := testconfig.GetTestDeps(afs, v)
+	bcfg := d.Conf
 
-	ns := New(&deps.Deps{Fs: fs, Conf: conf})
+	ns := New(d)
 
 	for _, test := range configTests {
 
@@ -104,7 +101,7 @@ func TestNSConfig(t *testing.T) {
 		// cast path to string for afero.WriteFile
 		sp, err := cast.ToStringE(test.path)
 		c.Assert(err, qt.IsNil)
-		afero.WriteFile(ns.deps.Fs.Source, filepath.Join(bcfg.WorkingDir, sp), test.input, 0755)
+		afero.WriteFile(ns.deps.Fs.Source, filepath.Join(bcfg.WorkingDir(), sp), test.input, 0755)
 
 		result, err := ns.Config(test.path)
 
diff --git a/tpl/images/integration_test.go b/tpl/images/integration_test.go
new file mode 100644
index 00000000000..ad810ad921c
--- /dev/null
+++ b/tpl/images/integration_test.go
@@ -0,0 +1,56 @@
+// Copyright 2023 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package images_test
+
+import (
+	"testing"
+
+	"github.com/gohugoio/hugo/hugolib"
+)
+
+func TestImageConfigFromModule(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- hugo.toml --
+baseURL = 'http://example.com/'
+theme = ["mytheme"]
+-- static/images/pixel1.png --
+iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
+-- themes/mytheme/static/images/pixel2.png --
+iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
+-- layouts/index.html --
+{{ $path := "static/images/pixel1.png" }}
+fileExists OK: {{ fileExists $path }}|
+imageConfig OK: {{ (imageConfig $path).Width }}|
+{{ $path2 := "static/images/pixel2.png" }}
+fileExists2 OK: {{ fileExists $path2 }}|
+imageConfig2 OK: {{ (imageConfig $path2).Width }}|
+
+  `
+
+	b := hugolib.NewIntegrationTestBuilder(
+		hugolib.IntegrationTestConfig{
+			T:           t,
+			TxtarString: files,
+		},
+	).Build()
+
+	b.AssertFileContent("public/index.html", `
+fileExists OK: true|
+imageConfig OK: 1|
+fileExists2 OK: true|
+imageConfig2 OK: 1|
+`)
+}