use std::path::PathBuf;
use shun::config::{ShunConfig, TargetConfig};
use shun::flow::{Flow, FlowEvent};
use shun::payload::{ArchivePayload, pack_directory};
use shun::targets::install::{InstallContext, InstallFlow, WindowsRegistration, uninstall};
fn main() {
if let Err(err) = run() {
eprintln!("error: {err}");
std::process::exit(1);
}
}
fn run() -> Result<(), Box<dyn std::error::Error>> {
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let demo_manifest = manifest_dir.join("demo-app").join("Cargo.toml");
let config = ShunConfig::from_cargo_manifest(&demo_manifest)?;
let payload_dir = demo_manifest
.parent()
.map(|dir| dir.join(config.payload.clone().unwrap_or_else(|| PathBuf::from("."))))
.unwrap_or_else(|| manifest_dir.clone());
let mut install_dir: Option<PathBuf> = None;
let mut portable = false;
let mut uninstall_mode = false;
for arg in std::env::args().skip(1) {
if let Some(value) = arg.strip_prefix("--dir=") {
install_dir = Some(PathBuf::from(value));
} else {
match arg.as_str() {
"--portable" => portable = true,
"--uninstall" => uninstall_mode = true,
other => return Err(format!("unknown argument: {other}").into()),
}
}
}
let product = config.product.name.clone();
let package_path = std::env::current_dir()?.join(format!("{product}.shun"));
let archive = pack_directory(&payload_dir)?;
std::fs::write(&package_path, &archive)?;
println!("✔ installer package written: {}", package_path.display());
let install_dir = install_dir.unwrap_or_else(|| default_install_dir(&product, portable));
let mut ctx = InstallContext::new(
product,
config.product.version.clone(),
install_dir,
portable,
);
ctx.publisher = config.product.publisher.clone();
ctx.main_exe = config.targets.iter().find_map(|t| match t {
TargetConfig::Install(install) => install.main_exe.clone(),
_ => None,
});
if let Some(install) = config.targets.iter().find_map(|t| match t {
TargetConfig::Install(install) => Some(install),
_ => None,
}) {
ctx.apply_config(install, shun::targets::install::WizardAnswers::defaults());
}
let staged = ctx
.main_exe
.as_ref()
.is_some_and(|main| payload_dir.join(main).is_file());
if !staged {
let main = ctx
.main_exe
.as_ref()
.map(|main| main.display().to_string())
.unwrap_or_else(|| "<unset>".into());
if portable {
println!(
"△ demo application not staged — run `just demo-payload` for the full \
Tauri demo app before packing"
);
} else {
return Err(format!(
"demo application not staged — local installs create a Start-menu shortcut, \
which needs {main} in the payload; run `just demo-payload` first"
)
.into());
}
}
if uninstall_mode {
uninstall(&ctx, &WindowsRegistration)?;
println!("✔ uninstalled {}", ctx.install_dir.display());
return Ok(());
}
let payload = ArchivePayload::from_bytes(&archive)?;
let flow = InstallFlow {
payload: &payload,
registration: &WindowsRegistration,
ctx: ctx.clone(),
};
flow.run(&mut print_event)?;
let entry = ctx
.main_exe
.as_ref()
.map(|main| ctx.install_dir.join(main))
.unwrap_or_else(|| ctx.install_dir.clone());
if ctx.portable {
println!("✔ portable install complete: {}", ctx.install_dir.display());
println!(" run it: {}", entry.display());
println!(
" remove it: cargo run --example demo_install -- --uninstall --dir={}",
ctx.install_dir.display()
);
} else {
println!("✔ install complete: {}", ctx.install_dir.display());
println!(
" verify it: Settings → Apps → Installed apps → {}",
ctx.product
);
println!(
" shortcut: Start Menu → Programs → {}",
ctx.product
);
println!(" remove it: Settings → Apps, or rerun with --uninstall");
}
Ok(())
}
fn default_install_dir(product: &str, portable: bool) -> PathBuf {
if portable {
return std::env::current_dir()
.unwrap_or_else(|_| PathBuf::from("."))
.join(format!("{product}-portable"));
}
std::env::var("LOCALAPPDATA")
.map(|local| PathBuf::from(local).join(product))
.unwrap_or_else(|_| std::env::current_dir().unwrap_or_default().join(product))
}
fn print_event(event: FlowEvent) {
match event {
FlowEvent::Started => println!("▸ flow started"),
FlowEvent::Progress {
phase,
step,
percent,
} => match percent {
Some(percent) => println!(" [{percent:>3}%] [{phase:?}] {step}"),
None => println!(" [ ··· ] [{phase:?}] {step}"),
},
FlowEvent::Completed => println!("✔ flow completed"),
FlowEvent::Failed { message } => println!("✖ failed: {message}"),
_ => {}
}
}