typescript_tools/
package_manifest.rs1use std::collections::{HashMap, HashSet, VecDeque};
2use std::path::PathBuf;
3
4use serde::{Deserialize, Serialize};
5
6use crate::configuration_file::ConfigurationFile;
7use crate::io::{read_json_from_file, FromFileError};
8use crate::types::{Directory, PackageName};
9
10#[derive(Serialize, Deserialize, Clone, Debug)]
11#[serde(rename_all = "camelCase")]
12pub struct PackageManifestFile {
13 pub name: PackageName,
14 pub version: String,
15 #[serde(flatten)]
16 pub extra_fields: serde_json::Map<String, serde_json::Value>,
17}
18
19#[derive(Clone, Debug)]
20pub struct PackageManifest {
21 relative_directory: Directory,
22 pub contents: PackageManifestFile,
23}
24
25#[derive(Debug)]
26pub(crate) struct DependencyGroup;
27
28impl DependencyGroup {
29 pub(crate) const VALUES: [&str; 4] = [
30 "dependencies",
31 "devDependencies",
32 "optionalDependencies",
33 "peerDependencies",
34 ];
35}
36
37impl ConfigurationFile for PackageManifest {
38 type Contents = PackageManifestFile;
39
40 const FILENAME: &'static str = "package.json";
41
42 fn from_directory(
43 monorepo_root: &Directory,
44 relative_directory: Directory,
45 ) -> Result<Self, FromFileError> {
46 let filename = monorepo_root.join(&relative_directory).join(Self::FILENAME);
47 let manifest_contents: PackageManifestFile = read_json_from_file(&filename)?;
48 Ok(PackageManifest {
49 relative_directory,
50 contents: manifest_contents,
51 })
52 }
53
54 fn directory(&self) -> &Directory {
55 &self.relative_directory
56 }
57
58 fn path(&self) -> PathBuf {
59 self.relative_directory.join(Self::FILENAME)
60 }
61
62 fn contents(&self) -> &PackageManifestFile {
63 &self.contents
64 }
65}
66
67impl AsRef<PackageManifest> for PackageManifest {
68 fn as_ref(&self) -> &Self {
69 self
70 }
71}
72
73impl PackageManifest {
74 pub(crate) fn get_dependency_version<S>(&self, dependency: S) -> Option<String>
77 where
78 S: AsRef<str>,
79 {
80 DependencyGroup::VALUES
81 .iter()
82 .filter_map(|dependency_group| {
84 self.contents
85 .extra_fields
86 .get(*dependency_group)?
87 .as_object()
88 })
89 .filter_map(|dependency_group_value| {
91 dependency_group_value
92 .get(dependency.as_ref())
93 .and_then(|version_value| version_value.as_str().map(|a| a.to_owned()))
94 })
95 .take(1)
96 .next()
97 }
98
99 pub(crate) fn dependencies_iter(
100 &self,
101 ) -> impl Iterator<Item = (PackageName, &serde_json::Value)> {
102 DependencyGroup::VALUES
103 .iter()
104 .filter_map(|dependency_group| {
105 self.contents
106 .extra_fields
107 .get(*dependency_group)?
108 .as_object()
109 })
110 .flat_map(|object| object.iter())
111 .map(|(package_name, package_version)| {
112 (PackageName::from(package_name), package_version)
113 })
114 }
115
116 pub(crate) fn internal_dependencies_iter<'a>(
117 &'a self,
118 package_manifests_by_package_name: &'a HashMap<PackageName, PackageManifest>,
119 ) -> impl Iterator<Item = &'a PackageManifest> {
120 DependencyGroup::VALUES
121 .iter()
122 .filter_map(|dependency_group| {
124 self.contents
125 .extra_fields
126 .get(*dependency_group)?
127 .as_object()
128 })
129 .flat_map(|dependency_group_value| dependency_group_value.keys())
131 .filter_map(|package_name| {
133 package_manifests_by_package_name.get(&PackageName::from(package_name))
134 })
135 }
136
137 pub(crate) fn transitive_internal_dependency_package_names_exclusive<'a>(
138 &'a self,
139 package_manifest_by_package_name: &'a HashMap<PackageName, PackageManifest>,
140 ) -> impl Iterator<Item = &'a PackageManifest> {
141 let mut seen_package_names = HashSet::new();
143 let mut internal_dependencies = HashSet::new();
144 let mut to_visit_package_manifests = VecDeque::new();
145
146 to_visit_package_manifests.push_back(self);
147
148 while let Some(current_manifest) = to_visit_package_manifests.pop_front() {
149 seen_package_names.insert(¤t_manifest.contents.name);
150
151 for dependency in
152 current_manifest.internal_dependencies_iter(package_manifest_by_package_name)
153 {
154 internal_dependencies.insert(&dependency.contents.name);
155 if !seen_package_names.contains(&dependency.contents.name) {
156 to_visit_package_manifests.push_back(dependency);
157 }
158 }
159 }
160
161 internal_dependencies
162 .into_iter()
163 .map(|dependency_package_name| {
164 package_manifest_by_package_name
165 .get(dependency_package_name)
166 .unwrap()
167 })
168 }
169
170 pub(crate) fn npm_pack_file_basename(&self) -> String {
173 format!(
174 "{}-{}.tgz",
175 self.contents
176 .name
177 .as_str()
178 .trim_start_matches('@')
179 .replace('/', "-"),
180 &self.contents.version,
181 )
182 }
183
184 pub(crate) fn unscoped_package_name(&self) -> &str {
185 match &self.contents.name.as_str().rsplit_once('/') {
186 Some((_scope, name)) => name,
187 None => &self.contents.name.as_str(),
188 }
189 }
190}