xtask-toolkit 0.1.12

A collection of useful tools for xtask building
Documentation
use std::path::Path;

use xshell::{cmd, Shell};

#[derive(serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct GhResponse {
    pub tag_name: String,
    pub is_draft: bool,
    pub is_prelease: bool,
}

pub struct Release {
    pub name: String,
    pub version: semver::Version,
    pub draft: bool,
    pub prelease: bool,
}

impl TryFrom<GhResponse> for Release {
    type Error = ();
    fn try_from(value: GhResponse) -> Result<Self, Self::Error> {
        let (name, version) = value.tag_name.rsplit_once('-').ok_or(())?;

        let version = semver::Version::parse(version).map_err(|_| ())?;

        Ok(Release {
            name: name.to_string(),
            version,
            draft: value.is_draft,
            prelease: value.is_prelease,
        })
    }
}

#[derive(Debug, thiserror::Error)]
pub enum GetFromGHError {
    #[error(transparent)]
    XShellError(#[from] xshell::Error),

    #[error(transparent)]
    SerdeError(#[from] serde_json::Error),
}

#[derive(Default)]
pub enum ReleaseMode {
    #[default]
    Normal,

    Draft,
    Prerelease,
}

impl Release {
    pub fn new(name: &str, version: &str) -> Result<Self, semver::Error> {
        let version = semver::Version::parse(version)?;
        Ok(Release {
            name: name.to_string(),
            version,
            draft: false,
            prelease: false,
        })
    }

    pub fn with_release_mode(&mut self, mode: ReleaseMode) -> &mut Self {
        self.draft = matches!(mode, ReleaseMode::Draft);
        self.prelease = matches!(mode, ReleaseMode::Prerelease);
        self
    }

    pub fn get_from_gh() -> Result<Vec<Self>, GetFromGHError> {
        let sh = Shell::new()?;
        let previous_releases: Vec<GhResponse> =
            serde_json::from_str(&cmd!(sh, "gh release list --json tagName").read()?)?;

        Ok(previous_releases
            .into_iter()
            .filter_map(|x| Self::try_from(x).ok())
            .collect())
    }

    pub fn release<T, I>(&self, files: T) -> Result<String, xshell::Error>
    where
        T: IntoIterator<Item = I>,
        I: AsRef<Path> + Sized,
    {
        let sh = Shell::new()?;
        let files = files
            .into_iter()
            .map(|x| x.as_ref().display().to_string())
            .collect::<Vec<_>>();

        let name = self.name.clone();

        let draft = if self.draft { " --draft" } else { "" };

        sh.cmd("gh")
            .args(["release", "create", "generate-notes", draft, &name])
            .args(files)
            .read()
    }
}