diff --git a/crates/engine/src/cmd/package.rs b/crates/engine/src/cmd/package.rs index d0ed58b03..ffc69b39e 100644 --- a/crates/engine/src/cmd/package.rs +++ b/crates/engine/src/cmd/package.rs @@ -907,13 +907,12 @@ mod tests { let outside = base.join("separate"); std::fs::create_dir_all(&outside).unwrap(); let redirected = base.join("Package"); - match symlink_dir(&outside, &redirected) { - Ok(()) => {} - Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => { - let _ = std::fs::remove_dir_all(&base); - return; - } - Err(e) => panic!("could not create directory redirect: {e}"), + // A symbolic link needs Developer Mode or elevation (os error 1314 without them); a + // directory junction needs neither, and `is_redirect` treats both as redirects. + if symlink_dir(&outside, &redirected).is_err() && !make_junction(&outside, &redirected) { + eprintln!("skipped: directory redirects can't be made here"); + let _ = std::fs::remove_dir_all(&base); + return; } let mut s = Session::new(); s.execute("file.new", &json!({"title": "Annual"})).unwrap(); @@ -922,4 +921,17 @@ mod tests { std::fs::remove_dir(&redirected).unwrap(); let _ = std::fs::remove_dir_all(&base); } + + /// Makes `link` a directory junction to `target` with `mklink /J`, and reports whether it was made. + #[cfg(windows)] + fn make_junction(target: &Path, link: &Path) -> bool { + use std::os::windows::process::CommandExt; + + // Windows paths can't hold `"`, so quoting each one keeps `&` and spaces in it literal for cmd. + std::process::Command::new("cmd") + .args(["/D", "/C"]) + .raw_arg(format!("mklink /J \"{}\" \"{}\"", link.display(), target.display())) + .output() + .is_ok_and(|output| output.status.success()) + } }