rlyx 0.3.1

rlyx is a fast release manager that automatically bumps versions, creates changelogs, tags commits, and publishes GitHub releases across JS, Rust, and Python projects with first class monorepos support.
Documentation
use anyhow::Result;
use clap::ValueEnum;
use regex::Regex;

#[derive(Debug, Clone, Copy, ValueEnum, serde::Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum BumpKind {
    Patch,
    Minor,
    Major,
}

pub fn base_of(v: &str) -> String {
    v.split('-').next().unwrap_or(v).to_string()
}

pub fn bump(base: &str, kind: BumpKind) -> Result<String> {
    let mut parts = base
        .split('.')
        .map(|s| s.parse::<u64>().unwrap_or(0))
        .collect::<Vec<_>>();
    while parts.len() < 3 {
        parts.push(0);
    }
    match kind {
        BumpKind::Patch => parts[2] += 1,
        BumpKind::Minor => {
            parts[1] += 1;
            parts[2] = 0;
        }
        BumpKind::Major => {
            parts[0] += 1;
            parts[1] = 0;
            parts[2] = 0;
        }
    }
    Ok(format!("{}.{}.{}", parts[0], parts[1], parts[2]))
}

pub fn prerelease_new(base: &str, id: &str) -> Result<String> {
    if id == "nightly" {
        let d = chrono::Utc::now().format("%Y%m%d").to_string();
        let pattern = format!("v{}-nightly.{}*", base, d);
        let tags = crate::git::list_tags(&pattern)?;
        if tags.is_empty() {
            return Ok(format!("{}-nightly.{}", base, d));
        }
        let pat = format!(
            "^v{}-nightly\\.{}\\.(\\d+)$",
            regex::escape(base),
            regex::escape(&d)
        );
        let re = Regex::new(&pat)?;
        let mut maxn = 0u64;
        for t in tags {
            if let Some(c) = re.captures(&t) {
                if let Some(m) = c.get(1) {
                    if let Ok(n) = m.as_str().parse::<u64>() {
                        maxn = maxn.max(n);
                    }
                }
            }
        }
        return Ok(format!("{}-nightly.{}.{}", base, d, maxn + 1));
    }

    let pattern = format!("v{}-{}.*", base, id);
    let tags = crate::git::list_tags(&pattern)?;
    if tags.is_empty() {
        return Ok(format!("{}-{}.1", base, id));
    }
    let pat = format!(
        "^v{}-{}\\.(\\d+)$",
        regex::escape(base),
        regex::escape(id)
    );
    let re = Regex::new(&pat)?;
    let mut maxn = 0u64;
    for t in tags {
        if let Some(c) = re.captures(&t) {
            if let Some(m) = c.get(1) {
                if let Ok(n) = m.as_str().parse::<u64>() {
                    maxn = maxn.max(n);
                }
            }
        }
    }
    Ok(format!("{}-{}.{}", base, id, maxn + 1))
}

impl From<crate::ReleaseKind> for BumpKind {
    fn from(k: crate::ReleaseKind) -> Self {
        match k {
            crate::ReleaseKind::Major => BumpKind::Major,
            crate::ReleaseKind::Minor => BumpKind::Minor,
            crate::ReleaseKind::Patch => BumpKind::Patch,
        }
    }
}