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