1use std::env::consts::EXE_EXTENSION;
4use std::ffi::OsStr;
5use std::path::Path;
6use std::process::Command;
7
8#[derive(Debug)]
9pub enum WindowsRunnable {
10 Executable,
12 PowerShell,
14 Command,
16 Batch,
18}
19
20impl WindowsRunnable {
21 fn all() -> &'static [Self] {
23 &[
24 Self::Executable,
25 Self::PowerShell,
26 Self::Command,
27 Self::Batch,
28 ]
29 }
30
31 fn to_extension(&self) -> &'static str {
33 match self {
34 Self::Executable => EXE_EXTENSION,
35 Self::PowerShell => "ps1",
36 Self::Command => "cmd",
37 Self::Batch => "bat",
38 }
39 }
40
41 fn from_extension(ext: &str) -> Option<Self> {
43 match ext {
44 EXE_EXTENSION => Some(Self::Executable),
45 "ps1" => Some(Self::PowerShell),
46 "cmd" => Some(Self::Command),
47 "bat" => Some(Self::Batch),
48 _ => None,
49 }
50 }
51
52 fn as_command(&self, runnable_path: &Path) -> Command {
54 match self {
55 Self::Executable => Command::new(runnable_path),
56 Self::PowerShell => {
57 let mut cmd = Command::new("powershell");
58 cmd.arg("-NoLogo").arg("-File").arg(runnable_path);
59 cmd
60 }
61 Self::Command | Self::Batch => {
62 let mut cmd = Command::new("cmd");
63 cmd.arg("/q").arg("/c").arg(runnable_path);
64 cmd
65 }
66 }
67 }
68
69 pub fn from_script_path(script_path: &Path, runnable_name: &OsStr) -> Command {
74 let script_path = script_path.join(runnable_name);
75
76 if let Some(script_type) = script_path
78 .extension()
79 .and_then(OsStr::to_str)
80 .and_then(Self::from_extension)
81 .filter(|_| script_path.is_file())
82 {
83 return script_type.as_command(&script_path);
84 }
85
86 Self::all()
89 .iter()
90 .map(|script_type| {
91 (
92 script_type,
93 script_path.with_added_extension(script_type.to_extension()),
94 )
95 })
96 .find(|(_, script_path)| script_path.is_file())
97 .map(|(script_type, script_path)| script_type.as_command(&script_path))
98 .unwrap_or_else(|| Command::new(runnable_name))
99 }
100}
101
102#[cfg(test)]
103mod tests {
104
105 #[cfg(target_os = "windows")]
106 use super::WindowsRunnable;
107 #[cfg(target_os = "windows")]
108 use fs_err as fs;
109 #[cfg(target_os = "windows")]
110 use std::ffi::OsStr;
111 #[cfg(target_os = "windows")]
112 use std::io;
113
114 #[cfg(target_os = "windows")]
116 fn create_test_environment() -> io::Result<tempfile::TempDir> {
117 let temp_dir = tempfile::tempdir()?;
118 let scripts_dir = temp_dir.path().join("Scripts");
119 fs::create_dir_all(&scripts_dir)?;
120
121 fs::write(scripts_dir.join("python.exe"), "")?;
123 fs::write(scripts_dir.join("awslabs.cdk-mcp-server.exe"), "")?;
124 fs::write(scripts_dir.join("org.example.tool.exe"), "")?;
125 fs::write(scripts_dir.join("multi.dot.package.name.exe"), "")?;
126 fs::write(scripts_dir.join("script.ps1"), "")?;
127 fs::write(scripts_dir.join("batch.bat"), "")?;
128 fs::write(scripts_dir.join("command.cmd"), "")?;
129 fs::write(scripts_dir.join("explicit.ps1"), "")?;
130
131 Ok(temp_dir)
132 }
133
134 #[cfg(target_os = "windows")]
135 #[test]
136 fn test_from_script_path_single_dot_package() {
137 let temp_dir = create_test_environment().expect("Failed to create test environment");
138 let scripts_dir = temp_dir.path().join("Scripts");
139
140 let command =
142 WindowsRunnable::from_script_path(&scripts_dir, OsStr::new("awslabs.cdk-mcp-server"));
143
144 let expected_path = scripts_dir.join("awslabs.cdk-mcp-server.exe");
146 assert_eq!(command.get_program(), expected_path.as_os_str());
147 }
148
149 #[cfg(target_os = "windows")]
150 #[test]
151 fn test_from_script_path_multiple_dots_package() {
152 let temp_dir = create_test_environment().expect("Failed to create test environment");
153 let scripts_dir = temp_dir.path().join("Scripts");
154
155 let command =
157 WindowsRunnable::from_script_path(&scripts_dir, OsStr::new("org.example.tool"));
158
159 let expected_path = scripts_dir.join("org.example.tool.exe");
160 assert_eq!(command.get_program(), expected_path.as_os_str());
161
162 let command =
164 WindowsRunnable::from_script_path(&scripts_dir, OsStr::new("multi.dot.package.name"));
165
166 let expected_path = scripts_dir.join("multi.dot.package.name.exe");
167 assert_eq!(command.get_program(), expected_path.as_os_str());
168 }
169
170 #[cfg(target_os = "windows")]
171 #[test]
172 fn test_from_script_path_simple_package_name() {
173 let temp_dir = create_test_environment().expect("Failed to create test environment");
174 let scripts_dir = temp_dir.path().join("Scripts");
175
176 let command = WindowsRunnable::from_script_path(&scripts_dir, OsStr::new("python"));
178
179 let expected_path = scripts_dir.join("python.exe");
180 assert_eq!(command.get_program(), expected_path.as_os_str());
181 }
182
183 #[cfg(target_os = "windows")]
184 #[test]
185 fn test_from_script_path_explicit_extensions() {
186 let temp_dir = create_test_environment().expect("Failed to create test environment");
187 let scripts_dir = temp_dir.path().join("Scripts");
188
189 let command = WindowsRunnable::from_script_path(&scripts_dir, OsStr::new("explicit.ps1"));
191
192 let expected_path = scripts_dir.join("explicit.ps1");
193 assert_eq!(command.get_program(), "powershell");
194
195 let args: Vec<&OsStr> = command.get_args().collect();
197 assert!(args.contains(&OsStr::new("-File")));
198 assert!(args.contains(&expected_path.as_os_str()));
199
200 let command = WindowsRunnable::from_script_path(&scripts_dir, OsStr::new("batch.bat"));
202 assert_eq!(command.get_program(), "cmd");
203
204 let command = WindowsRunnable::from_script_path(&scripts_dir, OsStr::new("command.cmd"));
206 assert_eq!(command.get_program(), "cmd");
207 }
208}