cargo-winapp 0.1.1

Cargo subcommand for Windows app development with winapp CLI
//! cargo winapp pack - Package as MSIX

use crate::util;
use anyhow::{Context, Result};
use std::fs;
use std::process::Command;

pub fn run(
    input: String,
    output: Option<String>,
    cargo_args: Vec<String>,
    verbose: bool,
) -> Result<()> {
    crate::info!("[INFO] Packaging as MSIX...");

    let info = util::load_project_info()?;

    // Check if appxmanifest.xml exists
    let manifest_path = info.manifest_dir.join("appxmanifest.xml");
    if !manifest_path.exists() {
        anyhow::bail!(
            "appxmanifest.xml not found. Run 'winapp init' first to set up your project."
        );
    }

    // Build and get the executable path
    crate::info!("[INFO] Building...");

    let build_result = util::build_and_get_exe(&info.manifest_dir, &cargo_args, verbose)?;
    let exe_path = &build_result.exe_path;

    // Prepare dist directory
    let dist_dir = std::env::current_dir()
        .context("Failed to get current directory")?
        .join(input);

    fs::create_dir_all(&dist_dir).context("Failed to create dist directory")?;

    // Copy executable (preserve filename from build output)
    let exe_name = exe_path
        .file_name()
        .context("Failed to get executable filename")?;
    let dest_exe = dist_dir.join(exe_name);
    fs::copy(exe_path, &dest_exe)
        .with_context(|| format!("Failed to copy {:?} to {:?}", exe_path, dest_exe))?;

    crate::status!(
        verbose,
        "> Copied {} to {}/",
        exe_name.to_string_lossy(),
        dist_dir.display()
    );

    // Copy Assets if they exist
    let assets_src = info.manifest_dir.join("Assets");
    let assets_dest = dist_dir.join("Assets");
    if assets_src.exists() {
        copy_dir_recursive(&assets_src, &assets_dest)?;
        crate::status!(verbose, "> Copied Assets/");
    }

    // Find or generate certificate
    let cert_path = info.manifest_dir.join("devcert.pfx");
    if !cert_path.exists() {
        crate::info!("[WARN] No certificate found. Generating one...");

        let mut cmd = Command::new("winapp");
        cmd.args(["cert", "generate"])
            .current_dir(&info.manifest_dir);

        if verbose {
            cmd.arg("--verbose");
        }

        let status = cmd.status().context("Failed to generate certificate")?;

        if !status.success() {
            anyhow::bail!("Failed to generate certificate. Run 'winapp cert generate' manually.");
        }
    }

    // Pack with winapp
    crate::info!("[INFO] Running winapp pack...");

    let mut cmd = Command::new("winapp");
    cmd.arg("pack").arg(&dist_dir).arg("--cert").arg(&cert_path);

    if let Some(out_path) = output {
        cmd.arg("--output").arg(out_path);
    }

    if verbose {
        cmd.arg("--verbose");
    }

    let status = cmd.status().context("Failed to run winapp pack")?;

    if !status.success() {
        anyhow::bail!("winapp pack failed");
    }

    crate::info!("[INFO] MSIX package created.");
    crate::info!("To install:");
    crate::info!("  1. Install the certificate: winapp cert install devcert.pfx");
    crate::info!("  2. Double-click the .msix file to install");

    Ok(())
}

fn copy_dir_recursive(src: &std::path::Path, dest: &std::path::Path) -> Result<()> {
    fs::create_dir_all(dest)?;
    for entry in fs::read_dir(src)? {
        let entry = entry?;
        let file_type = entry.file_type()?;
        let src_path = entry.path();
        let dest_path = dest.join(entry.file_name());

        if file_type.is_dir() {
            copy_dir_recursive(&src_path, &dest_path)?;
        } else {
            fs::copy(&src_path, &dest_path)?;
        }
    }
    Ok(())
}