| skipped 11 lines |
12 | 12 | | use std::process::{exit, Command}; |
13 | 13 | | |
14 | 14 | | // Get PATH env and join it with bin_dir |
15 | | - | fn get_path_env(bin_dir: &str) -> String { |
| 15 | + | fn get_path_env(bin_dirs: Vec<PathBuf>) -> String { |
16 | 16 | | let mut path = env::var("PATH").unwrap_or_default(); |
17 | | - | path.push_str(":"); |
18 | | - | path.push_str(bin_dir); |
| 17 | + | for dir in bin_dirs { |
| 18 | + | path.push_str(":"); |
| 19 | + | path.push_str(dir.to_str().unwrap()); |
| 20 | + | } |
19 | 21 | | path |
20 | 22 | | } |
21 | 23 | | |
22 | 24 | | // A function to find the closest file |
23 | 25 | | // Starting from current directory |
24 | 26 | | // Recusively until it finds the file or reach root directory `/` |
25 | | - | fn find_closest_file(_current_dir: &PathBuf, name: &str) -> Option<PathBuf> { |
26 | | - | let mut closest_file = None; |
| 27 | + | fn find_closest_files(_current_dir: &PathBuf, name: &str, stop_on_first: bool) -> Vec<PathBuf> { |
| 28 | + | let mut closest_file: Vec<PathBuf> = Vec::new(); |
27 | 29 | | let stop_dir = "/".to_string(); |
28 | 30 | | let mut current_dir = _current_dir.clone(); |
29 | 31 | | |
30 | 32 | | loop { |
31 | 33 | | let path = current_dir.join(name); |
32 | 34 | | if path.exists() { |
33 | | - | closest_file = Some(PathBuf::from(path.to_str().unwrap())); |
34 | | - | break; |
| 35 | + | closest_file.push(path); |
| 36 | + | if stop_on_first { |
| 37 | + | break; |
| 38 | + | } |
35 | 39 | | } |
36 | 40 | | |
37 | 41 | | if current_dir.to_str().unwrap() == stop_dir { |
| skipped 29 lines |
67 | 71 | | exit(status.code().unwrap_or(1)); |
68 | 72 | | } |
69 | 73 | | |
70 | | - | pub fn dum(args: &args::AppArgs) { |
71 | | - | let pkg_path = find_closest_file(&args.change_dir, "package.json").expect("no package.json"); |
| 74 | + | fn resolve_bin_path(bin_name: &str, dirs: &Vec<PathBuf>) -> Option<PathBuf> { |
| 75 | + | for dir in dirs { |
| 76 | + | let path = dir.join(bin_name); |
| 77 | + | if path.exists() { |
| 78 | + | return Some(path); |
| 79 | + | } |
| 80 | + | } |
| 81 | + | |
| 82 | + | None |
| 83 | + | } |
| 84 | + | |
| 85 | + | pub fn dum(app_args: &args::AppArgs) { |
| 86 | + | let pkg_paths = find_closest_files(&app_args.change_dir, "package.json", true); |
| 87 | + | let pkg_path = if pkg_paths.is_empty() { |
| 88 | + | println!("No package.json found"); |
| 89 | + | exit(1); |
| 90 | + | } else { |
| 91 | + | pkg_paths[0].clone() |
| 92 | + | }; |
| 93 | + | |
72 | 94 | | // The current_dir to execute npm scripts |
73 | 95 | | let execute_dir = PathBuf::from(pkg_path.parent().unwrap()); |
74 | | - | // bin_dir is the dirname of pkg_data followed by node_modules/.bin |
75 | | - | let bin_dir = PathBuf::from(execute_dir.join("node_modules").join(".bin")); |
| 96 | + | |
| 97 | + | let node_modules_dirs = find_closest_files(&app_args.change_dir, "node_modules", false); |
| 98 | + | let bin_dirs = node_modules_dirs |
| 99 | + | .iter() |
| 100 | + | .map(|dir| dir.join(".bin")) |
| 101 | + | .collect::<Vec<PathBuf>>(); |
76 | 102 | | |
77 | 103 | | let contents = read_to_string(pkg_path).expect("failed to read package.json"); |
78 | 104 | | let v: Value = serde_json::from_str(&contents).expect("failed to parse package.json"); |
79 | 105 | | |
80 | | - | if args.script_name.is_empty() { |
| 106 | + | if app_args.command == "run" && app_args.script_name.is_empty() { |
81 | 107 | | if let Some(scripts) = v["scripts"].as_object() { |
82 | 108 | | println!("\nAvailable scripts:\n"); |
83 | 109 | | for (name, value) in scripts { |
| skipped 6 lines |
90 | 116 | | return; |
91 | 117 | | } |
92 | 118 | | |
| 119 | + | if app_args.script_name.is_empty() { |
| 120 | + | println!("No script name specified.\n"); |
| 121 | + | println!("{}", args::get_help()); |
| 122 | + | return; |
| 123 | + | } |
| 124 | + | |
93 | 125 | | // Run npm install if the script_name is "install" |
94 | | - | if ["install", "add", "remove"].contains(&args.script_name.as_str()) { |
| 126 | + | if ["install", "add", "remove"].contains(&app_args.script_name.as_str()) { |
95 | 127 | | let pm = install::guess_package_manager(&execute_dir); |
96 | 128 | | |
97 | 129 | | if pm.is_none() { |
| skipped 2 lines |
100 | 132 | | } |
101 | 133 | | |
102 | 134 | | run_command( |
103 | | - | &[&pm.unwrap(), &args.script_name, &args.forwared], |
| 135 | + | &[&pm.unwrap(), &app_args.script_name, &app_args.forwared], |
104 | 136 | | &RunOptions { |
105 | 137 | | current_dir: execute_dir, |
106 | 138 | | envs: HashMap::new(), |
| skipped 2 lines |
109 | 141 | | return; |
110 | 142 | | } |
111 | 143 | | |
112 | | - | let result = v |
113 | | - | .get("scripts") |
114 | | - | .and_then(|scripts| match scripts.get(&args.script_name) { |
115 | | - | Some(script) => { |
116 | | - | println!("> {}", args.script_name); |
117 | | - | println!("> {}{}", script.as_str().unwrap(), args.forwared); |
118 | | - | script.as_str().map(|script| script.to_string()) |
119 | | - | } |
120 | | - | None => { |
121 | | - | let bin_file = bin_dir.join(&args.script_name); |
122 | | - | if bin_file.exists() { |
123 | | - | println!("> {}", bin_file.display()); |
124 | | - | Some(bin_file.to_string_lossy().to_string()) |
125 | | - | } else { |
126 | | - | None |
127 | | - | } |
128 | | - | } |
| 144 | + | let npm_script = v.get("scripts").and_then(|scripts| { |
| 145 | + | scripts.as_object().and_then(|scripts| { |
| 146 | + | scripts |
| 147 | + | .get(app_args.script_name.as_str()) |
| 148 | + | .and_then(|script| { |
| 149 | + | let script = script.as_str().map(|script| script.to_string()); |
| 150 | + | Some(script.unwrap_or_default()) |
| 151 | + | }) |
129 | 152 | | }) |
130 | | - | .map(|script| { |
131 | | - | let envs = |
132 | | - | HashMap::from([("PATH".to_string(), get_path_env(&bin_dir.to_str().unwrap()))]); |
| 153 | + | }); |
133 | 154 | | |
134 | | - | run_command( |
135 | | - | &[&script, &args.forwared], |
136 | | - | &RunOptions { |
137 | | - | current_dir: execute_dir, |
138 | | - | envs, |
139 | | - | }, |
140 | | - | ); |
141 | | - | }); |
| 155 | + | if npm_script.is_some() { |
| 156 | + | let script = npm_script.unwrap(); |
| 157 | + | println!("> {}", app_args.script_name); |
| 158 | + | println!("> {}{}", script, app_args.forwared); |
| 159 | + | let envs = HashMap::from([("PATH".to_string(), get_path_env(bin_dirs))]); |
| 160 | + | run_command( |
| 161 | + | &[&script, &app_args.forwared], |
| 162 | + | &RunOptions { |
| 163 | + | current_dir: execute_dir, |
| 164 | + | envs, |
| 165 | + | }, |
| 166 | + | ); |
| 167 | + | return; |
| 168 | + | } |
142 | 169 | | |
143 | | - | if result.is_none() { |
144 | | - | eprintln!("Error: script not found."); |
145 | | - | std::process::exit(1); |
| 170 | + | let resolved_bin = resolve_bin_path(app_args.script_name.as_str(), &bin_dirs); |
| 171 | + | if resolved_bin.is_some() { |
| 172 | + | let bin_path = resolved_bin.unwrap(); |
| 173 | + | println!("> {}", app_args.script_name); |
| 174 | + | println!("> {}{}", bin_path.to_str().unwrap(), app_args.forwared); |
| 175 | + | let envs = HashMap::from([("PATH".to_string(), get_path_env(bin_dirs))]); |
| 176 | + | run_command( |
| 177 | + | &[bin_path.to_str().unwrap(), &app_args.forwared], |
| 178 | + | &RunOptions { |
| 179 | + | current_dir: execute_dir, |
| 180 | + | envs, |
| 181 | + | }, |
| 182 | + | ); |
| 183 | + | return; |
146 | 184 | | } |
| 185 | + | |
| 186 | + | println!("No script found."); |
| 187 | + | println!("To see a list of scripts, run `dum run`"); |
| 188 | + | exit(1); |
147 | 189 | | } |
148 | 190 | | |