Repository: gohugoio/hugo (language: go)

Issue:
CSS files not found when vendoring modules in v0.123.x
### What version of Hugo are you using (`hugo version`)?

<pre>
hugo v0.123.3-a75a659f6fc0cb3a52b2b2ba666a81f79a459376+extended darwin/arm64 BuildDate=2024-02-23T17:09:20Z VendorInfo=brew
</pre>

### Does this issue reproduce with the latest release?

Yes


### Context

When upgrading Hugo from v0.122.0 to v0.123.3 I get a CSS transpiler error. Below the full error logs (specific to the Hinode theme):

```
ERROR render of "taxonomy" failed: execute of template failed: template: partials/head/head.html:8:7: executing "head" at <partialCached "head/stylesheet-core.html" .>: error calling partialCached: "[...]/hinode/layouts/partials/head/stylesheet-core.html:3:3": execute of template failed: template: partials/head/stylesheet-core.html:3:3: executing "partials/head/stylesheet-core.html" at <partial "head/stylesheet.html">: error calling partial: "[...]/hinode/layouts/partials/head/stylesheet.html:60:12": execute of template failed: template: partials/head/stylesheet.html:60:12: executing "partials/head/stylesheet.html" at <partial "head/icons.html" (dict "css" $css)>: error calling partial: "[...]/hinode/layouts/partials/head/icons.html:22:54": execute of template failed: template: partials/head/icons.html:22:54: executing "partials/head/icons.html" at <$css.Content>: error calling Content: TOCSS: failed to transform "css/main.css" (text/x-scss): "[...]/hinode/_vendor/github.com/gethinode/mod-bootstrap/assets/scss/bootstrap.scss:7:1": File to import not found or unreadable: modules/bootstrap/mixins/banner.
ERROR render of "404" failed: execute of template failed: template: partials/head/head.html:8:7: executing "head" at <partialCached "head/stylesheet-core.html" .>: error calling partialCached: "[...]/hinode/layouts/partials/head/stylesheet-core.html:3:3": execute of template failed: template: partials/head/stylesheet-core.html:3:3: executing "partials/head/stylesheet-core.html" at <partial "head/stylesheet.html">: error calling partial: "[...]/hinode/layouts/partials/head/stylesheet.html:60:12": execute of template failed: template: partials/head/stylesheet.html:60:12: executing "partials/head/stylesheet.html" at <partial "head/icons.html" (dict "css" $css)>: error calling partial: "[...]/hinode/layouts/partials/head/icons.html:22:54": execute of template failed: template: partials/head/icons.html:22:54: executing "partials/head/icons.html" at <$css.Content>: error calling Content: TOCSS: failed to transform "css/main.css" (text/x-scss): "[...]/hinode/_vendor/github.com/gethinode/mod-bootstrap/assets/scss/bootstrap.scss:7:1": File to import not found or unreadable: modules/bootstrap/mixins/banner.
ERROR render: failed to render pages: render of "home" failed: execute of template failed: template: partials/head/head.html:8:7: executing "head" at <partialCached "head/stylesheet-core.html" .>: error calling partialCached: "[...]/hinode/layouts/partials/head/stylesheet-core.html:3:3": execute of template failed: template: partials/head/stylesheet-core.html:3:3: executing "partials/head/stylesheet-core.html" at <partial "head/stylesheet.html">: error calling partial: "[...]/hinode/layouts/partials/head/stylesheet.html:60:12": execute of template failed: template: partials/head/stylesheet.html:60:12: executing "partials/head/stylesheet.html" at <partial "head/icons.html" (dict "css" $css)>: error calling partial: "[...]/hinode/layouts/partials/head/icons.html:22:54": execute of template failed: template: partials/head/icons.html:22:54: executing "partials/head/icons.html" at <$css.Content>: error calling Content: TOCSS: failed to transform "css/main.css" (text/x-scss): "[...]/hinode/_vendor/github.com/gethinode/mod-bootstrap/assets/scss/bootstrap.scss:7:1": File to import not found or unreadable: modules/bootstrap/mixins/banner.
Built in 140 ms
Error: error building site: TOCSS: failed to transform "css/main.css" (text/x-scss): "[...]/hinode/_vendor/github.com/gethinode/mod-bootstrap/assets/scss/bootstrap.scss:7:1": File to import not found or unreadable: modules/bootstrap/mixins/banner.
```

The error only occurs when running `hugo mod vendor`. 

### To reproduce

```bash
git clone https://github.com/gethinode/hinode.git
cd hinode
hugo mod vendor
hugo server
```

Removing the `_vendor` directory solves the `TOCSS` transpiler error (it doesn't seem to matter if using `libsass` or `dartsass`).

```bash
rm -rf _vendor
hugo server
```

### Rationale

The Hinode theme vendors its modules to add specific CSS files to a safe list, preventing certain style definitions to be purged from the final CSS file. See `config/postcss.config.js` for more details. Purge is enabled by setting `purge = true` in  the`[style]` section of `config/_default/params.toml`.


Discussion / hints:
I just tested this OK:

```
hugo new site my-hinode-site && cd my-hinode-site
hugo mod init example.com/my-hinode-site
echo "[[module.imports]]" >> hugo.toml
echo "path = 'github.com/gethinode/hinode'" >> hugo.toml
hugo mod vendor
hugo server
````

You are right that it fails when running the vendored version directly from the theme, but comparing the vendored directories from v0.122 and v0.123 shows that they are the same, which means that it's not the vendoring that's failing.

Hmm.

OK, I think I see what's happening now.

Fix patch:
diff --git a/hugofs/rootmapping_fs.go b/hugofs/rootmapping_fs.go
index ce4243fbb0e..9a89914beb7 100644
--- a/hugofs/rootmapping_fs.go
+++ b/hugofs/rootmapping_fs.go
@@ -338,12 +338,17 @@ func (c ComponentPath) ComponentPathJoined() string {
 
 type ReverseLookupProvder interface {
 	ReverseLookup(filename string, checkExists bool) ([]ComponentPath, error)
+	ReverseLookupComponent(component, filename string, checkExists bool) ([]ComponentPath, error)
 }
 
 // func (fs *RootMappingFs) ReverseStat(filename string) ([]FileMetaInfo, error)
-func (fs *RootMappingFs) ReverseLookup(in string, checkExists bool) ([]ComponentPath, error) {
-	in = fs.cleanName(in)
-	key := filepathSeparator + in
+func (fs *RootMappingFs) ReverseLookup(filename string, checkExists bool) ([]ComponentPath, error) {
+	return fs.ReverseLookupComponent("", filename, checkExists)
+}
+
+func (fs *RootMappingFs) ReverseLookupComponent(component, filename string, checkExists bool) ([]ComponentPath, error) {
+	filename = fs.cleanName(filename)
+	key := filepathSeparator + filename
 
 	s, roots := fs.getRootsReverse(key)
 
@@ -357,6 +362,9 @@ func (fs *RootMappingFs) ReverseLookup(in string, checkExists bool) ([]Component
 	dir, name := filepath.Split(base)
 
 	for _, first := range roots {
+		if component != "" && first.FromBase != component {
+			continue
+		}
 		if first.Meta.Rename != nil {
 			name = first.Meta.Rename(name, true)
 		}
diff --git a/hugolib/filesystems/basefs.go b/hugolib/filesystems/basefs.go
index 25c58d516b9..5479e22662e 100644
--- a/hugolib/filesystems/basefs.go
+++ b/hugolib/filesystems/basefs.go
@@ -362,7 +362,7 @@ func (d *SourceFilesystem) ReverseLookup(filename string, checkExists bool) ([]h
 	var cps []hugofs.ComponentPath
 	hugofs.WalkFilesystems(d.Fs, func(fs afero.Fs) bool {
 		if rfs, ok := fs.(hugofs.ReverseLookupProvder); ok {
-			if c, err := rfs.ReverseLookup(filename, checkExists); err == nil {
+			if c, err := rfs.ReverseLookupComponent(d.Name, filename, checkExists); err == nil {
 				cps = append(cps, c...)
 			}
 		}
diff --git a/resources/resource_transformers/tocss/dartsass/transform.go b/resources/resource_transformers/tocss/dartsass/transform.go
index a9600f380ad..17f16a68851 100644
--- a/resources/resource_transformers/tocss/dartsass/transform.go
+++ b/resources/resource_transformers/tocss/dartsass/transform.go
@@ -144,7 +144,7 @@ func (t importResolver) CanonicalizeURL(url string) (string, error) {
 	var pathDir string
 	if isURL {
 		var found bool
-		prevDir, found = t.c.sfs.MakePathRelative(filepath.Dir(filePath), false)
+		prevDir, found = t.c.sfs.MakePathRelative(filepath.Dir(filePath), true)
 
 		if !found {
 			// Not a member of this filesystem, let Dart Sass handle it.
diff --git a/resources/resource_transformers/tocss/scss/tocss.go b/resources/resource_transformers/tocss/scss/tocss.go
index a3f2a5289a7..3a46e601691 100644
--- a/resources/resource_transformers/tocss/scss/tocss.go
+++ b/resources/resource_transformers/tocss/scss/tocss.go
@@ -86,7 +86,7 @@ func (t *toCSSTransformation) Transform(ctx *resources.ResourceTransformationCtx
 		if prev == "stdin" {
 			prevDir = baseDir
 		} else {
-			prevDir, _ = t.c.sfs.MakePathRelative(filepath.Dir(prev), false)
+			prevDir, _ = t.c.sfs.MakePathRelative(filepath.Dir(prev), true)
 
 			if prevDir == "" {
 				// Not a member of this filesystem. Let LibSASS handle it.


Test patch:
diff --git a/hugolib/filesystems/basefs_test.go b/hugolib/filesystems/basefs_test.go
index f5b7b6170e5..f50bdb09f02 100644
--- a/hugolib/filesystems/basefs_test.go
+++ b/hugolib/filesystems/basefs_test.go
@@ -478,6 +478,36 @@ Home.
 	_ = stat("blog/b1.md")
 }
 
+func TestReverseLookupShouldOnlyConsiderFilesInCurrentComponent(t *testing.T) {
+	files := `
+-- hugo.toml --
+baseURL = "https://example.com/"
+[module]
+[[module.mounts]]
+source = "files/layouts"
+target = "layouts"
+[[module.mounts]]
+source = "files/layouts/assets"
+target = "assets"
+-- files/layouts/l1.txt --
+l1
+-- files/layouts/assets/l2.txt --
+l2
+`
+	b := hugolib.Test(t, files)
+
+	assetsFs := b.H.Assets
+
+	for _, checkExists := range []bool{false, true} {
+		cps, err := assetsFs.ReverseLookup(filepath.FromSlash("files/layouts/assets/l2.txt"), checkExists)
+		b.Assert(err, qt.IsNil)
+		b.Assert(cps, qt.HasLen, 1)
+		cps, err = assetsFs.ReverseLookup(filepath.FromSlash("files/layouts/l2.txt"), checkExists)
+		b.Assert(err, qt.IsNil)
+		b.Assert(cps, qt.HasLen, 0)
+	}
+}
+
 func TestStaticComposite(t *testing.T) {
 	files := `
 -- hugo.toml --