mod.go (8124B)
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 }