Skip to content

Commit

Permalink
Add "hugo mod npm package"
Browse files Browse the repository at this point in the history
  • Loading branch information
bep committed Sep 11, 2020
1 parent d4611c4 commit 215226e
Show file tree
Hide file tree
Showing 11 changed files with 531 additions and 17 deletions.
15 changes: 14 additions & 1 deletion commands/mod.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2019 The Hugo Authors. All rights reserved.
// 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.
Expand All @@ -20,6 +20,8 @@ import (
"path/filepath"
"regexp"

"github.com/gohugoio/hugo/hugolib"

"github.com/gohugoio/hugo/modules"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -114,6 +116,8 @@ This is not needed if you only operate on modules inside /themes or if you have
RunE: nil,
}

cmd.AddCommand(newModNPMCmd(c))

cmd.AddCommand(
&cobra.Command{
Use: "get",
Expand Down Expand Up @@ -272,6 +276,15 @@ func (c *modCmd) withModsClient(failOnMissingConfig bool, f func(*modules.Client
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, &c.hugoBuilderCommon, c, nil)
if err != nil {
Expand Down
62 changes: 62 additions & 0 deletions commands/npm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// 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 (
"fmt"

"github.com/gohugoio/hugo/hugolib"
"github.com/spf13/cobra"
)

func newModNPMCmd(c *modCmd) *cobra.Command {

cmd := &cobra.Command{
Use: "npm",
Short: "Various npm helpers.",
Long: `Various npm (Node package manager) helpers.`,
RunE: func(cmd *cobra.Command, args []string) error {
return c.withHugo(func(h *hugolib.HugoSites) error {
return nil
})
},
}

cmd.AddCommand(&cobra.Command{
Use: "pack",
Short: "Experimental: Prepares and writes a composite package.json file for your project.",
Long: `Prepares and writes a composite package.json file for your project.
Note that this command will fail if there is already a "package.json" file in the project.
If there is a file named "package.hugo.json", that file will be used as a template file
with the base dependency set.
This command is marked as 'Experimental'. We think it's a great idea, so it's not likely to be
removed from Hugo, but we need to test this out in "real life" to get a feel of it,
so this may/will change in future versions of Hugo.
`,
RunE: func(cmd *cobra.Command, args []string) error {

return c.withHugo(func(h *hugolib.HugoSites) error {
fmt.Println("PACK", h)

return nil

})
},
})

return cmd
}
1 change: 1 addition & 0 deletions hugofs/files/classifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ func isHTMLContent(r io.Reader) bool {
}

const (
ComponentFolderRoot = "root" // Pseudo folder for files in the root of the project/module.
ComponentFolderArchetypes = "archetypes"
ComponentFolderStatic = "static"
ComponentFolderLayouts = "layouts"
Expand Down
5 changes: 1 addition & 4 deletions hugofs/rootmapping_fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,6 @@ func NewRootMappingFs(fs afero.Fs, rms ...RootMapping) (*RootMappingFs, error) {
(&rm).clean()

fromBase := files.ResolveComponentFolder(rm.From)
if fromBase == "" {
panic("unrecognised component folder in" + rm.From)
}

if len(rm.To) < 2 {
panic(fmt.Sprintf("invalid root mapping; from/to: %s/%s", rm.From, rm.To))
Expand Down Expand Up @@ -113,7 +110,7 @@ func newRootMappingFsFromFromTo(
ToBasedir: baseDir,
}
}

return NewRootMappingFs(fs, rms...)
}

Expand Down
25 changes: 21 additions & 4 deletions hugolib/filesystems/basefs.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ type BaseFs struct {
// SourceFilesystems contains the different source file systems.
*SourceFilesystems

// The project source.
SourceFs afero.Fs

// The filesystem used to publish the rendered site.
// This usually maps to /my-project/public.
PublishFs afero.Fs
Expand Down Expand Up @@ -111,6 +114,10 @@ type SourceFilesystems struct {
Archetypes *SourceFilesystem
Assets *SourceFilesystem

// A special filesystem with a set of files only, e.g. package.json,
// postcss.config.js etc.
Root *SourceFilesystem

// Writable filesystem on top the project's resources directory,
// with any sub module's resource fs layered below.
ResourcesCache afero.Fs
Expand Down Expand Up @@ -346,8 +353,10 @@ func NewBase(p *paths.Paths, logger *loggers.Logger, options ...func(*BaseFs) er
}

publishFs := hugofs.NewBaseFileDecorator(afero.NewBasePathFs(fs.Destination, p.AbsPublishDir))
sourceFs := hugofs.NewBaseFileDecorator(afero.NewBasePathFs(fs.Source, p.WorkingDir))

b := &BaseFs{
SourceFs: sourceFs,
PublishFs: publishFs,
}

Expand Down Expand Up @@ -422,6 +431,7 @@ func (b *sourceFilesystemsBuilder) Build() (*SourceFilesystems, error) {
b.result.Archetypes = createView(files.ComponentFolderArchetypes)
b.result.Layouts = createView(files.ComponentFolderLayouts)
b.result.Assets = createView(files.ComponentFolderAssets)
b.result.Root = createView(files.ComponentFolderRoot)
b.result.ResourcesCache = b.theBigFs.overlayResources

// Data, i18n and content cannot use the overlay fs
Expand Down Expand Up @@ -696,11 +706,18 @@ type filesystemsCollector struct {

func (c *filesystemsCollector) addDirs(rfs *hugofs.RootMappingFs) {
for _, componentFolder := range files.ComponentFolders {
dirs, err := rfs.Dirs(componentFolder)
c.addDir(rfs, componentFolder)
}

if err == nil {
c.overlayDirs[componentFolder] = append(c.overlayDirs[componentFolder], dirs...)
}
// Also add the special root folder.
c.addDir(rfs, files.ComponentFolderRoot)
}

func (c *filesystemsCollector) addDir(rfs *hugofs.RootMappingFs, componentFolder string) {
dirs, err := rfs.Dirs(componentFolder)

if err == nil {
c.overlayDirs[componentFolder] = append(c.overlayDirs[componentFolder], dirs...)
}
}

Expand Down
36 changes: 36 additions & 0 deletions hugolib/filesystems/basefs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,42 @@ func TestMakePathRelative(t *testing.T) {

}

func TestRootFs(t *testing.T) {
c := qt.New(t)
v := createConfig()
fs := hugofs.NewMem(v)
workDir := "mywork"
v.Set("workingDir", workDir)

c.Assert(afero.WriteFile(fs.Source, filepath.Join(workDir, "package.json"), []byte(`my packages`), 0777), qt.IsNil)

moduleCfg := map[string]interface{}{
"mounts": []interface{}{
map[string]interface{}{
"source": "package.json",
"target": "package.json",
},
},
}

v.Set("module", moduleCfg)

c.Assert(initConfig(fs.Source, v), qt.IsNil)

p, err := paths.New(fs, v)
c.Assert(err, qt.IsNil)
bfs, err := NewBase(p, nil)
c.Assert(err, qt.IsNil)

rfs := bfs.Root
c.Assert(rfs, qt.Not(qt.IsNil))

c.Assert(rfs.Dirs, qt.HasLen, 1)
checkFileCount(rfs.Fs, "", c, 1)
checkFileContent(rfs.Fs, "package.json", c, "my packages")

}

func checkFileCount(fs afero.Fs, dirname string, c *qt.C, expected int) {
count, _, err := countFilesAndGetFilenames(fs, dirname)
c.Assert(err, qt.IsNil)
Expand Down
54 changes: 53 additions & 1 deletion hugolib/hugo_modules_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import (
"testing"
"time"

"github.com/gohugoio/hugo/modules/npm"

"github.com/gohugoio/hugo/common/loggers"

"github.com/spf13/afero"
Expand All @@ -38,7 +40,6 @@ import (
"github.com/spf13/viper"
)

// https://github.com/gohugoio/hugo/issues/6730
func TestHugoModulesVariants(t *testing.T) {
if !isCI() {
t.Skip("skip (relative) long running modules test when running locally")
Expand Down Expand Up @@ -129,6 +130,57 @@ JS imported in module: |
`)
})

t.Run("Create package.json", func(t *testing.T) {

b, clean := newTestBuilder(t, "ignoreImports=true")
defer clean()

b.WithSourceFile("package.json", `{
"name": "mypack",
"version": "1.2.3",
"scripts": {},
"dependencies": {
"nonon": "error"
}
}`)

b.WithSourceFile("package.hugo.json", `{
"name": "mypack",
"version": "1.2.3",
"scripts": {},
"dependencies": {
"foo": "1.2.3"
},
"devDependencies": {
"postcss-cli": "7.8.0",
"tailwindcss": "1.8.0"
}
}`)

b.Build(BuildCfg{})
b.Assert(npm.Pack(b.H.BaseFs.SourceFs, b.H.BaseFs.Root.Dirs), qt.IsNil)

b.AssertFileContentFn("package.json", func(s string) bool {
return s == `{
"dependencies": {
"foo": "1.2.3",
"react-dom": "^16.13.1"
},
"devDependencies": {
"@babel/cli": "7.8.4",
"@babel/core": "7.9.0",
"@babel/preset-env": "7.9.5",
"postcss-cli": "7.8.0",
"tailwindcss": "1.8.0"
},
"name": "mypack",
"scripts": {},
"version": "1.2.3"
}`
})
})

}

// TODO(bep) this fails when testmodBuilder is also building ...
Expand Down
Loading

0 comments on commit 215226e

Please sign in to comment.