forklaunch 1.18.0

Launch faster with forklaunch
use std::io::Write;

use anyhow::Result;
use clap::{Arg, ArgAction, ArgMatches, Command};
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};

use crate::{
    CliCommand,
    core::command::command,
    managed::{
        client::{
            Missing, extract_list, get_value, print_dryrun, require_managed_mode,
            resolve_managed_auth,
        },
        types::{FleetRollout, dash},
    },
};

#[derive(Debug)]
pub(super) struct ListCommand;

impl ListCommand {
    pub(super) fn new() -> Self {
        Self
    }
}

impl CliCommand for ListCommand {
    fn command(&self) -> Command {
        command("list", "List fleet rollouts, newest first")
            .arg(
                Arg::new("template")
                    .long("template")
                    .help("Only rollouts of this template"),
            )
            .arg(
                Arg::new("dryrun")
                    .long("dryrun")
                    .help("Print the request that would be sent without sending it")
                    .action(ArgAction::SetTrue),
            )
            .arg(
                Arg::new("json")
                    .long("json")
                    .help("Output raw JSON instead of formatted terminal output")
                    .action(ArgAction::SetTrue),
            )
    }

    fn handler(&self, matches: &ArgMatches) -> Result<()> {
        if matches.get_flag("dryrun") {
            return print_dryrun("GET", "/rollouts", None);
        }
        let auth_mode = resolve_managed_auth()?;
        require_managed_mode(&auth_mode)?;

        let value = get_value(&auth_mode, "/rollouts", Missing::Endpoint)?;
        let template_filter = matches.get_one::<String>("template");
        let rollouts: Vec<FleetRollout> = extract_list::<FleetRollout>(value, &["rollouts"])?
            .into_iter()
            .filter(|rollout| match (template_filter, &rollout.template_slug) {
                (Some(wanted), Some(actual)) => actual == wanted,
                (Some(_), None) => false,
                (None, _) => true,
            })
            .collect();

        if matches.get_flag("json") {
            println!("{}", serde_json::to_string_pretty(&rollouts)?);
            return Ok(());
        }

        let mut stdout = StandardStream::stdout(ColorChoice::Always);
        writeln!(stdout)?;
        stdout.set_color(ColorSpec::new().set_fg(Some(Color::Cyan)).set_bold(true))?;
        writeln!(stdout, "Fleet rollouts")?;
        stdout.reset()?;
        writeln!(stdout)?;
        if rollouts.is_empty() {
            writeln!(
                stdout,
                "  No rollouts yet. Start one with `forklaunch managed rollout start`."
            )?;
            writeln!(stdout)?;
            return Ok(());
        }
        stdout.set_color(ColorSpec::new().set_bold(true))?;
        writeln!(
            stdout,
            "  {:<38} {:<18} {:<10} {:<12} {:<8} {}",
            "ID", "TEMPLATE", "TARGET", "STATE", "WAVE", "ITEMS"
        )?;
        stdout.reset()?;
        for rollout in &rollouts {
            let mut counts = std::collections::BTreeMap::new();
            for item in &rollout.items {
                *counts
                    .entry(item.state.as_deref().unwrap_or("-"))
                    .or_insert(0u32) += 1;
            }
            let items: Vec<String> = counts.iter().map(|(k, v)| format!("{} {}", v, k)).collect();
            writeln!(
                stdout,
                "  {:<38} {:<18} {:<10} {:<12} {:<8} {}",
                dash(&rollout.id),
                dash(&rollout.template_slug),
                dash(&rollout.target_version_semver),
                dash(&rollout.state),
                format!(
                    "{}/{}",
                    rollout
                        .current_wave
                        .map(|w| (w + 1).min(rollout.wave_percents.len() as u64))
                        .unwrap_or(0),
                    rollout.wave_percents.len()
                ),
                items.join(", ")
            )?;
        }
        writeln!(stdout)?;
        Ok(())
    }
}