use std::ffi::OsString;
use std::path::PathBuf;
const EXIT_CONFIG: i32 = 70;
const EXIT_SPAWN: i32 = 71;
const ALLOWED_VARS: [&str; 2] = ["BUN_BE_BUN", "ELECTRON_RUN_AS_NODE"];
const VAR_VALUE: &str = "1";
struct Target {
runtime: PathBuf,
name: String,
}
fn parse_target(body: &str) -> Option<Target> {
let mut runtime: Option<PathBuf> = None;
let mut env: Option<(String, String)> = None;
for line in body.lines() {
let line = line.trim_end_matches('\r');
if line.is_empty() {
continue;
}
if let Some(path) = line.strip_prefix("runtime=") {
if runtime.is_some() {
return None;
}
runtime = Some(PathBuf::from(path));
} else {
let assign = line.strip_prefix("env=")?;
if env.is_some() {
return None;
}
let (name, value) = assign.split_once('=')?;
env = Some((name.to_string(), value.to_string()));
}
}
let runtime = runtime?;
let (name, value) = env?;
if !runtime.is_absolute() || !ALLOWED_VARS.contains(&name.as_str()) || value != VAR_VALUE {
return None;
}
Some(Target { runtime, name })
}
fn load_target() -> Option<Target> {
let exe = std::env::current_exe().ok()?;
let config = exe.with_extension("target");
let body = std::fs::read_to_string(config).ok()?;
parse_target(&body)
}
fn main() {
let Some(target) = load_target() else {
eprintln!("openlatch-cline-runtime: missing or invalid .target beside the launcher");
std::process::exit(EXIT_CONFIG);
};
let args: Vec<OsString> = std::env::args_os().skip(1).collect();
std::process::exit(run(&target, &args));
}
#[cfg(unix)]
fn run(target: &Target, args: &[OsString]) -> i32 {
use std::os::unix::process::CommandExt;
let err = std::process::Command::new(&target.runtime)
.args(args)
.env(&target.name, VAR_VALUE)
.exec();
eprintln!(
"openlatch-cline-runtime: cannot run {}: {err}",
target.runtime.display()
);
EXIT_SPAWN
}
#[cfg(windows)]
fn run(target: &Target, args: &[OsString]) -> i32 {
windows::run(target, args)
}
#[cfg(not(any(unix, windows)))]
fn run(_target: &Target, _args: &[OsString]) -> i32 {
EXIT_SPAWN
}
#[cfg_attr(not(windows), allow(dead_code))]
fn quote_windows_arg(arg: &str) -> String {
if !arg.is_empty() && !arg.contains([' ', '\t', '\n', '\u{b}', '"']) {
return arg.to_string();
}
let mut out = String::with_capacity(arg.len() + 2);
out.push('"');
let mut backslashes = 0usize;
for c in arg.chars() {
match c {
'\\' => backslashes += 1,
'"' => {
out.extend(std::iter::repeat_n('\\', backslashes * 2 + 1));
out.push('"');
backslashes = 0;
}
_ => {
out.extend(std::iter::repeat_n('\\', backslashes));
out.push(c);
backslashes = 0;
}
}
}
out.extend(std::iter::repeat_n('\\', backslashes * 2));
out.push('"');
out
}
#[cfg_attr(not(windows), allow(dead_code))]
fn child_env(
ours: impl IntoIterator<Item = (String, String)>,
name: &str,
value: &str,
) -> Vec<(String, String)> {
let mut env: Vec<(String, String)> = ours
.into_iter()
.filter(|(k, _)| !k.eq_ignore_ascii_case(name))
.collect();
env.push((name.to_string(), value.to_string()));
env.sort_by_key(|(k, _)| k.to_uppercase());
env
}
#[cfg(windows)]
mod windows {
use std::ffi::OsString;
use std::os::windows::ffi::OsStrExt;
use windows_sys::Win32::Foundation::{CloseHandle, TRUE};
use windows_sys::Win32::System::Threading::{
CreateProcessW, GetExitCodeProcess, GetStartupInfoW, WaitForSingleObject,
CREATE_UNICODE_ENVIRONMENT, INFINITE, PROCESS_INFORMATION, STARTUPINFOW,
};
use super::{child_env, quote_windows_arg, Target, EXIT_SPAWN, VAR_VALUE};
fn wide(s: &std::ffi::OsStr) -> Vec<u16> {
s.encode_wide().chain(std::iter::once(0)).collect()
}
pub(super) fn run(target: &Target, args: &[OsString]) -> i32 {
let mut ours = STARTUPINFOW {
cb: std::mem::size_of::<STARTUPINFOW>() as u32,
..Default::default()
};
unsafe { GetStartupInfoW(&mut ours) };
let si = STARTUPINFOW {
cb: std::mem::size_of::<STARTUPINFOW>() as u32,
dwFlags: ours.dwFlags,
wShowWindow: ours.wShowWindow,
cbReserved2: ours.cbReserved2,
lpReserved2: ours.lpReserved2,
hStdInput: ours.hStdInput,
hStdOutput: ours.hStdOutput,
hStdError: ours.hStdError,
..Default::default()
};
let env = child_env(
std::env::vars_os().map(|(k, v)| {
(
k.to_string_lossy().into_owned(),
v.to_string_lossy().into_owned(),
)
}),
&target.name,
VAR_VALUE,
);
let mut block: Vec<u16> = Vec::new();
for (k, v) in &env {
block.extend(std::ffi::OsStr::new(&format!("{k}={v}")).encode_wide());
block.push(0);
}
block.push(0);
let runtime = target.runtime.as_os_str();
let mut cmdline = quote_windows_arg(&runtime.to_string_lossy());
for arg in args {
cmdline.push(' ');
cmdline.push_str("e_windows_arg(&arg.to_string_lossy()));
}
let mut cmdline = wide(std::ffi::OsStr::new(&cmdline));
let application = wide(runtime);
let mut pi = PROCESS_INFORMATION {
hProcess: std::ptr::null_mut(),
hThread: std::ptr::null_mut(),
dwProcessId: 0,
dwThreadId: 0,
};
let created = unsafe {
CreateProcessW(
application.as_ptr(),
cmdline.as_mut_ptr(),
std::ptr::null(),
std::ptr::null(),
TRUE,
CREATE_UNICODE_ENVIRONMENT,
block.as_ptr().cast(),
std::ptr::null(),
&si,
&mut pi,
)
};
if created == 0 {
eprintln!(
"openlatch-cline-runtime: cannot run {}: {}",
target.runtime.display(),
std::io::Error::last_os_error()
);
return EXIT_SPAWN;
}
let mut code: u32 = EXIT_SPAWN as u32;
unsafe {
WaitForSingleObject(pi.hProcess, INFINITE);
GetExitCodeProcess(pi.hProcess, &mut code);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
code as i32
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn target_accepts_exactly_one_runtime_and_one_allowed_variable() {
let abs = if cfg!(windows) {
r"C:\VS Code\Code.exe"
} else {
"/opt/code/code"
};
let ok =
parse_target(&format!("runtime={abs}\nenv=ELECTRON_RUN_AS_NODE=1\n")).expect("valid");
assert_eq!(ok.runtime, PathBuf::from(abs));
assert_eq!(ok.name, "ELECTRON_RUN_AS_NODE");
assert!(parse_target(&format!("runtime={abs}\r\nenv=BUN_BE_BUN=1\r\n")).is_some());
for bad in [
format!("runtime={abs}\nenv=PATH=1\n"),
format!("runtime={abs}\nenv=BUN_BE_BUN=0\n"),
format!("runtime={abs}\n"),
"env=BUN_BE_BUN=1\n".to_string(),
"runtime=relative/code\nenv=BUN_BE_BUN=1\n".to_string(),
format!("runtime={abs}\nenv=BUN_BE_BUN=1\nextra=1\n"),
format!("runtime={abs}\nruntime={abs}\nenv=BUN_BE_BUN=1\n"),
] {
assert!(parse_target(&bad).is_none(), "{bad:?}");
}
}
#[test]
fn windows_arguments_round_trip_the_crt_rules() {
assert_eq!(quote_windows_arg("plain"), "plain");
assert_eq!(quote_windows_arg(""), "\"\"");
assert_eq!(quote_windows_arg("a b"), "\"a b\"");
assert_eq!(quote_windows_arg(r#"a"b"#), r#""a\"b""#);
assert_eq!(
quote_windows_arg(r"C:\dir with space\"),
r#""C:\dir with space\\""#
);
assert_eq!(quote_windows_arg(r"C:\no\space"), r"C:\no\space");
}
#[test]
fn child_env_replaces_and_sorts() {
let env = child_env(
[
("Path".to_string(), "x".to_string()),
("electron_run_as_node".to_string(), "0".to_string()),
("APPDATA".to_string(), "y".to_string()),
],
"ELECTRON_RUN_AS_NODE",
"1",
);
assert_eq!(
env,
vec![
("APPDATA".to_string(), "y".to_string()),
("ELECTRON_RUN_AS_NODE".to_string(), "1".to_string()),
("Path".to_string(), "x".to_string()),
]
);
}
}