use std::path::Path;
use crate::config::ShunConfig;
use crate::payload::ArchivePayload;
pub const EVERGREEN_PREFIX: &str = "webview2";
const EVERGREEN_INSTALLER_STEM: &str = "microsoftedgewebview2runtimeinstaller";
pub fn evergreen_carried(payload: &ArchivePayload) -> bool {
evergreen_entry(payload).is_some()
}
fn evergreen_entry(payload: &ArchivePayload) -> Option<std::path::PathBuf> {
payload
.entries()
.iter()
.map(|entry| &entry.path)
.find(|path| {
path.starts_with(Path::new(EVERGREEN_PREFIX))
&& path.file_name().is_some_and(|name| {
name.to_string_lossy()
.to_lowercase()
.starts_with(EVERGREEN_INSTALLER_STEM)
})
&& path
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("exe"))
})
.cloned()
}
pub fn evergreen_cache(product: &str) -> Option<std::path::PathBuf> {
std::env::var_os("LOCALAPPDATA")
.map(std::path::PathBuf::from)
.map(|local| local.join("shun").join(product).join("webview2"))
}
pub fn stage_evergreen(payload: &ArchivePayload, cache: &Path) -> Option<std::path::PathBuf> {
let entry = evergreen_entry(payload)?;
if payload
.extract_prefix(cache, Path::new(EVERGREEN_PREFIX), &mut |_| {})
.is_err()
{
return None;
}
let staged = cache.join(&entry);
staged.is_file().then_some(staged)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EvergreenSource {
Carried(std::path::PathBuf),
Download(String),
None,
}
pub fn evergreen_source(config: &ShunConfig, payload: &ArchivePayload) -> EvergreenSource {
if let Some(entry) = evergreen_entry(payload) {
return EvergreenSource::Carried(entry);
}
if config.webview2_silent_install() {
if let Some(url) = config.webview2_download_url() {
return EvergreenSource::Download(url.to_string());
}
}
EvergreenSource::None
}
#[cfg(windows)]
pub fn run_evergreen_silent(installer: &Path) -> std::io::Result<std::process::ExitStatus> {
use std::os::windows::process::CommandExt;
const CREATE_NO_WINDOW: u32 = 0x0800_0000;
let args = ["/silent", "/install"];
match std::process::Command::new(installer)
.args(args)
.creation_flags(CREATE_NO_WINDOW)
.status()
{
Ok(status) => Ok(status),
Err(err) if err.raw_os_error() == Some(740) => {
let exe = installer.display().to_string().replace('\'', "''");
let script = format!(
"Start-Process -FilePath '{exe}' -ArgumentList '{}' -Verb RunAs -Wait",
args.join("','")
);
std::process::Command::new("powershell")
.args(["-NoProfile", "-Command", &script])
.creation_flags(CREATE_NO_WINDOW)
.status()
}
Err(err) => Err(err),
}
}
#[cfg(not(windows))]
pub fn run_evergreen_silent(_installer: &Path) -> std::io::Result<std::process::ExitStatus> {
Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"the Evergreen installer only exists on Windows",
))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::payload::pack_directory;
fn packed(dir: &Path) -> ArchivePayload {
ArchivePayload::from_bytes(&pack_directory(dir).expect("packs")).expect("decodes")
}
#[test]
fn evergreen_is_found_under_the_payload_prefix() {
let dir = std::env::temp_dir().join(format!("shun-wv2-found-{}", std::process::id()));
let inner = dir.join(EVERGREEN_PREFIX);
std::fs::create_dir_all(&inner).unwrap();
std::fs::write(
inner.join("MicrosoftEdgeWebView2RuntimeInstallerX64.exe"),
b"MZ",
)
.unwrap();
let payload = packed(&dir);
let _ = std::fs::remove_dir_all(&dir);
assert!(evergreen_carried(&payload));
assert_eq!(
evergreen_entry(&payload),
Some(Path::new("webview2/MicrosoftEdgeWebView2RuntimeInstallerX64.exe").to_path_buf())
);
}
#[test]
fn evergreen_match_is_case_insensitive_but_name_gated() {
let dir = std::env::temp_dir().join(format!("shun-wv2-case-{}", std::process::id()));
let inner = dir.join(EVERGREEN_PREFIX);
std::fs::create_dir_all(&inner).unwrap();
std::fs::write(
inner.join("MICROSOFTEDGEWEBVIEW2RUNTIMEINSTALLERARM64.EXE"),
b"MZ",
)
.unwrap();
std::fs::write(inner.join("license.txt"), b"eula").unwrap();
let payload = packed(&dir);
let _ = std::fs::remove_dir_all(&dir);
assert!(evergreen_carried(&payload));
assert_eq!(
evergreen_entry(&payload)
.unwrap()
.to_string_lossy()
.to_lowercase(),
"webview2/microsoftedgewebview2runtimeinstallerarm64.exe"
);
}
#[test]
fn evergreen_outside_the_prefix_is_ignored() {
let dir = std::env::temp_dir().join(format!("shun-wv2-outside-{}", std::process::id()));
std::fs::create_dir_all(dir.join("tools")).unwrap();
std::fs::write(
dir.join("tools/MicrosoftEdgeWebView2RuntimeInstallerX64.exe"),
b"MZ",
)
.unwrap();
let payload = packed(&dir);
let _ = std::fs::remove_dir_all(&dir);
assert!(!evergreen_carried(&payload));
}
#[test]
fn evergreen_staging_extracts_the_installer() {
let dir = std::env::temp_dir().join(format!("shun-wv2-stage-{}", std::process::id()));
let inner = dir.join(EVERGREEN_PREFIX);
std::fs::create_dir_all(&inner).unwrap();
std::fs::write(
inner.join("MicrosoftEdgeWebView2RuntimeInstallerX64.exe"),
b"MZ-payload",
)
.unwrap();
let payload = packed(&dir);
let _ = std::fs::remove_dir_all(&dir);
let cache = std::env::temp_dir().join(format!("shun-wv2-cache-{}", std::process::id()));
let staged = stage_evergreen(&payload, &cache);
assert_eq!(
staged.as_deref(),
Some(
cache
.join(EVERGREEN_PREFIX)
.join("MicrosoftEdgeWebView2RuntimeInstallerX64.exe")
.as_path()
)
);
assert_eq!(
std::fs::read(staged.unwrap()).unwrap(),
b"MZ-payload".to_vec()
);
let _ = std::fs::remove_dir_all(&cache);
}
#[test]
fn staging_without_a_carried_installer_is_none() {
let dir = std::env::temp_dir().join(format!("shun-wv2-bare-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
std::fs::write(dir.join("app.exe"), b"MZ").unwrap();
let payload = packed(&dir);
let _ = std::fs::remove_dir_all(&dir);
let cache =
std::env::temp_dir().join(format!("shun-wv2-bare-cache-{}", std::process::id()));
assert_eq!(stage_evergreen(&payload, &cache), None);
assert!(!cache.join(EVERGREEN_PREFIX).exists());
}
fn evergreen_config(download_url: Option<&str>, silent_install: bool) -> ShunConfig {
ShunConfig {
script: None,
variants: None,
product: crate::config::ProductIdentity {
name: "ShunDemo".into(),
version: "0.1.0".into(),
publisher: None,
logo: None,
},
payload: None,
webview2: Some(crate::config::Webview2Strategy::EvergreenInstaller {
silent_install: Some(silent_install),
warn_missing: None,
download_url: download_url.map(str::to_string),
}),
targets: Vec::new(),
shell: None,
source: None,
update: None,
attachments: Vec::new(),
license_sysl: None,
license: None,
license_locales: Default::default(),
licenses: Vec::new(),
custom_steps: Vec::new(),
steps: None,
signing: None,
msix: None,
}
}
#[test]
fn carried_runtime_wins_over_the_download_url() {
let dir = std::env::temp_dir().join(format!("shun-wv2-src-carried-{}", std::process::id()));
let inner = dir.join(EVERGREEN_PREFIX);
std::fs::create_dir_all(&inner).unwrap();
std::fs::write(
inner.join("MicrosoftEdgeWebView2RuntimeInstallerX64.exe"),
b"MZ",
)
.unwrap();
let payload = packed(&dir);
let _ = std::fs::remove_dir_all(&dir);
let config = evergreen_config(
Some("https://go.microsoft.com/fwlink/?linkid=2124701"),
true,
);
assert_eq!(
evergreen_source(&config, &payload),
EvergreenSource::Carried(
Path::new("webview2/MicrosoftEdgeWebView2RuntimeInstallerX64.exe").to_path_buf()
)
);
}
#[test]
fn download_source_needs_url_and_the_silent_knob() {
let dir = std::env::temp_dir().join(format!("shun-wv2-src-dl-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
std::fs::write(dir.join("app.exe"), b"MZ").unwrap();
let payload = packed(&dir);
let _ = std::fs::remove_dir_all(&dir);
let url = "https://go.microsoft.com/fwlink/?linkid=2124701";
let config = evergreen_config(Some(url), true);
assert_eq!(
evergreen_source(&config, &payload),
EvergreenSource::Download(url.to_string())
);
let declined = evergreen_config(Some(url), false);
assert_eq!(evergreen_source(&declined, &payload), EvergreenSource::None);
let offline = evergreen_config(None, true);
assert_eq!(evergreen_source(&offline, &payload), EvergreenSource::None);
}
}