/
githubmirror
/
hugo
Обзор
Документация
Войти
/
githubmirror
/
hugo
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
modules/config.go
517 строк
15 KB
Bjørn Erik Pedersen
npm: Use workspaces to simplify `hugo mod npm pack`
23 мар 2026, 18:00
Не верифицирован
23 мар 2026, 18:00
d88a29e
Код
Авторство
О чём код?
// Copyright 2019 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 modules import ( "fmt" "os" "path/filepath" "slices" "strings" "github.com/bep/logg" "github.com/gohugoio/hugo/common/hstrings" "github.com/gohugoio/hugo/common/hugo" "github.com/gohugoio/hugo/common/types" "github.com/gohugoio/hugo/common/version" "github.com/gohugoio/hugo/hugofs/files" hglob "github.com/gohugoio/hugo/hugofs/hglob" "github.com/gohugoio/hugo/hugolib/sitesmatrix" "github.com/gohugoio/hugo/langs" "github.com/gohugoio/hugo/config" "github.com/mitchellh/mapstructure" ) const WorkspaceDisabled = "off" var DefaultModuleConfig = Config{ // Default to direct, which means "git clone" and similar. We // will investigate proxy settings in more depth later. // See https://github.com/golang/go/issues/26334 Proxy: "direct", // Comma separated glob list matching paths that should not use the // proxy configured above. NoProxy: "none", // Comma separated glob list matching paths that should be // treated as private. Private: "*.*", // Default is no workspace resolution. Workspace: WorkspaceDisabled, // A list of replacement directives mapping a module path to a directory // or a theme component in the themes folder. // Note that this will turn the component into a traditional theme component // that does not partake in vendoring etc. // The syntax is the similar to the replacement directives used in go.mod, e.g: // github.com/mod1 -> ../mod1,github.com/mod2 -> ../mod2 Replacements: nil, } // ApplyProjectConfigDefaults applies default/missing module configuration for // the main project. func ApplyProjectConfigDefaults(logger logg.Logger, mod Module, cfgs ...config.AllProvider) error { moda := mod.(*moduleAdapter) // To bridge between old and new configuration format we need // a way to make sure all of the core components are configured on // the basic level. componentsConfigured := make(map[string]bool) for _, mnt := range moda.mounts { if !strings.HasPrefix(mnt.Target, files.JsConfigFolderMountPrefix) { componentsConfigured[mnt.Component()] = true } } var mounts []Mount for _, component := range []string{ files.ComponentFolderContent, files.ComponentFolderData, files.ComponentFolderLayouts, files.ComponentFolderI18n, files.ComponentFolderArchetypes, files.ComponentFolderAssets, files.ComponentFolderStatic, } { if componentsConfigured[component] { continue } first := cfgs[0] dirsBase := first.DirsBase() isMultihost := first.IsMultihost() for i, cfg := range cfgs { dirs := cfg.Dirs() var dir string var dropLang bool switch component { case files.ComponentFolderContent: dir = dirs.ContentDir dropLang = dir == dirsBase.ContentDir case files.ComponentFolderData: //lint:ignore SA1019 Keep as adapter for now. dir = dirs.DataDir case files.ComponentFolderLayouts: //lint:ignore SA1019 Keep as adapter for now. dir = dirs.LayoutDir case files.ComponentFolderI18n: //lint:ignore SA1019 Keep as adapter for now. dir = dirs.I18nDir case files.ComponentFolderArchetypes: //lint:ignore SA1019 Keep as adapter for now. dir = dirs.ArcheTypeDir case files.ComponentFolderAssets: //lint:ignore SA1019 Keep as adapter for now. dir = dirs.AssetDir case files.ComponentFolderStatic: // For static dirs, we only care about the language in multihost setups. dropLang = !isMultihost } var perLang bool switch component { case files.ComponentFolderContent, files.ComponentFolderStatic: perLang = true default: } if i > 0 && !perLang { continue } var lang string var sites sitesmatrix.Sites if perLang && !dropLang { l := cfg.Language().(*langs.Language) lang = l.Lang sites = sitesmatrix.Sites{ Matrix: sitesmatrix.StringSlices{ Languages: []string{l.Lang}, }, } } // Static mounts are a little special. if component == files.ComponentFolderStatic { staticDirs := cfg.StaticDirs() for _, dir := range staticDirs { mounts = append(mounts, Mount{Sites: sites, Source: dir, Target: component}) } continue } if dir != "" { mnt := Mount{Source: dir, Target: component, Sites: sites} if err := mnt.init(logger); err != nil { return fmt.Errorf("failed to init mount %q %d: %w", lang, i, err) } mounts = append(mounts, mnt) } } } moda.mounts = append(moda.mounts, mounts...) moda.mounts = filterDuplicateMounts(moda.mounts) return nil } // DecodeConfig creates a modules Config from a given Hugo configuration. func DecodeConfig(logger logg.Logger, cfg config.Provider) (Config, error) { return decodeConfig(logger, cfg, nil) } func decodeConfig(logger logg.Logger, cfg config.Provider, pathReplacements map[string]string) (Config, error) { c := DefaultModuleConfig c.replacementsMap = pathReplacements if cfg == nil { return c, nil } themeSet := cfg.IsSet("theme") moduleSet := cfg.IsSet("module") if moduleSet { m := cfg.GetStringMap("module") if err := mapstructure.WeakDecode(m, &c); err != nil { return c, err } if c.replacementsMap == nil { if len(c.Replacements) == 1 { c.Replacements = strings.Split(c.Replacements[0], ",") } for i, repl := range c.Replacements { c.Replacements[i] = strings.TrimSpace(repl) } c.replacementsMap = make(map[string]string) for _, repl := range c.Replacements { parts := strings.Split(repl, "->") if len(parts) != 2 { return c, fmt.Errorf(`invalid module.replacements: %q; configure replacement pairs on the form "oldpath->newpath" `, repl) } c.replacementsMap[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1]) } } if c.replacementsMap != nil && c.Imports != nil { for i, imp := range c.Imports { if newImp, found := c.replacementsMap[imp.Path]; found { imp.Path = newImp imp.pathProjectReplaced = true c.Imports[i] = imp } } } for i, mnt := range c.Mounts { mnt.Source = filepath.Clean(mnt.Source) mnt.Target = filepath.Clean(mnt.Target) if err := mnt.init(logger); err != nil { return c, fmt.Errorf("failed to init mount %d: %w", i, err) } c.Mounts[i] = mnt } if c.Workspace == "" { c.Workspace = WorkspaceDisabled } if c.Workspace != WorkspaceDisabled { c.Workspace = filepath.Clean(c.Workspace) if !filepath.IsAbs(c.Workspace) { workingDir := cfg.GetString("workingDir") c.Workspace = filepath.Join(workingDir, c.Workspace) } if _, err := os.Stat(c.Workspace); err != nil { //lint:ignore ST1005 end user message. return c, fmt.Errorf("module workspace %q does not exist. Check your module.workspace setting (or HUGO_MODULE_WORKSPACE env var).", c.Workspace) } } } if themeSet { imports := config.GetStringSlicePreserveString(cfg, "theme") for _, imp := range imports { c.Imports = append(c.Imports, Import{ Path: imp, }) } } return c, nil } // Config holds a module config. type Config struct { // File system mounts. Mounts []Mount // Module imports. Imports []Import // Meta info about this module (license information etc.). Params map[string]any // Will be validated against the running Hugo version. HugoVersion HugoVersion // Optional Glob pattern matching module paths to skip when vendoring, e.g. “github.com/**” NoVendor string // When enabled, we will pick the vendored module closest to the module // using it. // The default behavior is to pick the first. // Note that there can still be only one dependency of a given module path, // so once it is in use it cannot be redefined. VendorClosest bool // A comma separated (or a slice) list of module path to directory replacement mapping, // e.g. github.com/bep/my-theme -> ../..,github.com/bep/shortcodes -> /some/path. // This is mostly useful for temporary locally development of a module, and then it makes sense to set it as an // OS environment variable, e.g: env HUGO_MODULE_REPLACEMENTS="github.com/bep/my-theme -> ../..". // Any relative path is relate to themesDir, and absolute paths are allowed. Replacements []string replacementsMap map[string]string // Defines the proxy server to use to download remote modules. Default is direct, which means “git clone” and similar. // Configures GOPROXY when running the Go command for module operations. Proxy string // Comma separated glob list matching paths that should not use the proxy configured above. // Configures GONOPROXY when running the Go command for module operations. NoProxy string // Comma separated glob list matching paths that should be treated as private. // Configures GOPRIVATE when running the Go command for module operations. Private string // Configures GOAUTH when running the Go command for module operations. // This is a semicolon-separated list of authentication commands for go-import and HTTPS module mirror interactions. // This is useful for private repositories. // See `go help goauth` for more information. Auth string // Defaults to "off". // Set to a work file, e.g. hugo.work, to enable Go "Workspace" mode. // Can be relative to the working directory or absolute. // Requires Go 1.18+. // Note that this can also be set via OS env, e.g. export HUGO_MODULE_WORKSPACE=/my/hugo.work. Workspace string } // hasModuleImport reports whether the project config have one or more // modules imports, e.g. github.com/bep/myshortcodes. func (c Config) hasModuleImport() bool { for _, imp := range c.Imports { if isProbablyModule(imp.Path) { return true } } return false } // HugoVersion holds Hugo binary version requirements for a module. type HugoVersion struct { // The minimum Hugo version that this module works with. Min version.VersionString // The maximum Hugo version that this module works with. Max version.VersionString // Set if the extended version is needed. Extended bool } func (v HugoVersion) String() string { extended := "" if v.Extended { extended = " extended" } if v.Min != "" && v.Max != "" { return fmt.Sprintf("%s/%s%s", v.Min, v.Max, extended) } if v.Min != "" { return fmt.Sprintf("Min %s%s", v.Min, extended) } if v.Max != "" { return fmt.Sprintf("Max %s%s", v.Max, extended) } return extended } // IsValid reports whether this version is valid compared to the running // Hugo binary. func (v HugoVersion) IsValid() bool { current := hugo.CurrentVersion.Version() if v.Min != "" && current.Compare(v.Min) > 0 { return false } if v.Max != "" && current.Compare(v.Max) < 0 { return false } return true } type Import struct { // Module path Path string // The common case is to leave this empty and let Go Modules resolve the version. // Can be set to a version query, e.g. "v1.2.3", ">=v1.2.0", "latest", which will // make this a direct dependency. Version string // Set when Path is replaced in project config. pathProjectReplaced bool // Ignore any config in config.toml (will still follow imports). IgnoreConfig bool // Do not follow any configured imports. IgnoreImports bool // Do not mount any folder in this import. NoMounts bool // Never vendor this import (only allowed in main project). NoVendor bool // Turn off this module. Disable bool // File mounts. Mounts []Mount // Controls whether npm package files (package.json/package.hugo.json) are // read from this module. Values: "auto" (default), "always", "never". // "auto" reads package files when a Hugo config file or package.hugo.json // is present in the module root. UsePackageJSON string } const ( UsePackageJSONAuto = "auto" UsePackageJSONAlways = "always" UsePackageJSONNever = "never" ) type Mount struct { // Relative path in source repo, e.g. "scss". Source string // Relative target path, e.g. "assets/bootstrap/scss". Target string // Any file in this mount will be associated with this language. // Deprecated, use Sites instead. Lang string `json:"-"` // Sites defines which sites this mount applies to. Sites sitesmatrix.Sites // A slice of Glob patterns (string or slice) to exclude or include in this mount. // To exclude, prefix with "! ". Files []string // Include only files matching the given Glob patterns (string or slice). // Deprecated, use Files instead. IncludeFiles any `json:"-"` // Exclude all files matching the given Glob patterns (string or slice). // Deprecated, use Files instead. ExcludeFiles any `json:"-"` // Disable watching in watch mode for this mount. DisableWatch bool } func (m Mount) Equal(o Mount) bool { if m.Lang != o.Lang { return false } if m.Source != o.Source { return false } if m.Target != o.Target { return false } if m.DisableWatch != o.DisableWatch { return false } if !m.Sites.Equal(o.Sites) { return false } patterns, hasLegacy11, hasLegacy12 := m.FilesToFilter() patterns2, hasLegacy21, hasLegacy22 := o.FilesToFilter() if hasLegacy11 != hasLegacy21 || hasLegacy12 != hasLegacy22 { return false } return slices.Equal(patterns, patterns2) } func (m Mount) FilesToFilter() (patterns []string, hasLegacyIncludeFiles, hasLegacyExcludeFiles bool) { patterns = m.Files // Legacy config, add IncludeFiles first. for _, pattern := range types.ToStringSlicePreserveString(m.IncludeFiles) { hasLegacyIncludeFiles = true patterns = append(patterns, pattern) } for _, pattern := range types.ToStringSlicePreserveString(m.ExcludeFiles) { hasLegacyExcludeFiles = true patterns = append(patterns, hglob.NegationPrefix+pattern) } return patterns, hasLegacyIncludeFiles, hasLegacyExcludeFiles } func (m Mount) Component() string { return strings.Split(m.Target, fileSeparator)[0] } func (m Mount) ComponentAndName() (string, string) { c, n, _ := strings.Cut(m.Target, fileSeparator) return c, n } func (m *Mount) init(logger logg.Logger) error { if m.Lang != "" { // We moved this to a more flixeble setup in Hugo 0.153.0. m.Sites.Matrix.Languages = append(m.Sites.Matrix.Languages, m.Lang) m.Lang = "" hugo.DeprecateWithLogger("module.mounts.lang", "Replaced by the more powerful 'sites.matrix' setting, see https://gohugo.io/configuration/module/#mounts", "v0.153.0", logger) } m.Sites.Matrix.Languages = hstrings.UniqueStringsReuse(m.Sites.Matrix.Languages) return nil }