Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/gohugoio/hugo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2020-09-09 23:31:43 +0300
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2020-09-13 21:55:29 +0300
commit85ba9bfffba9bfd0b095cb766f72700d4c211e31 (patch)
tree43b66efaafe4cb804234ca7273873ab949305799 /modules
parent9df60b62f9c4e36a269f0c6e9a69bee9dc691031 (diff)
Add "hugo mod npm pack"
This commit also introduces a convention where these common JS config files, including `package.hugo.json`, gets mounted into: ``` assets/_jsconfig ´`` These files mapped to their real filename will be added to the environment when running PostCSS, Babel etc., so you can do `process.env.HUGO_FILE_TAILWIND_CONFIG_JS` to resolve the real filename. But do note that `assets` is a composite/union filesystem, so if your config file is not meant to be overridden, name them something specific. This commit also adds adds `workDir/node_modules` to `NODE_PATH` and `HUGO_WORKDIR` to the env when running the JS tools above. Fixes #7644 Fixes #7656 Fixes #7675
Diffstat (limited to 'modules')
-rw-r--r--modules/collect.go43
-rw-r--r--modules/config.go13
-rw-r--r--modules/npm/package_builder.go230
-rw-r--r--modules/npm/package_builder_test.go95
4 files changed, 380 insertions, 1 deletions
diff --git a/modules/collect.go b/modules/collect.go
index b82d395fd..8959572d6 100644
--- a/modules/collect.go
+++ b/modules/collect.go
@@ -18,6 +18,7 @@ import (
"fmt"
"os"
"path/filepath"
+ "regexp"
"strings"
"time"
@@ -382,6 +383,11 @@ func (c *collector) applyMounts(moduleImport Import, mod *moduleAdapter) error {
return err
}
+ mounts, err = c.mountCommonJSConfig(mod, mounts)
+ if err != nil {
+ return err
+ }
+
mod.mounts = mounts
return nil
}
@@ -549,6 +555,43 @@ func (c *collector) loadModules() error {
return nil
}
+// Matches postcss.config.js etc.
+var commonJSConfigs = regexp.MustCompile(`(babel|postcss|tailwind)\.config\.js`)
+
+func (c *collector) mountCommonJSConfig(owner *moduleAdapter, mounts []Mount) ([]Mount, error) {
+ for _, m := range mounts {
+ if strings.HasPrefix(m.Target, files.JsConfigFolderMountPrefix) {
+ // This follows the convention of the other component types (assets, content, etc.),
+ // if one or more is specificed by the user, we skip the defaults.
+ // These mounts were added to Hugo in 0.75.
+ return mounts, nil
+ }
+ }
+
+ // Mount the common JS config files.
+ fis, err := afero.ReadDir(c.fs, owner.Dir())
+ if err != nil {
+ return mounts, err
+ }
+
+ for _, fi := range fis {
+ n := fi.Name()
+
+ should := n == files.FilenamePackageHugoJSON || n == files.FilenamePackageJSON
+ should = should || commonJSConfigs.MatchString(n)
+
+ if should {
+ mounts = append(mounts, Mount{
+ Source: n,
+ Target: filepath.Join(files.ComponentFolderAssets, files.FolderJSConfig, n),
+ })
+ }
+
+ }
+
+ return mounts, nil
+}
+
func (c *collector) normalizeMounts(owner *moduleAdapter, mounts []Mount) ([]Mount, error) {
var out []Mount
dir := owner.Dir()
diff --git a/modules/config.go b/modules/config.go
index 1964479f4..e0a0ea060 100644
--- a/modules/config.go
+++ b/modules/config.go
@@ -56,7 +56,9 @@ func ApplyProjectConfigDefaults(cfg config.Provider, mod Module) error {
// the basic level.
componentsConfigured := make(map[string]bool)
for _, mnt := range moda.mounts {
- componentsConfigured[mnt.Component()] = true
+ if !strings.HasPrefix(mnt.Target, files.JsConfigFolderMountPrefix) {
+ componentsConfigured[mnt.Component()] = true
+ }
}
type dirKeyComponent struct {
@@ -318,12 +320,21 @@ type Mount struct {
Target string // relative target path, e.g. "assets/bootstrap/scss"
Lang string // any language code associated with this mount.
+
}
func (m Mount) Component() string {
return strings.Split(m.Target, fileSeparator)[0]
}
+func (m Mount) ComponentAndName() (string, string) {
+ k := strings.Index(m.Target, fileSeparator)
+ if k == -1 {
+ return m.Target, ""
+ }
+ return m.Target[:k], m.Target[k+1:]
+}
+
func getStaticDirs(cfg config.Provider) []string {
var staticDirs []string
for i := -1; i <= 10; i++ {
diff --git a/modules/npm/package_builder.go b/modules/npm/package_builder.go
new file mode 100644
index 000000000..23aac7246
--- /dev/null
+++ b/modules/npm/package_builder.go
@@ -0,0 +1,230 @@
+// 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 npm
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+
+ "github.com/gohugoio/hugo/hugofs/files"
+
+ "github.com/pkg/errors"
+
+ "github.com/gohugoio/hugo/hugofs"
+ "github.com/spf13/afero"
+
+ "github.com/spf13/cast"
+
+ "github.com/gohugoio/hugo/helpers"
+)
+
+const (
+ dependenciesKey = "dependencies"
+ devDependenciesKey = "devDependencies"
+
+ packageJSONName = "package.json"
+
+ packageJSONTemplate = `{
+ "name": "%s",
+ "version": "%s"
+}`
+)
+
+func Pack(fs afero.Fs, fis []hugofs.FileMetaInfo) error {
+
+ var b *packageBuilder
+
+ // Have a package.hugo.json?
+ fi, err := fs.Stat(files.FilenamePackageHugoJSON)
+ if err != nil {
+ // Have a package.json?
+ fi, err = fs.Stat(packageJSONName)
+ if err != nil {
+ // Create one.
+ name := "project"
+ // Use the Hugo site's folder name as the default name.
+ // The owner can change it later.
+ rfi, err := fs.Stat("")
+ if err == nil {
+ name = rfi.Name()
+ }
+ packageJSONContent := fmt.Sprintf(packageJSONTemplate, name, "0.1.0")
+ if err = afero.WriteFile(fs, files.FilenamePackageHugoJSON, []byte(packageJSONContent), 0666); err != nil {
+ return err
+ }
+ fi, err = fs.Stat(files.FilenamePackageHugoJSON)
+ if err != nil {
+ return err
+ }
+ }
+ }
+
+ meta := fi.(hugofs.FileMetaInfo).Meta()
+ masterFilename := meta.Filename()
+ f, err := meta.Open()
+ if err != nil {
+ return errors.Wrap(err, "npm pack: failed to open package file")
+ }
+ b = newPackageBuilder(meta.Module(), f)
+ f.Close()
+
+ for _, fi := range fis {
+ if fi.IsDir() {
+ // We only care about the files in the root.
+ continue
+ }
+
+ if fi.Name() != files.FilenamePackageHugoJSON {
+ continue
+ }
+
+ meta := fi.(hugofs.FileMetaInfo).Meta()
+
+ if meta.Filename() == masterFilename {
+ continue
+ }
+
+ f, err := meta.Open()
+ if err != nil {
+ return errors.Wrap(err, "npm pack: failed to open package file")
+ }
+ b.Add(meta.Module(), f)
+ f.Close()
+ }
+
+ if b.Err() != nil {
+ return errors.Wrap(b.Err(), "npm pack: failed to build")
+ }
+
+ // Replace the dependencies in the original template with the merged set.
+ b.originalPackageJSON[dependenciesKey] = b.dependencies
+ b.originalPackageJSON[devDependenciesKey] = b.devDependencies
+ var commentsm map[string]interface{}
+ comments, found := b.originalPackageJSON["comments"]
+ if found {
+ commentsm = cast.ToStringMap(comments)
+ } else {
+ commentsm = make(map[string]interface{})
+ }
+ commentsm[dependenciesKey] = b.dependenciesComments
+ commentsm[devDependenciesKey] = b.devDependenciesComments
+ b.originalPackageJSON["comments"] = commentsm
+
+ // Write it out to the project package.json
+ packageJSONData, err := json.MarshalIndent(b.originalPackageJSON, "", " ")
+ if err != nil {
+ return errors.Wrap(err, "npm pack: failed to marshal JSON")
+ }
+
+ if err := afero.WriteFile(fs, packageJSONName, packageJSONData, 0666); err != nil {
+ return errors.Wrap(err, "npm pack: failed to write package.json")
+ }
+
+ return nil
+
+}
+
+func newPackageBuilder(source string, first io.Reader) *packageBuilder {
+ b := &packageBuilder{
+ devDependencies: make(map[string]interface{}),
+ devDependenciesComments: make(map[string]interface{}),
+ dependencies: make(map[string]interface{}),
+ dependenciesComments: make(map[string]interface{}),
+ }
+
+ m := b.unmarshal(first)
+ if b.err != nil {
+ return b
+ }
+
+ b.addm(source, m)
+ b.originalPackageJSON = m
+
+ return b
+}
+
+type packageBuilder struct {
+ err error
+
+ // The original package.hugo.json.
+ originalPackageJSON map[string]interface{}
+
+ devDependencies map[string]interface{}
+ devDependenciesComments map[string]interface{}
+ dependencies map[string]interface{}
+ dependenciesComments map[string]interface{}
+}
+
+func (b *packageBuilder) Add(source string, r io.Reader) *packageBuilder {
+ if b.err != nil {
+ return b
+ }
+
+ m := b.unmarshal(r)
+ if b.err != nil {
+ return b
+ }
+
+ b.addm(source, m)
+
+ return b
+}
+
+func (b *packageBuilder) addm(source string, m map[string]interface{}) {
+ if source == "" {
+ source = "project"
+ }
+
+ // The version selection is currently very simple.
+ // We may consider minimal version selection or something
+ // after testing this out.
+ //
+ // But for now, the first version string for a given dependency wins.
+ // These packages will be added by order of import (project, module1, module2...),
+ // so that should at least give the project control over the situation.
+ if devDeps, found := m[devDependenciesKey]; found {
+ mm := cast.ToStringMapString(devDeps)
+ for k, v := range mm {
+ if _, added := b.devDependencies[k]; !added {
+ b.devDependencies[k] = v
+ b.devDependenciesComments[k] = source
+ }
+ }
+ }
+
+ if deps, found := m[dependenciesKey]; found {
+ mm := cast.ToStringMapString(deps)
+ for k, v := range mm {
+ if _, added := b.dependencies[k]; !added {
+ b.dependencies[k] = v
+ b.dependenciesComments[k] = source
+ }
+ }
+ }
+
+}
+
+func (b *packageBuilder) unmarshal(r io.Reader) map[string]interface{} {
+ m := make(map[string]interface{})
+ err := json.Unmarshal(helpers.ReaderToBytes(r), &m)
+ if err != nil {
+ b.err = err
+ }
+ return m
+}
+
+func (b *packageBuilder) Err() error {
+ return b.err
+}
diff --git a/modules/npm/package_builder_test.go b/modules/npm/package_builder_test.go
new file mode 100644
index 000000000..510a04776
--- /dev/null
+++ b/modules/npm/package_builder_test.go
@@ -0,0 +1,95 @@
+// 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 npm
+
+import (
+ "strings"
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+)
+
+const templ = `{
+ "name": "foo",
+ "version": "0.1.1",
+ "scripts": {},
+ "dependencies": {
+ "react-dom": "1.1.1",
+ "tailwindcss": "1.2.0",
+ "@babel/cli": "7.8.4",
+ "@babel/core": "7.9.0",
+ "@babel/preset-env": "7.9.5"
+ },
+ "devDependencies": {
+ "postcss-cli": "7.1.0",
+ "tailwindcss": "1.2.0",
+ "@babel/cli": "7.8.4",
+ "@babel/core": "7.9.0",
+ "@babel/preset-env": "7.9.5"
+ }
+}`
+
+func TestPackageBuilder(t *testing.T) {
+ c := qt.New(t)
+
+ b := newPackageBuilder("", strings.NewReader(templ))
+ c.Assert(b.Err(), qt.IsNil)
+
+ b.Add("mymod", strings.NewReader(`{
+"dependencies": {
+ "react-dom": "9.1.1",
+ "add1": "1.1.1"
+},
+"devDependencies": {
+ "tailwindcss": "error",
+ "add2": "2.1.1"
+}
+}`))
+
+ b.Add("mymod", strings.NewReader(`{
+"dependencies": {
+ "react-dom": "error",
+ "add1": "error",
+ "add3": "3.1.1"
+},
+"devDependencies": {
+ "tailwindcss": "error",
+ "add2": "error",
+ "add4": "4.1.1"
+
+}
+}`))
+
+ c.Assert(b.Err(), qt.IsNil)
+
+ c.Assert(b.dependencies, qt.DeepEquals, map[string]interface{}{
+ "@babel/cli": "7.8.4",
+ "add1": "1.1.1",
+ "add3": "3.1.1",
+ "@babel/core": "7.9.0",
+ "@babel/preset-env": "7.9.5",
+ "react-dom": "1.1.1",
+ "tailwindcss": "1.2.0",
+ })
+
+ c.Assert(b.devDependencies, qt.DeepEquals, map[string]interface{}{
+ "tailwindcss": "1.2.0",
+ "@babel/cli": "7.8.4",
+ "@babel/core": "7.9.0",
+ "add2": "2.1.1",
+ "add4": "4.1.1",
+ "@babel/preset-env": "7.9.5",
+ "postcss-cli": "7.1.0",
+ })
+}