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