carte 0.1.0

Runs your apps' processes on demand behind *.test hostnames, one stack per git worktree
Documentation
use std::process::ExitCode;

use anyhow::bail;

use super::SetupArgs;
use super::prompt::{self, tilde};
use crate::paths::Paths;
use crate::setup::{self, Facts, SetupEnv, check};

pub fn run(paths: &Paths, args: SetupArgs) -> anyhow::Result<ExitCode> {
    let env = SetupEnv::from_system(paths)?;
    if let Some(domain) = &args.share {
        return share(&env, domain, &args.tunnel, args.dry_run);
    }
    let mut facts = Facts::gather(&env);
    if args.no_skill {
        facts.skill = None;
    }
    if args.check {
        let report = check::check(&env, &facts);
        print!("{report}");
        return Ok(if report.ok() {
            ExitCode::SUCCESS
        } else {
            ExitCode::FAILURE
        });
    }

    let plan = if args.uninstall {
        setup::plan_uninstall(&env, &facts)
    } else {
        setup::plan_install(&env, &facts)
    };
    let import_first = !args.uninstall && facts.puma_dev.links > 0 && facts.projects == 0;
    if !args.uninstall && facts.puma_dev.installed() {
        println!(
            "puma-dev is installed. Setup stops and disables its LaunchAgent so carte can take \
             over ports {} and {}, and points /etc/resolver/{} at carte. {} and puma-dev's CA stay; \
             `carte setup --uninstall` shows how to bring puma-dev back.",
            env.machine.http_port,
            env.machine.https_port,
            env.machine.tld,
            tilde(&env.puma_dev_dir),
        );
        if import_first {
            println!(
                "You have {} apps in {} and no carte projects yet: run `carte import puma-dev` first.",
                facts.puma_dev.links,
                tilde(&env.puma_dev_dir)
            );
        }
        println!();
    }

    if !plan.changes_anything() && !args.uninstall {
        println!("Everything is set up already.");
    } else if plan.steps.is_empty() {
        println!("Nothing to undo.");
    } else {
        let sudo = plan.steps.iter().any(|s| s.needs_sudo());
        println!(
            "{}{}:\n",
            if args.uninstall {
                "Uninstall plan"
            } else {
                "Setup plan"
            },
            if sudo {
                " (sudo asks for your password)"
            } else {
                ""
            }
        );
    }
    print!("{}", plan.render(&env, args.dry_run));

    if args.dry_run {
        println!("\nDry run: nothing was changed.");
        return Ok(ExitCode::SUCCESS);
    }
    if plan.steps.is_empty() {
        return Ok(ExitCode::SUCCESS);
    }
    if !prompt::interactive(false) {
        bail!(
            "setup asks for confirmation and sudo, so it needs a terminal; `--dry-run` shows the plan"
        );
    }
    println!();
    if import_first && !prompt::confirm("Continue without importing from puma-dev?", false)? {
        println!("Nothing changed. Run `carte import puma-dev`, then `carte setup` again.");
        return Ok(ExitCode::SUCCESS);
    }
    if plan.changes_anything() && !prompt::confirm("Run these steps?", true)? {
        println!("Nothing changed.");
        return Ok(ExitCode::SUCCESS);
    }
    if let Err(err) = setup::execute(&plan.steps, &env) {
        eprintln!("carte: {err:#}");
        if !args.uninstall {
            eprintln!("\n{}", check::fallback(&env));
        }
        return Ok(ExitCode::FAILURE);
    }
    if args.uninstall {
        println!("\ncarte is uninstalled.");
    } else {
        println!("\ncarte is set up.");
    }
    Ok(ExitCode::SUCCESS)
}

fn share(env: &SetupEnv, domain: &str, tunnel: &str, dry_run: bool) -> anyhow::Result<ExitCode> {
    let provider = setup::share::Provider::for_domain(domain);
    let facts = setup::share::ShareFacts::gather(env, provider, tunnel);
    let plan = setup::share::plan(env, &facts, domain, tunnel)?;
    println!("Sharing plan:\n");
    print!("{}", plan.render(env, true));
    if dry_run {
        println!("\nDry run: nothing was changed.");
        return Ok(ExitCode::SUCCESS);
    }
    if !prompt::interactive(false) {
        bail!("setup asks for confirmation, so it needs a terminal; `--dry-run` shows the plan");
    }
    println!();
    if !prompt::confirm("Run these steps?", true)? {
        println!("Nothing changed.");
        return Ok(ExitCode::SUCCESS);
    }
    setup::execute(&plan.steps, env)?;
    println!("\nSharing is set up. Try `carte share <app>`.");
    Ok(ExitCode::SUCCESS)
}