github.com/gohugoio/hugo@v0.88.1/commands/mod.go (about)

     1  // Copyright 2020 The Hugo Authors. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package commands
    15  
    16  import (
    17  	"errors"
    18  	"fmt"
    19  	"os"
    20  	"path/filepath"
    21  	"regexp"
    22  
    23  	"github.com/gohugoio/hugo/hugolib"
    24  
    25  	"github.com/gohugoio/hugo/modules"
    26  	"github.com/spf13/cobra"
    27  )
    28  
    29  var _ cmder = (*modCmd)(nil)
    30  
    31  type modCmd struct {
    32  	*baseBuilderCmd
    33  }
    34  
    35  func (c *modCmd) newVerifyCmd() *cobra.Command {
    36  	var clean bool
    37  
    38  	verifyCmd := &cobra.Command{
    39  		Use:   "verify",
    40  		Short: "Verify dependencies.",
    41  		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.
    42  `,
    43  		RunE: func(cmd *cobra.Command, args []string) error {
    44  			return c.withModsClient(true, func(c *modules.Client) error {
    45  				return c.Verify(clean)
    46  			})
    47  		},
    48  	}
    49  
    50  	verifyCmd.Flags().BoolVarP(&clean, "clean", "", false, "delete module cache for dependencies that fail verification")
    51  
    52  	return verifyCmd
    53  }
    54  
    55  var moduleNotFoundRe = regexp.MustCompile("module.*not found")
    56  
    57  func (c *modCmd) newCleanCmd() *cobra.Command {
    58  	var pattern string
    59  	var all bool
    60  	cmd := &cobra.Command{
    61  		Use:   "clean",
    62  		Short: "Delete the Hugo Module cache for the current project.",
    63  		Long: `Delete the Hugo Module cache for the current project.
    64  
    65  Note that after you run this command, all of your dependencies will be re-downloaded next time you run "hugo".
    66  
    67  Also note that if you configure a positive maxAge for the "modules" file cache, it will also be cleaned as part of "hugo --gc".
    68   
    69  `,
    70  		RunE: func(cmd *cobra.Command, args []string) error {
    71  			if all {
    72  				com, err := c.initConfig(false)
    73  
    74  				if err != nil && !moduleNotFoundRe.MatchString(err.Error()) {
    75  					return err
    76  				}
    77  
    78  				_, err = com.hugo().FileCaches.ModulesCache().Prune(true)
    79  				return err
    80  			}
    81  			return c.withModsClient(true, func(c *modules.Client) error {
    82  				return c.Clean(pattern)
    83  			})
    84  		},
    85  	}
    86  
    87  	cmd.Flags().StringVarP(&pattern, "pattern", "", "", `pattern matching module paths to clean (all if not set), e.g. "**hugo*"`)
    88  	cmd.Flags().BoolVarP(&all, "all", "", false, "clean entire module cache")
    89  
    90  	return cmd
    91  }
    92  
    93  func (b *commandsBuilder) newModCmd() *modCmd {
    94  	c := &modCmd{}
    95  
    96  	const commonUsage = `
    97  Note that Hugo will always start out by resolving the components defined in the site
    98  configuration, provided by a _vendor directory (if no --ignoreVendor flag provided),
    99  Go Modules, or a folder inside the themes directory, in that order.
   100  
   101  See https://gohugo.io/hugo-modules/ for more information.
   102  
   103  `
   104  
   105  	cmd := &cobra.Command{
   106  		Use:   "mod",
   107  		Short: "Various Hugo Modules helpers.",
   108  		Long: `Various helpers to help manage the modules in your project's dependency graph.
   109  
   110  Most operations here requires a Go version installed on your system (>= Go 1.12) and the relevant VCS client (typically Git).
   111  This is not needed if you only operate on modules inside /themes or if you have vendored them via "hugo mod vendor".
   112  
   113  ` + commonUsage,
   114  
   115  		RunE: nil,
   116  	}
   117  
   118  	cmd.AddCommand(newModNPMCmd(c))
   119  
   120  	cmd.AddCommand(
   121  		&cobra.Command{
   122  			Use:                "get",
   123  			DisableFlagParsing: true,
   124  			Short:              "Resolves dependencies in your current Hugo Project.",
   125  			Long: `
   126  Resolves dependencies in your current Hugo Project.
   127  
   128  Some examples:
   129  
   130  Install the latest version possible for a given module:
   131  
   132      hugo mod get github.com/gohugoio/testshortcodes
   133      
   134  Install a specific version:
   135  
   136      hugo mod get github.com/gohugoio/testshortcodes@v0.3.0
   137  
   138  Install the latest versions of all module dependencies:
   139  
   140      hugo mod get -u
   141      hugo mod get -u ./... (recursive)
   142  
   143  Run "go help get" for more information. All flags available for "go get" is also relevant here.
   144  ` + commonUsage,
   145  			RunE: func(cmd *cobra.Command, args []string) error {
   146  				// We currently just pass on the flags we get to Go and
   147  				// need to do the flag handling manually.
   148  				if len(args) == 1 && args[0] == "-h" {
   149  					return cmd.Help()
   150  				}
   151  
   152  				var lastArg string
   153  				if len(args) != 0 {
   154  					lastArg = args[len(args)-1]
   155  				}
   156  
   157  				if lastArg == "./..." {
   158  					args = args[:len(args)-1]
   159  					// Do a recursive update.
   160  					dirname, err := os.Getwd()
   161  					if err != nil {
   162  						return err
   163  					}
   164  
   165  					// Sanity check. We do recursive walking and want to avoid
   166  					// accidents.
   167  					if len(dirname) < 5 {
   168  						return errors.New("must not be run from the file system root")
   169  					}
   170  
   171  					filepath.Walk(dirname, func(path string, info os.FileInfo, err error) error {
   172  						if info.IsDir() {
   173  							return nil
   174  						}
   175  
   176  						if info.Name() == "go.mod" {
   177  							// Found a module.
   178  							dir := filepath.Dir(path)
   179  							fmt.Println("Update module in", dir)
   180  							c.source = dir
   181  							err := c.withModsClient(false, func(c *modules.Client) error {
   182  								if len(args) == 1 && args[0] == "-h" {
   183  									return cmd.Help()
   184  								}
   185  								return c.Get(args...)
   186  							})
   187  							if err != nil {
   188  								return err
   189  							}
   190  
   191  						}
   192  
   193  						return nil
   194  					})
   195  
   196  					return nil
   197  				}
   198  
   199  				return c.withModsClient(false, func(c *modules.Client) error {
   200  					return c.Get(args...)
   201  				})
   202  			},
   203  		},
   204  		&cobra.Command{
   205  			Use:   "graph",
   206  			Short: "Print a module dependency graph.",
   207  			Long: `Print a module dependency graph with information about module status (disabled, vendored).
   208  Note that for vendored modules, that is the version listed and not the one from go.mod.
   209  `,
   210  			RunE: func(cmd *cobra.Command, args []string) error {
   211  				return c.withModsClient(true, func(c *modules.Client) error {
   212  					return c.Graph(os.Stdout)
   213  				})
   214  			},
   215  		},
   216  		&cobra.Command{
   217  			Use:   "init",
   218  			Short: "Initialize this project as a Hugo Module.",
   219  			Long: `Initialize this project as a Hugo Module.
   220  It will try to guess the module path, but you may help by passing it as an argument, e.g:
   221  
   222      hugo mod init github.com/gohugoio/testshortcodes
   223  
   224  Note that Hugo Modules supports multi-module projects, so you can initialize a Hugo Module
   225  inside a subfolder on GitHub, as one example.
   226  `,
   227  			RunE: func(cmd *cobra.Command, args []string) error {
   228  				var path string
   229  				if len(args) >= 1 {
   230  					path = args[0]
   231  				}
   232  				return c.withModsClient(false, func(c *modules.Client) error {
   233  					return c.Init(path)
   234  				})
   235  			},
   236  		},
   237  		&cobra.Command{
   238  			Use:   "vendor",
   239  			Short: "Vendor all module dependencies into the _vendor directory.",
   240  			Long: `Vendor all module dependencies into the _vendor directory.
   241  
   242  If a module is vendored, that is where Hugo will look for it's dependencies.
   243  `,
   244  			RunE: func(cmd *cobra.Command, args []string) error {
   245  				return c.withModsClient(true, func(c *modules.Client) error {
   246  					return c.Vendor()
   247  				})
   248  			},
   249  		},
   250  		c.newVerifyCmd(),
   251  		&cobra.Command{
   252  			Use:   "tidy",
   253  			Short: "Remove unused entries in go.mod and go.sum.",
   254  			RunE: func(cmd *cobra.Command, args []string) error {
   255  				return c.withModsClient(true, func(c *modules.Client) error {
   256  					return c.Tidy()
   257  				})
   258  			},
   259  		},
   260  		c.newCleanCmd(),
   261  	)
   262  
   263  	c.baseBuilderCmd = b.newBuilderCmd(cmd)
   264  
   265  	return c
   266  }
   267  
   268  func (c *modCmd) withModsClient(failOnMissingConfig bool, f func(*modules.Client) error) error {
   269  	com, err := c.initConfig(failOnMissingConfig)
   270  	if err != nil {
   271  		return err
   272  	}
   273  
   274  	return f(com.hugo().ModulesClient)
   275  }
   276  
   277  func (c *modCmd) withHugo(f func(*hugolib.HugoSites) error) error {
   278  	com, err := c.initConfig(true)
   279  	if err != nil {
   280  		return err
   281  	}
   282  
   283  	return f(com.hugo())
   284  }
   285  
   286  func (c *modCmd) initConfig(failOnNoConfig bool) (*commandeer, error) {
   287  	com, err := initializeConfig(failOnNoConfig, false, false, &c.hugoBuilderCommon, c, nil)
   288  	if err != nil {
   289  		return nil, err
   290  	}
   291  	return com, nil
   292  }