summaryrefslogtreecommitdiffstats
path: root/modules/npm/package_builder.go
diff options
context:
space:
mode:
Diffstat (limited to 'modules/npm/package_builder.go')
-rw-r--r--modules/npm/package_builder.go230
1 files changed, 230 insertions, 0 deletions
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
+}