aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/cli/internal/fs/package_json.go
diff options
context:
space:
mode:
Diffstat (limited to 'cli/internal/fs/package_json.go')
-rw-r--r--cli/internal/fs/package_json.go142
1 files changed, 142 insertions, 0 deletions
diff --git a/cli/internal/fs/package_json.go b/cli/internal/fs/package_json.go
new file mode 100644
index 0000000..883f7a4
--- /dev/null
+++ b/cli/internal/fs/package_json.go
@@ -0,0 +1,142 @@
+package fs
+
+import (
+ "bytes"
+ "encoding/json"
+ "sync"
+
+ "github.com/vercel/turbo/cli/internal/lockfile"
+ "github.com/vercel/turbo/cli/internal/turbopath"
+)
+
+// PackageJSON represents NodeJS package.json
+type PackageJSON struct {
+ Name string `json:"name"`
+ Version string `json:"version"`
+ Scripts map[string]string `json:"scripts"`
+ Dependencies map[string]string `json:"dependencies"`
+ DevDependencies map[string]string `json:"devDependencies"`
+ OptionalDependencies map[string]string `json:"optionalDependencies"`
+ PeerDependencies map[string]string `json:"peerDependencies"`
+ PackageManager string `json:"packageManager"`
+ Os []string `json:"os"`
+ Workspaces Workspaces `json:"workspaces"`
+ Private bool `json:"private"`
+ // Exact JSON object stored in package.json including unknown fields
+ // During marshalling struct fields will take priority over raw fields
+ RawJSON map[string]interface{} `json:"-"`
+
+ // relative path from repo root to the package.json file
+ PackageJSONPath turbopath.AnchoredSystemPath `json:"-"`
+ // relative path from repo root to the package
+ Dir turbopath.AnchoredSystemPath `json:"-"`
+ InternalDeps []string `json:"-"`
+ UnresolvedExternalDeps map[string]string `json:"-"`
+ TransitiveDeps []lockfile.Package `json:"-"`
+ LegacyTurboConfig *TurboJSON `json:"turbo"`
+ Mu sync.Mutex `json:"-"`
+ ExternalDepsHash string `json:"-"`
+}
+
+type Workspaces []string
+
+type WorkspacesAlt struct {
+ Packages []string `json:"packages,omitempty"`
+}
+
+func (r *Workspaces) UnmarshalJSON(data []byte) error {
+ var tmp = &WorkspacesAlt{}
+ if err := json.Unmarshal(data, tmp); err == nil {
+ *r = Workspaces(tmp.Packages)
+ return nil
+ }
+ var tempstr = []string{}
+ if err := json.Unmarshal(data, &tempstr); err != nil {
+ return err
+ }
+ *r = tempstr
+ return nil
+}
+
+// ReadPackageJSON returns a struct of package.json
+func ReadPackageJSON(path turbopath.AbsoluteSystemPath) (*PackageJSON, error) {
+ b, err := path.ReadFile()
+ if err != nil {
+ return nil, err
+ }
+ return UnmarshalPackageJSON(b)
+}
+
+// UnmarshalPackageJSON decodes a byte slice into a PackageJSON struct
+func UnmarshalPackageJSON(data []byte) (*PackageJSON, error) {
+ var rawJSON map[string]interface{}
+ if err := json.Unmarshal(data, &rawJSON); err != nil {
+ return nil, err
+ }
+
+ pkgJSON := &PackageJSON{}
+ if err := json.Unmarshal(data, &pkgJSON); err != nil {
+ return nil, err
+ }
+ pkgJSON.RawJSON = rawJSON
+
+ return pkgJSON, nil
+}
+
+// MarshalPackageJSON Serialize PackageJSON to a slice of bytes
+func MarshalPackageJSON(pkgJSON *PackageJSON) ([]byte, error) {
+ structuredContent, err := json.Marshal(pkgJSON)
+ if err != nil {
+ return nil, err
+ }
+ var structuredFields map[string]interface{}
+ if err := json.Unmarshal(structuredContent, &structuredFields); err != nil {
+ return nil, err
+ }
+
+ fieldsToSerialize := make(map[string]interface{}, len(pkgJSON.RawJSON))
+
+ // copy pkgJSON.RawJSON
+ for key, value := range pkgJSON.RawJSON {
+ fieldsToSerialize[key] = value
+ }
+
+ for key, value := range structuredFields {
+ if isEmpty(value) {
+ delete(fieldsToSerialize, key)
+ } else {
+ fieldsToSerialize[key] = value
+ }
+ }
+
+ var b bytes.Buffer
+ encoder := json.NewEncoder(&b)
+ encoder.SetEscapeHTML(false)
+ encoder.SetIndent("", " ")
+ if err := encoder.Encode(fieldsToSerialize); err != nil {
+ return nil, err
+ }
+
+ return b.Bytes(), nil
+}
+
+func isEmpty(value interface{}) bool {
+ if value == nil {
+ return true
+ }
+ switch s := value.(type) {
+ case string:
+ return s == ""
+ case bool:
+ return !s
+ case []string:
+ return len(s) == 0
+ case map[string]interface{}:
+ return len(s) == 0
+ case Workspaces:
+ return len(s) == 0
+ default:
+ // Assume any unknown types aren't empty
+ return false
+ }
+}