use std::process::{Command, Stdio}; use anyhow::Result; use clap::{Args, Subcommand}; use semver::Version; use crate::PKG_VER; use self::bump::{Bump, Level}; mod bump; #[derive(Debug, Clone, Args)] pub struct Release { #[command(subcommand)] step: Option, /// Level of version bump version. #[arg(global = true, required = false)] level: bump::Level, /// Options passed to git commit. #[arg(global = true, last = true)] git_commit_args: Vec, } impl Release { pub fn run(self) -> Result<()> { match self.step { Some(step) => step.run(), None => { let bump = Step::bump(self.level)?; println!("Bumped version: {bump}"); Ok(()) } } } } #[derive(Debug, Clone, Subcommand)] pub enum Step { /// Bump version in package files and commit changes. Bump { #[arg(from_global)] level: bump::Level, }, /// Make a release commit. Commit { #[arg(from_global)] git_commit_args: Vec, }, /// Create git tag for release. Tag { #[arg(from_global)] level: bump::Level, }, } impl Step { pub fn run(self) -> Result<()> { match self { Step::Bump { level } => { let bump = Self::bump(level)?; println!("Bumped version: {bump}"); } Step::Commit { git_commit_args } => Self::commit(git_commit_args)?, Step::Tag { level } => { let stdout = Command::new("git") .arg("describe") .arg("--abbrev=0") .output()? .stdout; let prev = std::str::from_utf8(&stdout)?.parse()?; let next = level.bump(&prev); Self::tag(prev, next)?; } }; Ok(()) } pub fn bump(level: Level) -> Result { let version = PKG_VER.parse().unwrap_or_else(|_| Version::new(0, 1, 0)); let next = level.bump(&version); let mut bump = Bump { next, version }; bump.bump_file("./Cargo.toml", bump::cargo)?; bump.bump_file("./README.md", bump::replace)?; bump.bump_file("./CHANGELOG.md", bump::changelog)?; bump.bump_file("./pkg/archlinux/projectr/PKGBUILD", bump::replace)?; bump.bump_file("./pkg/archlinux/projectr-bin/PKGBUILD", bump::replace)?; bump.bump_file("./pkg/archlinux/projectr-git/PKGBUILD", bump::vsc_pkgbuild)?; Ok(bump) } pub fn commit(git_commit_args: Vec) -> Result<()> { let git_commit = Command::new("git") .arg("commit") .args(git_commit_args) .status()?; anyhow::ensure!(git_commit.success(), "Failed to commit changes"); Ok(()) } pub fn tag(from: Version, to: Version) -> Result { let tag_name = format!("v{}", to); let shortlog_child = Command::new("git") .arg("shortlog") .arg(format!("v{}..HEAD", from)) .arg("--abbrev=7") .stdout(Stdio::piped()) .spawn()?; let git_commit = Command::new("git") .arg("tag") .arg("-s") .arg(&tag_name) .arg("--file") .arg("-") .stdin(Stdio::from(shortlog_child.stdout.unwrap())) // Pipe through. .status()?; anyhow::ensure!(git_commit.success(), "Failed to commit changes"); Ok(tag_name) } }