Repository: gohugoio/hugo (language: go)

Issue:
Build fails with changed mount behavior `assets` directory
Module mounts behavior of the `assets` directory changed with Hugo v0.123.0 and later.

Known inpact: all [Doks](https://github.com/gethyas/doks) and [Hyas](https://github.com/gethyas/hyas) users.

## Replicate

Clone repository

```bash
git clone https://github.com/h-enk/hyas-hugo-v0.123.6.git
```

Install dependencies

```bash
npm install
```

Run development server

```bash
npm run dev
```

## Module mounts (default settings)

`config/_default/module.toml`

```toml
## assets
[[mounts]]
  source = "node_modules/@hyas/core/assets"
  target = "assets"

[[mounts]]
  source = "assets"
  target = "assets"
```

Result: NOK

```bash
execute of template failed: template: partials/head/libsass.html:9:36: executing "partials/head/libsass.html" at <$css.Permalink>: error calling Permalink: operation not supported on directory ""
```

## Module mounts (temporary fix)

`config/_default/module.toml`

```toml
## assets
[[mounts]]
  source = "node_modules/@hyas/core/assets"
  target = "assets"
  excludeFiles = ["scss/app.scss"]

[[mounts]]
  source = "assets"
  target = "assets"
```

Result: OK

## Resources

- [hyas-hugo-v0.122.0](https://github.com/h-enk/hyas-hugo-v0.122.0)
- [hyas-hugo-v0.123.6](https://github.com/h-enk/hyas-hugo-v0.123.6)



Discussion / hints:
@h-enk 

Here's what I did on a virgin Debian system with node v20.11.1:

```text
git clone https://github.com/h-enk/hyas-hugo-v0.123.6.git
cd hyas-hugo-v0.123.6/
npm install
```

Here's what I got:

```text
npm notice 
npm notice New minor version of npm available! 10.2.4 -> 10.5.0
npm notice Changelog: https://github.com/npm/cli/releases/tag/v10.5.0
npm notice Run npm install -g npm@10.5.0 to update!
npm notice 
npm ERR! code 1
npm ERR! path /home/jmooring/temp/hyas-hugo-v0.123.6/node_modules/gethyas
npm ERR! command failed
npm ERR! command sh -c hugo-installer --version otherDependencies.hugo --extended --destination node_modules/.bin/hugo

npm ERR! A complete log of this run can be found in: /home/jmooring/.npm/_logs/2024-03-01T00_57_30_589Z-debug-0.log
```

I tested on a virgin Debian system because I couldn't get it to work on my Ubuntu system either.
Here's a repository that doesn't (try to) install Hugo locally:

- [hyas-hugo-v0.123.6-global](https://github.com/h-enk/hyas-hugo-v0.123.6-global)
Can confirm the issue on Windows
```
$ hugo env
hugo v0.123.6-92684f9a26838a46d1a81e3c250fef5207bcb735+extended windows/amd64 BuildDate=2024-02-28T18:29:40Z VendorInfo=gohugoio
GOOS="windows"
GOARCH="amd64"
GOVERSION="go1.22.0"
github.com/sass/libsass="3.6.5"
github.com/webmproject/libwebp="v1.3.2"
```

Fix patch:
diff --git a/hugofs/rootmapping_fs.go b/hugofs/rootmapping_fs.go
index 336c8b4e71a..a3049866880 100644
--- a/hugofs/rootmapping_fs.go
+++ b/hugofs/rootmapping_fs.go
@@ -475,6 +475,11 @@ func (fs *RootMappingFs) newUnionFile(fis ...FileMetaInfo) (afero.File, error) {
 		return fis[0].Meta().Open()
 	}
 
+	if !fis[0].IsDir() {
+		// Pick the last file mount.
+		return fis[len(fis)-1].Meta().Open()
+	}
+
 	openers := make([]func() (afero.File, error), len(fis))
 	for i := len(fis) - 1; i >= 0; i-- {
 		fi := fis[i]
@@ -647,6 +652,28 @@ func (rfs *RootMappingFs) collectDirEntries(prefix string) ([]iofs.DirEntry, err
 }
 
 func (fs *RootMappingFs) doStat(name string) ([]FileMetaInfo, error) {
+	fis, err := fs.doDoStat(name)
+	if err != nil {
+		return nil, err
+	}
+	// Sanity check. Check that all is either file or directories.
+	var isDir, isFile bool
+	for _, fi := range fis {
+		if fi.IsDir() {
+			isDir = true
+		} else {
+			isFile = true
+		}
+	}
+	if isDir && isFile {
+		// For now.
+		return nil, os.ErrNotExist
+	}
+
+	return fis, nil
+}
+
+func (fs *RootMappingFs) doDoStat(name string) ([]FileMetaInfo, error) {
 	name = fs.cleanName(name)
 	key := filepathSeparator + name
 
@@ -669,7 +696,6 @@ func (fs *RootMappingFs) doStat(name string) ([]FileMetaInfo, error) {
 		var fis []FileMetaInfo
 
 		for _, rm := range roots {
-
 			var fi FileMetaInfo
 			fi, err = fs.statRoot(rm, name)
 			if err == nil {


Test patch:
diff --git a/hugolib/filesystems/basefs_test.go b/hugolib/filesystems/basefs_test.go
index 4fdeba7655a..10b4a4cb296 100644
--- a/hugolib/filesystems/basefs_test.go
+++ b/hugolib/filesystems/basefs_test.go
@@ -509,6 +509,40 @@ l2
 	}
 }
 
+func TestAssetsIssue12175(t *testing.T) {
+	files := `
+-- hugo.toml --
+baseURL = "https://example.com/"
+[module]
+[[module.mounts]]
+source = "node_modules/@foo/core/assets"
+target = "assets"
+[[module.mounts]]
+source = "assets"
+target = "assets"
+-- node_modules/@foo/core/assets/js/app.js --
+JS.
+-- node_modules/@foo/core/assets/scss/app.scss --
+body { color: red; }
+-- assets/scss/app.scss --
+body { color: blue; }
+-- layouts/index.html --
+Home.
+SCSS: {{ with resources.Get "scss/app.scss" }}{{ .RelPermalink }}|{{ .Content }}{{ end }}|
+# Note that the pattern below will match 2 resources, which doesn't make much sense,
+# but is how the current (and also < v0.123.0) merge logic works, and for most practical purposes, it doesn't matter.
+SCSS Match: {{ with resources.Match "**.scss" }}{{ . | len }}|{{ range .}}{{ .RelPermalink }}|{{ end }}{{ end }}|
+
+`
+
+	b := hugolib.Test(t, files)
+
+	b.AssertFileContent("public/index.html", `	
+SCSS: /scss/app.scss|body { color: blue; }|
+SCSS Match: 2|
+`)
+}
+
 func TestStaticComposite(t *testing.T) {
 	files := `
 -- hugo.toml --