typescript_tools/
monorepo_manifest.rs1use std::collections::HashMap;
2use std::fmt::Display;
3use std::path::{Path, PathBuf};
4
5use globwalk::{FileType, GlobWalkerBuilder};
6use pariter::IteratorExt;
7use serde::Deserialize;
8
9use crate::configuration_file::ConfigurationFile;
10use crate::io::{read_json_from_file, FromFileError};
11use crate::package_manifest::PackageManifest;
12use crate::types::{Directory, PackageName};
13
14#[derive(Debug, Deserialize)]
15struct PackageManifestGlob(String);
16
17#[derive(Debug, Clone, Copy)]
18pub enum MonorepoKind {
19 Lerna,
20 Workspace,
21}
22
23impl Into<&'static str> for MonorepoKind {
24 fn into(self) -> &'static str {
25 match self {
26 MonorepoKind::Lerna => "lerna",
27 MonorepoKind::Workspace => "workspace",
28 }
29 }
30}
31
32#[derive(Debug, Deserialize)]
33struct LernaManifest {
34 packages: Vec<PackageManifestGlob>,
35}
36
37#[derive(Debug, Deserialize)]
38struct WorkspaceManifest {
39 workspaces: Vec<PackageManifestGlob>,
40}
41
42#[derive(Debug)]
43pub struct MonorepoManifest {
44 pub kind: MonorepoKind,
45 pub root: Directory,
46 globs: Vec<PackageManifestGlob>,
47}
48
49#[derive(Debug)]
50#[non_exhaustive]
51pub struct GlobError {
52 pub kind: GlobErrorKind,
53}
54
55impl Display for GlobError {
56 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 write!(f, "unable to enumerate monorepo packages")
58 }
59}
60
61impl std::error::Error for GlobError {
62 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
63 Some(&self.kind)
64 }
65}
66
67impl From<GlobErrorKind> for GlobError {
68 fn from(kind: GlobErrorKind) -> Self {
69 Self { kind }
70 }
71}
72
73#[derive(Debug)]
74pub enum GlobErrorKind {
75 #[non_exhaustive]
76 GlobNotValidUtf8(PathBuf),
77 #[non_exhaustive]
78 GlobWalkBuilderError(globwalk::GlobError),
79}
80
81impl Display for GlobErrorKind {
82 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83 match self {
84 GlobErrorKind::GlobNotValidUtf8(glob) => {
85 write!(f, "glob cannot be expressed in UTF-8: {:?}", glob)
86 }
87 GlobErrorKind::GlobWalkBuilderError(_) => {
88 write!(f, "unable to build glob walker")
89 }
90 }
91 }
92}
93
94impl std::error::Error for GlobErrorKind {
95 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
96 match &self {
97 GlobErrorKind::GlobNotValidUtf8(_) => None,
98 GlobErrorKind::GlobWalkBuilderError(err) => Some(err),
99 }
100 }
101}
102
103#[derive(Debug)]
104#[non_exhaustive]
105pub struct WalkError {
106 pub kind: WalkErrorKind,
107}
108
109impl Display for WalkError {
110 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111 write!(f, "unable to enumerate monorepo packages")
112 }
113}
114
115impl std::error::Error for WalkError {
116 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
117 Some(&self.kind)
118 }
119}
120
121impl From<globwalk::WalkError> for WalkError {
122 fn from(err: globwalk::WalkError) -> Self {
123 Self {
124 kind: WalkErrorKind::GlobWalkError(err),
125 }
126 }
127}
128
129impl From<WalkErrorKind> for WalkError {
130 fn from(kind: WalkErrorKind) -> Self {
131 Self { kind }
132 }
133}
134
135#[derive(Debug)]
136pub enum WalkErrorKind {
137 #[non_exhaustive]
138 GlobWalkError(globwalk::WalkError),
139 #[non_exhaustive]
140 FromFile(FromFileError),
141 #[non_exhaustive]
142 PackageInMonorepoRoot(PathBuf),
143}
144
145impl Display for WalkErrorKind {
146 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147 match self {
148 WalkErrorKind::FromFile(_) => {
149 write!(f, "unable to reading file")
150 }
151 WalkErrorKind::GlobWalkError(_) => {
152 write!(f, "error walking directory tree")
153 }
154 WalkErrorKind::PackageInMonorepoRoot(path) => {
155 write!(f, "package in monorepo root: {:?}", path)
156 }
157 }
158 }
159}
160
161impl std::error::Error for WalkErrorKind {
162 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
163 match &self {
164 WalkErrorKind::FromFile(err) => err.source(),
165 WalkErrorKind::GlobWalkError(err) => Some(err),
166 WalkErrorKind::PackageInMonorepoRoot(_) => None,
167 }
168 }
169}
170
171#[derive(Debug)]
172pub enum EnumeratePackageManifestsError {
173 #[non_exhaustive]
174 GlobError(GlobError),
175 #[non_exhaustive]
176 WalkError(WalkError),
177}
178
179impl Display for EnumeratePackageManifestsError {
180 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
181 write!(f, "unable to enumerate monorepo packages")
182 }
183}
184
185impl std::error::Error for EnumeratePackageManifestsError {
186 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
187 match &self {
188 EnumeratePackageManifestsError::GlobError(err) => Some(err),
189 EnumeratePackageManifestsError::WalkError(err) => Some(err),
190 }
191 }
192}
193
194impl From<GlobError> for EnumeratePackageManifestsError {
195 fn from(err: GlobError) -> Self {
196 Self::GlobError(err)
197 }
198}
199
200impl From<WalkError> for EnumeratePackageManifestsError {
201 fn from(err: WalkError) -> Self {
202 Self::WalkError(err)
203 }
204}
205
206fn get_internal_package_manifests(
207 monorepo_root: &Directory,
208 package_globs: &[PackageManifestGlob],
209) -> Result<impl Iterator<Item = Result<PackageManifest, WalkError>>, GlobError> {
210 let mut package_manifests: Vec<String> = package_globs
211 .iter()
212 .map(|package_manifest_glob| {
213 let glob = Path::new(&package_manifest_glob.0).join("package.json");
214 glob.to_str().map(ToOwned::to_owned).ok_or(GlobError {
215 kind: GlobErrorKind::GlobNotValidUtf8(glob),
216 })
217 })
218 .collect::<Result<_, _>>()?;
219
220 package_manifests.push(String::from("!node_modules/"));
222
223 let monorepo_root = monorepo_root.to_owned();
225
226 let package_manifests_iter =
229 GlobWalkerBuilder::from_patterns(&monorepo_root, &package_manifests)
230 .file_type(FileType::FILE)
231 .min_depth(1)
232 .build()
233 .map_err(|err| GlobError {
234 kind: GlobErrorKind::GlobWalkBuilderError(err),
235 })?
236 .parallel_map_custom(
237 |options| options.threads(32),
238 move |dir_entry| -> Result<PackageManifest, WalkError> {
239 let dir_entry = dir_entry?;
240 let path = dir_entry.path();
241 let manifest = PackageManifest::from_directory(
242 &monorepo_root,
243 Directory::unchecked_from_path(
244 path.parent()
245 .ok_or_else(|| {
246 WalkErrorKind::PackageInMonorepoRoot(path.to_owned())
247 })?
248 .strip_prefix(&monorepo_root)
249 .expect("expected all files to be children of monorepo root"),
250 ),
251 )
252 .map_err(WalkErrorKind::FromFile)?;
253 Ok(manifest)
254 },
255 );
256
257 Ok(package_manifests_iter)
258}
259
260impl MonorepoManifest {
261 const LERNA_MANIFEST_FILENAME: &'static str = "lerna.json";
262 const PACKAGE_MANIFEST_FILENAME: &'static str = "package.json";
263
264 fn from_lerna_manifest(root: &Path) -> Result<MonorepoManifest, FromFileError> {
265 let filename = root.join(Self::LERNA_MANIFEST_FILENAME);
266 let lerna_manifest: LernaManifest = read_json_from_file(&filename)?;
267 Ok(MonorepoManifest {
268 kind: MonorepoKind::Lerna,
269 root: Directory::unchecked_from_path(root),
270 globs: lerna_manifest.packages,
271 })
272 }
273
274 fn from_package_manifest(root: &Path) -> Result<MonorepoManifest, FromFileError> {
275 let filename = root.join(Self::PACKAGE_MANIFEST_FILENAME);
276 let package_manifest: WorkspaceManifest = read_json_from_file(&filename)?;
277 Ok(MonorepoManifest {
278 kind: MonorepoKind::Workspace,
279 root: Directory::unchecked_from_path(root),
280 globs: package_manifest.workspaces,
281 })
282 }
283
284 pub fn from_directory<P>(root: P) -> Result<MonorepoManifest, FromFileError>
285 where
286 P: AsRef<Path>,
287 {
288 MonorepoManifest::from_lerna_manifest(root.as_ref())
289 .or_else(|_| MonorepoManifest::from_package_manifest(root.as_ref()))
290 }
291
292 pub fn package_manifests_by_package_name(
293 &self,
294 ) -> Result<HashMap<PackageName, PackageManifest>, EnumeratePackageManifestsError> {
295 let map = get_internal_package_manifests(&self.root, &self.globs)?
296 .map(|maybe_manifest| -> Result<_, WalkError> {
297 let manifest = maybe_manifest?;
298 Ok((manifest.contents.name.to_owned(), manifest))
299 })
300 .collect::<Result<_, _>>()?;
301 Ok(map)
302 }
303
304 pub fn internal_package_manifests(
305 &self,
306 ) -> Result<impl Iterator<Item = Result<PackageManifest, WalkError>>, GlobError> {
307 get_internal_package_manifests(&self.root, &self.globs)
308 }
309}