github.com/linchen2chris/hugo@v0.0.0-20230307053224-cec209389705/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 && com == nil {
    75  					return err
    76  				}
    77  
    78  				count, err := com.hugo().FileCaches.ModulesCache().Prune(true)
    79  				com.logger.Printf("Deleted %d files from module cache.", count)
    80  				return err
    81  			}
    82  			return c.withModsClient(true, func(c *modules.Client) error {
    83  				return c.Clean(pattern)
    84  			})
    85  		},
    86  	}
    87  
    88  	cmd.Flags().StringVarP(&pattern, "pattern", "", "", `pattern matching module paths to clean (all if not set), e.g. "**hugo*"`)
    89  	cmd.Flags().BoolVarP(&all, "all", "", false, "clean entire module cache")
    90  
    91  	return cmd
    92  }
    93  
    94  func (b *commandsBuilder) newModCmd() *modCmd {
    95  	c := &modCmd{}
    96  
    97  	const commonUsage = `
    98  Note that Hugo will always start out by resolving the components defined in the site
    99  configuration, provided by a _vendor directory (if no --ignoreVendorPaths flag provided),
   100  Go Modules, or a folder inside the themes directory, in that order.
   101  
   102  See https://gohugo.io/hugo-modules/ for more information.
   103  
   104  `
   105  
   106  	cmd := &cobra.Command{
   107  		Use:   "mod",
   108  		Short: "Various Hugo Modules helpers.",
   109  		Long: `Various helpers to help manage the modules in your project's dependency graph.
   110  
   111  Most operations here requires a Go version installed on your system (>= Go 1.12) and the relevant VCS client (typically Git).
   112  This is not needed if you only operate on modules inside /themes or if you have vendored them via "hugo mod vendor".
   113  
   114  ` + commonUsage,
   115  
   116  		RunE: nil,
   117  	}
   118  
   119  	cmd.AddCommand(newModNPMCmd(c))
   120  
   121  	cmd.AddCommand(
   122  		&cobra.Command{
   123  			Use:                "get",
   124  			DisableFlagParsing: true,
   125  			Short:              "Resolves dependencies in your current Hugo Project.",
   126  			Long: `
   127  Resolves dependencies in your current Hugo Project.
   128  
   129  Some examples:
   130  
   131  Install the latest version possible for a given module:
   132  
   133      hugo mod get github.com/gohugoio/testshortcodes
   134      
   135  Install a specific version:
   136  
   137      hugo mod get github.com/gohugoio/testshortcodes@v0.3.0
   138  
   139  Install the latest versions of all module dependencies:
   140  
   141      hugo mod get -u
   142      hugo mod get -u ./... (recursive)
   143  
   144  Run "go help get" for more information. All flags available for "go get" is also relevant here.
   145  ` + commonUsage,
   146  			RunE: func(cmd *cobra.Command, args []string) error {
   147  				// We currently just pass on the flags we get to Go and
   148  				// need to do the flag handling manually.
   149  				if len(args) == 1 && args[0] == "-h" {
   150  					return cmd.Help()
   151  				}
   152  
   153  				var lastArg string
   154  				if len(args) != 0 {
   155  					lastArg = args[len(args)-1]
   156  				}
   157  
   158  				if lastArg == "./..." {
   159  					args = args[:len(args)-1]
   160  					// Do a recursive update.
   161  					dirname, err := os.Getwd()
   162  					if err != nil {
   163  						return err
   164  					}
   165  
   166  					// Sanity check. We do recursive walking and want to avoid
   167  					// accidents.
   168  					if len(dirname) < 5 {
   169  						return errors.New("must not be run from the file system root")
   170  					}
   171  
   172  					filepath.Walk(dirname, func(path string, info os.FileInfo, err error) error {
   173  						if info.IsDir() {
   174  							return nil
   175  						}
   176  
   177  						if info.Name() == "go.mod" {
   178  							// Found a module.
   179  							dir := filepath.Dir(path)
   180  							fmt.Println("Update module in", dir)
   181  							c.source = dir
   182  							err := c.withModsClient(false, func(c *modules.Client) error {
   183  								if len(args) == 1 && args[0] == "-h" {
   184  									return cmd.Help()
   185  								}
   186  								return c.Get(args...)
   187  							})
   188  							if err != nil {
   189  								return err
   190  							}
   191  
   192  						}
   193  
   194  						return nil
   195  					})
   196  
   197  					return nil
   198  				}
   199  
   200  				return c.withModsClient(false, func(c *modules.Client) error {
   201  					return c.Get(args...)
   202  				})
   203  			},
   204  		},
   205  		&cobra.Command{
   206  			Use:   "graph",
   207  			Short: "Print a module dependency graph.",
   208  			Long: `Print a module dependency graph with information about module status (disabled, vendored).
   209  Note that for vendored modules, that is the version listed and not the one from go.mod.
   210  `,
   211  			RunE: func(cmd *cobra.Command, args []string) error {
   212  				return c.withModsClient(true, func(c *modules.Client) error {
   213  					return c.Graph(os.Stdout)
   214  				})
   215  			},
   216  		},
   217  		&cobra.Command{
   218  			Use:   "init",
   219  			Short: "Initialize this project as a Hugo Module.",
   220  			Long: `Initialize this project as a Hugo Module.
   221  It will try to guess the module path, but you may help by passing it as an argument, e.g:
   222  
   223      hugo mod init github.com/gohugoio/testshortcodes
   224  
   225  Note that Hugo Modules supports multi-module projects, so you can initialize a Hugo Module
   226  inside a subfolder on GitHub, as one example.
   227  `,
   228  			RunE: func(cmd *cobra.Command, args []string) error {
   229  				var path string
   230  				if len(args) >= 1 {
   231  					path = args[0]
   232  				}
   233  				return c.withModsClient(false, func(c *modules.Client) error {
   234  					return c.Init(path)
   235  				})
   236  			},
   237  		},
   238  		&cobra.Command{
   239  			Use:   "vendor",
   240  			Short: "Vendor all module dependencies into the _vendor directory.",
   241  			Long: `Vendor all module dependencies into the _vendor directory.
   242  
   243  If a module is vendored, that is where Hugo will look for it's dependencies.
   244  `,
   245  			RunE: func(cmd *cobra.Command, args []string) error {
   246  				return c.withModsClient(true, func(c *modules.Client) error {
   247  					return c.Vendor()
   248  				})
   249  			},
   250  		},
   251  		c.newVerifyCmd(),
   252  		&cobra.Command{
   253  			Use:   "tidy",
   254  			Short: "Remove unused entries in go.mod and go.sum.",
   255  			RunE: func(cmd *cobra.Command, args []string) error {
   256  				return c.withModsClient(true, func(c *modules.Client) error {
   257  					return c.Tidy()
   258  				})
   259  			},
   260  		},
   261  		c.newCleanCmd(),
   262  	)
   263  
   264  	c.baseBuilderCmd = b.newBuilderCmd(cmd)
   265  
   266  	return c
   267  }
   268  
   269  func (c *modCmd) withModsClient(failOnMissingConfig bool, f func(*modules.Client) error) error {
   270  	com, err := c.initConfig(failOnMissingConfig)
   271  	if err != nil {
   272  		return err
   273  	}
   274  
   275  	return f(com.hugo().ModulesClient)
   276  }
   277  
   278  func (c *modCmd) withHugo(f func(*hugolib.HugoSites) error) error {
   279  	com, err := c.initConfig(true)
   280  	if err != nil {
   281  		return err
   282  	}
   283  
   284  	return f(com.hugo())
   285  }
   286  
   287  func (c *modCmd) initConfig(failOnNoConfig bool) (*commandeer, error) {
   288  	com, err := initializeConfig(failOnNoConfig, false, false, &c.hugoBuilderCommon, c, nil)
   289  	if err != nil {
   290  		return nil, err
   291  	}
   292  	return com, nil
   293  }