Skip to main content

Mountain/IPC/WindServiceHandlers/NativeHost/
InstallShellCommand.rs

1
2//! `nativeHost:installShellCommand` - create a `land` (or `code`) symlink in
3//! `/usr/local/bin` pointing at the running executable so the user can launch
4//! the editor from a terminal. Mirrors VS Code's "Install 'code' command in
5//! PATH" command. Uses `pkexec`/`osascript` to acquire elevated privileges
6//! when `/usr/local/bin` is not writable by the current user.
7
8use std::path::PathBuf;
9
10use serde_json::Value;
11
12use crate::dev_log;
13
14const CLI_NAME:&str = "land";
15
16const SYMLINK_DIR:&str = "/usr/local/bin";
17
18pub async fn Fn(_Arguments:Vec<Value>) -> Result<Value, String> {
19	let ExePath = std::env::current_exe().map_err(|E| format!("installShellCommand: cannot get exe path: {E}"))?;
20
21	let Target = PathBuf::from(SYMLINK_DIR).join(CLI_NAME);
22
23	dev_log!("shell-cmd", "installShellCommand: {} → {}", Target.display(), ExePath.display());
24
25	// Remove stale link first (ignore errors - may not exist yet).
26	let _ = std::fs::remove_file(&Target);
27
28	match std::os::unix::fs::symlink(&ExePath, &Target) {
29		Ok(()) => {
30			dev_log!("shell-cmd", "installShellCommand: symlink created");
31
32			Ok(Value::Bool(true))
33		},
34
35		Err(E) if E.kind() == std::io::ErrorKind::PermissionDenied => {
36			// Retry with osascript-elevated write on macOS.
37			#[cfg(target_os = "macos")]
38			{
39				// Pass paths via env vars; use AppleScript's `quoted form of` for
40				// safe shell quoting - no interpolation into script source.
41				let Status = tokio::process::Command::new("osascript")
42					.env("SH_SRC", ExePath.as_os_str())
43					.env("SH_DST", Target.as_os_str())
44					.args([
45						"-e",
46						"do shell script (\"ln -sf \" & quoted form of (system attribute \"SH_SRC\") & \" \" & quoted \
47						 form of (system attribute \"SH_DST\")) with administrator privileges",
48					])
49					.status()
50					.await
51					.map_err(|E| format!("installShellCommand: osascript failed: {E}"))?;
52
53				if Status.success() {
54					dev_log!("shell-cmd", "installShellCommand: symlink created (elevated)");
55
56					return Ok(Value::Bool(true));
57				}
58			}
59
60			Err(format!("installShellCommand: permission denied and elevation failed"))
61		},
62
63		Err(E) => Err(format!("installShellCommand: {E}")),
64	}
65}