fallow_core/scripts/
resolve.rs1use std::path::Path;
4
5static BINARY_TO_PACKAGE: &[(&str, &str)] = &[
7 ("tsc", "typescript"),
8 ("tsserver", "typescript"),
9 ("ng", "@angular/cli"),
10 ("nuxi", "nuxt"),
11 ("run-s", "npm-run-all"),
12 ("run-p", "npm-run-all"),
13 ("run-s2", "npm-run-all2"),
14 ("run-p2", "npm-run-all2"),
15 ("sb", "storybook"),
16 ("biome", "@biomejs/biome"),
17 ("oxlint", "oxlint"),
18];
19
20#[must_use]
27pub fn resolve_binary_to_package(binary: &str, root: &Path) -> String {
28 if let Some(&(_, pkg)) = BINARY_TO_PACKAGE.iter().find(|(bin, _)| *bin == binary) {
30 return pkg.to_string();
31 }
32
33 let bin_link = root.join("node_modules/.bin").join(binary);
35 if let Ok(target) = std::fs::read_link(&bin_link)
36 && let Some(pkg_name) = extract_package_from_bin_path(&target)
37 {
38 return pkg_name;
39 }
40
41 binary.to_string()
43}
44
45pub fn extract_package_from_bin_path(target: &std::path::Path) -> Option<String> {
51 let target_str = target.to_string_lossy();
52 let parts: Vec<&str> = target_str.split('/').collect();
53
54 for (i, part) in parts.iter().enumerate() {
55 if *part == ".." {
56 continue;
57 }
58 if part.starts_with('@') && i + 1 < parts.len() {
60 return Some(format!("{}/{}", part, parts[i + 1]));
61 }
62 return Some(part.to_string());
64 }
65
66 None
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72
73 #[test]
76 fn tsserver_maps_to_typescript() {
77 let pkg = resolve_binary_to_package("tsserver", Path::new("/nonexistent"));
78 assert_eq!(pkg, "typescript");
79 }
80
81 #[test]
82 fn nuxi_maps_to_nuxt() {
83 let pkg = resolve_binary_to_package("nuxi", Path::new("/nonexistent"));
84 assert_eq!(pkg, "nuxt");
85 }
86
87 #[test]
88 fn run_p_maps_to_npm_run_all() {
89 let pkg = resolve_binary_to_package("run-p", Path::new("/nonexistent"));
90 assert_eq!(pkg, "npm-run-all");
91 }
92
93 #[test]
94 fn run_s2_maps_to_npm_run_all2() {
95 let pkg = resolve_binary_to_package("run-s2", Path::new("/nonexistent"));
96 assert_eq!(pkg, "npm-run-all2");
97 }
98
99 #[test]
100 fn run_p2_maps_to_npm_run_all2() {
101 let pkg = resolve_binary_to_package("run-p2", Path::new("/nonexistent"));
102 assert_eq!(pkg, "npm-run-all2");
103 }
104
105 #[test]
106 fn sb_maps_to_storybook() {
107 let pkg = resolve_binary_to_package("sb", Path::new("/nonexistent"));
108 assert_eq!(pkg, "storybook");
109 }
110
111 #[test]
112 fn oxlint_maps_to_oxlint() {
113 let pkg = resolve_binary_to_package("oxlint", Path::new("/nonexistent"));
114 assert_eq!(pkg, "oxlint");
115 }
116
117 #[test]
120 fn unknown_binary_returns_identity() {
121 let pkg = resolve_binary_to_package("some-random-tool", Path::new("/nonexistent"));
122 assert_eq!(pkg, "some-random-tool");
123 }
124
125 #[test]
126 fn jest_identity_without_symlink() {
127 let pkg = resolve_binary_to_package("jest", Path::new("/nonexistent"));
129 assert_eq!(pkg, "jest");
130 }
131
132 #[test]
133 fn eslint_identity_without_symlink() {
134 let pkg = resolve_binary_to_package("eslint", Path::new("/nonexistent"));
135 assert_eq!(pkg, "eslint");
136 }
137
138 #[test]
141 fn bin_path_simple_package() {
142 let path = std::path::Path::new("../eslint/bin/eslint.js");
143 assert_eq!(
144 extract_package_from_bin_path(path),
145 Some("eslint".to_string())
146 );
147 }
148
149 #[test]
150 fn bin_path_scoped_package() {
151 let path = std::path::Path::new("../@angular/cli/bin/ng");
152 assert_eq!(
153 extract_package_from_bin_path(path),
154 Some("@angular/cli".to_string())
155 );
156 }
157
158 #[test]
159 fn bin_path_deeply_nested() {
160 let path = std::path::Path::new("../../typescript/bin/tsc");
161 assert_eq!(
162 extract_package_from_bin_path(path),
163 Some("typescript".to_string())
164 );
165 }
166
167 #[test]
168 fn bin_path_no_parent_dots() {
169 let path = std::path::Path::new("webpack/bin/webpack.js");
170 assert_eq!(
171 extract_package_from_bin_path(path),
172 Some("webpack".to_string())
173 );
174 }
175
176 #[test]
177 fn bin_path_only_dots() {
178 let path = std::path::Path::new("../../..");
179 assert_eq!(extract_package_from_bin_path(path), None);
180 }
181
182 #[test]
183 fn bin_path_scoped_with_multiple_parents() {
184 let path = std::path::Path::new("../../../@biomejs/biome/bin/biome");
185 assert_eq!(
186 extract_package_from_bin_path(path),
187 Some("@biomejs/biome".to_string())
188 );
189 }
190}