mirror of
https://github.com/gohugoio/hugo.git
synced 2026-08-24 15:28:54 +00:00
hugofs: Make node_modules a "special case" mount
For this and similar mounts in a theme: ```toml [[module.mounts]] source = 'node_modules/bootstrap' target = 'assets/vendor/bootstrap' ``` We first check the theme itself, then the project root. For backwards compatibility, we also make any `../../node_modules/...` `source` paths into `node_modules/...` paths when defined in themes/modules. Fixes #14089
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
package hugofs_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
@@ -44,3 +45,42 @@ All.
|
||||
b.Assert(err, qt.IsNotNil)
|
||||
b.Assert(err.Error(), qt.Contains, "mount source must be a local path for modules/themes")
|
||||
}
|
||||
|
||||
// Issue 14089.
|
||||
func TestMountNodeMoudulesFromTheme(t *testing.T) {
|
||||
filesTemplate := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ["taxonomy", "term", "rss"]
|
||||
theme = "mytheme"
|
||||
-- node_modules/bootstrap/foo.txt --
|
||||
foo project.
|
||||
-- layouts/all.html --
|
||||
{{ $foo := resources.Get "vendor/bootstrap/foo.txt" }}
|
||||
Foo: {{ with $foo }}{{ .Content }}{{ else }}Fail{{ end }}
|
||||
-- themes/mytheme/hugo.toml --
|
||||
[[module.mounts]]
|
||||
source = 'NODE_MODULES_SOURCE' # tries first in theme, then in project root
|
||||
target = 'assets/vendor/bootstrap'
|
||||
|
||||
`
|
||||
runFiles := func(files string) *hugolib.IntegrationTestBuilder {
|
||||
return hugolib.Test(t, files, hugolib.TestOptOsFs())
|
||||
}
|
||||
files := strings.ReplaceAll(filesTemplate, "NODE_MODULES_SOURCE", "node_modules/bootstrap")
|
||||
b := runFiles(files)
|
||||
b.AssertFileContent("public/index.html", "Foo: foo project.")
|
||||
|
||||
// This is for backwards compatibility. ../../node_modules/bootstrap works exactly the same as node_modules/bootstrap.
|
||||
files = strings.ReplaceAll(filesTemplate, "NODE_MODULES_SOURCE", "../../node_modules/bootstrap")
|
||||
b = runFiles(files)
|
||||
b.AssertFileContent("public/index.html", "Foo: foo project.")
|
||||
|
||||
files = strings.ReplaceAll(filesTemplate, "NODE_MODULES_SOURCE", "node_modules/bootstrap")
|
||||
files += `
|
||||
-- themes/mytheme/node_modules/bootstrap/foo.txt --
|
||||
foo theme.
|
||||
`
|
||||
|
||||
b = runFiles(files)
|
||||
b.AssertFileContent("public/index.html", "Foo: foo theme.")
|
||||
}
|
||||
|
||||
+36
-3
@@ -695,6 +695,19 @@ func (c *collector) mountCommonJSConfig(owner *moduleAdapter, mounts []Mount) ([
|
||||
return mounts, nil
|
||||
}
|
||||
|
||||
func (c *collector) nodeModulesRoot(s string) string {
|
||||
s = filepath.ToSlash(s)
|
||||
if strings.HasPrefix(s, "node_modules/") {
|
||||
return s
|
||||
}
|
||||
if strings.HasPrefix(s, "../../node_modules/") {
|
||||
// See #14083. This was a common construct to mount node_modules from the project root.
|
||||
// This started failing in v0.152.0 when we tightened the validation.
|
||||
return strings.TrimPrefix(s, "../../")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *collector) normalizeMounts(owner *moduleAdapter, mounts []Mount) ([]Mount, error) {
|
||||
var out []Mount
|
||||
dir := owner.Dir()
|
||||
@@ -706,6 +719,17 @@ func (c *collector) normalizeMounts(owner *moduleAdapter, mounts []Mount) ([]Mou
|
||||
return nil, errors.New(errMsg + ": both source and target must be set")
|
||||
}
|
||||
|
||||
// Special case for node_modules imports in themes/modules.
|
||||
// See #14089.
|
||||
var isModuleNodeModulesImport bool
|
||||
if !owner.projectMod {
|
||||
nodeModulesImportSource := c.nodeModulesRoot(mnt.Source)
|
||||
if nodeModulesImportSource != "" {
|
||||
isModuleNodeModulesImport = true
|
||||
mnt.Source = nodeModulesImportSource
|
||||
}
|
||||
}
|
||||
|
||||
mnt.Source = filepath.Clean(mnt.Source)
|
||||
mnt.Target = filepath.Clean(mnt.Target)
|
||||
var sourceDir string
|
||||
@@ -741,9 +765,18 @@ func (c *collector) normalizeMounts(owner *moduleAdapter, mounts []Mount) ([]Mou
|
||||
}
|
||||
f.Close()
|
||||
} else {
|
||||
// TODO(bep) commenting out for now, as this will create to much noise.
|
||||
// c.logger.Warnf("module %q: mount source %q does not exist", owner.Path(), sourceDir)
|
||||
continue
|
||||
if isModuleNodeModulesImport {
|
||||
// A module imported a path inside node_modules, but it didn't exist.
|
||||
// Make this a special case and also try relative to the project root.
|
||||
sourceDir = filepath.Join(c.ccfg.WorkingDir, mnt.Source)
|
||||
_, err := c.fs.Stat(sourceDir)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
mnt.Source = sourceDir
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user