// Copyright 2020 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 commands import ( "errors" "fmt" "os" "path/filepath" "regexp" "github.com/gohugoio/hugo/hugolib" "github.com/gohugoio/hugo/modules" "github.com/spf13/cobra" ) var _ cmder = (*modCmd)(nil) type modCmd struct { *baseBuilderCmd } func (c *modCmd) newVerifyCmd() *cobra.Command { var clean bool verifyCmd := &cobra.Command{ Use: "verify", Short: "Verify dependencies.", Long: `Verify checks that the dependencies of the current module, which are stored in a local downloaded source cache, have not been modified since being downloaded. `, RunE: func(cmd *cobra.Command, args []string) error { return c.withModsClient(true, func(c *modules.Client) error { return c.Verify(clean) }) }, } verifyCmd.Flags().BoolVarP(&clean, "clean", "", false, "delete module cache for dependencies that fail verification") return verifyCmd } var moduleNotFoundRe = regexp.MustCompile("module.*not found") func (c *modCmd) newCleanCmd() *cobra.Command { var pattern string var all bool cmd := &cobra.Command{ Use: "clean", Short: "Delete the Hugo Module cache for the current project.", Long: `Delete the Hugo Module cache for the current project. Note that after you run this command, all of your dependencies will be re-downloaded next time you run "hugo". Also note that if you configure a positive maxAge for the "modules" file cache, it will also be cleaned as part of "hugo --gc". `, RunE: func(cmd *cobra.Command, args []string) error { if all { com, err := c.initConfig(false) if err != nil && com == nil { return err } count, err := com.hugo().FileCaches.ModulesCache().Prune(true) com.logger.Printf("Deleted %d files from module cache.", count) return err } return c.withModsClient(true, func(c *modules.Client) error { return c.Clean(pattern) }) }, } cmd.Flags().StringVarP(&pattern, "pattern", "", "", `pattern matching module paths to clean (all if not set), e.g. "**hugo*"`) cmd.Flags().BoolVarP(&all, "all", "", false, "clean entire module cache") return cmd } func (b *commandsBuilder) newModCmd() *modCmd { c := &modCmd{} const commonUsage = ` Note that Hugo will always start out by resolving the components defined in the site configuration, provided by a _vendor directory (if no --ignoreVendor flag provided), Go Modules, or a folder inside the themes directory, in that order. See https://gohugo.io/hugo-modules/ for more information. ` cmd := &cobra.Command{ Use: "mod", Short: "Various Hugo Modules helpers.", Long: `Various helpers to help manage the modules in your project's dependency graph. Most operations here requires a Go version installed on your system (>= Go 1.12) and the relevant VCS client (typically Git). This is not needed if you only operate on modules inside /themes or if you have vendored them via "hugo mod vendor". ` + commonUsage, RunE: nil, } cmd.AddCommand(newModNPMCmd(c)) cmd.AddCommand( &cobra.Command{ Use: "get", DisableFlagParsing: true, Short: "Resolves dependencies in your current Hugo Project.", Long: ` Resolves dependencies in your current Hugo Project. Some examples: Install the latest version possible for a given module: hugo mod get github.com/gohugoio/testshortcodes Install a specific version: hugo mod get github.com/gohugoio/testshortcodes@v0.3.0 Install the latest versions of all module dependencies: hugo mod get -u hugo mod get -u ./... (recursive) Run "go help get" for more information. All flags available for "go get" is also relevant here. ` + commonUsage, RunE: func(cmd *cobra.Command, args []string) error { // We currently just pass on the flags we get to Go and // need to do the flag handling manually. if len(args) == 1 && args[0] == "-h" { return cmd.Help() } var lastArg string if len(args) != 0 { lastArg = args[len(args)-1] } if lastArg == "./..." { args = args[:len(args)-1] // Do a recursive update. dirname, err := os.Getwd() if err != nil { return err } // Sanity check. We do recursive walking and want to avoid // accidents. if len(dirname) < 5 { return errors.New("must not be run from the file system root") } filepath.Walk(dirname, func(path string, info os.FileInfo, err error) error { if info.IsDir() { return nil } if info.Name() == "go.mod" { // Found a module. dir := filepath.Dir(path) fmt.Println("Update module in", dir) c.source = dir err := c.withModsClient(false, func(c *modules.Client) error { if len(args) == 1 && args[0] == "-h" { return cmd.Help() } return c.Get(args...) }) if err != nil { return err } } return nil }) return nil } return c.withModsClient(false, func(c *modules.Client) error { return c.Get(args...) }) }, }, &cobra.Command{ Use: "graph", Short: "Print a module dependency graph.", Long: `Print a module dependency graph with information about module status (disabled, vendored). Note that for vendored modules, that is the version listed and not the one from go.mod. `, RunE: func(cmd *cobra.Command, args []string) error { return c.withModsClient(true, func(c *modules.Client) error { return c.Graph(os.Stdout) }) }, }, &cobra.Command{ Use: "init", Short: "Initialize this project as a Hugo Module.", Long: `Initialize this project as a Hugo Module. It will try to guess the module path, but you may help by passing it as an argument, e.g: hugo mod init github.com/gohugoio/testshortcodes Note that Hugo Modules supports multi-module projects, so you can initialize a Hugo Module inside a subfolder on GitHub, as one example. `, RunE: func(cmd *cobra.Command, args []string) error { var path string if len(args) >= 1 { path = args[0] } return c.withModsClient(false, func(c *modules.Client) error { return c.Init(path) }) }, }, &cobra.Command{ Use: "vendor", Short: "Vendor all module dependencies into the _vendor directory.", Long: `Vendor all module dependencies into the _vendor directory. If a module is vendored, that is where Hugo will look for it's dependencies. `, RunE: func(cmd *cobra.Command, args []string) error { return c.withModsClient(true, func(c *modules.Client) error { return c.Vendor() }) }, }, c.newVerifyCmd(), &cobra.Command{ Use: "tidy", Short: "Remove unused entries in go.mod and go.sum.", RunE: func(cmd *cobra.Command, args []string) error { return c.withModsClient(true, func(c *modules.Client) error { return c.Tidy() }) }, }, c.newCleanCmd(), ) c.baseBuilderCmd = b.newBuilderCmd(cmd) return c } func (c *modCmd) withModsClient(failOnMissingConfig bool, f func(*modules.Client) error) error { com, err := c.initConfig(failOnMissingConfig) if err != nil { return err } return f(com.hugo().ModulesClient) } func (c *modCmd) withHugo(f func(*hugolib.HugoSites) error) error { com, err := c.initConfig(true) if err != nil { return err } return f(com.hugo()) } func (c *modCmd) initConfig(failOnNoConfig bool) (*commandeer, error) { com, err := initializeConfig(failOnNoConfig, false, false, &c.hugoBuilderCommon, c, nil) if err != nil { return nil, err } return com, nil }