yieldskill 0.5.1

Build portable, resumable skill workflows in Rust.
Documentation
use sha2::{Digest, Sha256};
use std::fs::{self, OpenOptions};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::Command;

#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
use yieldskill_runtime_darwin_amd64 as runtime;
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
use yieldskill_runtime_darwin_arm64 as runtime;
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
use yieldskill_runtime_linux_amd64 as runtime;
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
use yieldskill_runtime_linux_arm64 as runtime;
#[cfg(all(target_os = "windows", target_arch = "x86_64"))]
use yieldskill_runtime_windows_amd64 as runtime;
#[cfg(all(target_os = "windows", target_arch = "aarch64"))]
use yieldskill_runtime_windows_arm64 as runtime;

fn runtime_path() -> Result<PathBuf, String> {
    let root = std::env::var_os("YIELD_RUNTIME_CACHE")
        .map(PathBuf::from)
        .unwrap_or_else(|| std::env::temp_dir().join("yieldskill"));
    let name = if cfg!(windows) {
        "yskill.exe"
    } else {
        "yskill"
    };
    let directory = root
        .join(env!("CARGO_PKG_VERSION"))
        .join(std::env::consts::ARCH);
    let path = directory.join(name);
    if verified(&path) {
        return Ok(path);
    }
    fs::create_dir_all(&directory)
        .map_err(|error| format!("could not create runtime cache: {error}"))?;
    let temporary = directory.join(format!(".{name}.{}.tmp", std::process::id()));
    let mut file = OpenOptions::new()
        .write(true)
        .create_new(true)
        .open(&temporary)
        .map_err(|error| format!("could not stage packaged runtime: {error}"))?;
    file.write_all(runtime::BYTES)
        .and_then(|_| file.sync_all())
        .map_err(|error| format!("could not write packaged runtime: {error}"))?;
    #[cfg(unix)]
    {
        use std::os::unix::fs::PermissionsExt;
        fs::set_permissions(&temporary, fs::Permissions::from_mode(0o755))
            .map_err(|error| format!("could not mark packaged runtime executable: {error}"))?;
    }
    if let Err(error) = fs::rename(&temporary, &path) {
        let _ = fs::remove_file(&temporary);
        if !verified(&path) {
            return Err(format!("could not install packaged runtime: {error}"));
        }
    }
    if !verified(&path) {
        return Err("packaged runtime checksum mismatch".to_string());
    }
    Ok(path)
}

fn verified(path: &Path) -> bool {
    fs::read(path)
        .map(|bytes| hex::encode(Sha256::digest(bytes)) == runtime::SHA256)
        .unwrap_or(false)
}

fn main() {
    let path = match runtime_path() {
        Ok(path) => path,
        Err(error) => {
            eprintln!("yskill: {error}");
            std::process::exit(1);
        }
    };
    let args: Vec<_> = std::env::args_os().skip(1).collect();
    let mut command = Command::new(path);
    command.args(args);
    if std::env::var_os("YIELD_LANGUAGE").is_none() {
        command.env("YIELD_LANGUAGE", "rust");
    }
    if let Ok(launcher) = std::env::current_exe() {
        command.env("YIELD_LAUNCHER_PATH", launcher);
    }
    #[cfg(unix)]
    {
        use std::os::unix::process::CommandExt;
        let error = command.exec();
        eprintln!("yskill: could not start packaged runtime: {error}");
        std::process::exit(1)
    }
    #[cfg(windows)]
    {
        match command.status() {
            Ok(status) => std::process::exit(status.code().unwrap_or(1)),
            Err(error) => {
                eprintln!("yskill: could not start packaged runtime: {error}");
                std::process::exit(1);
            }
        }
    }
}