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, Subcommand)] pub enum Step { /// Bump version in package files and commit changes. Bump { /// Level of version bump version. #[arg(required = false)] level: bump::Level, }, /// Make a release commit. Commit { /// Options passed to git commit. #[arg(last = true)] git_commit_args: Vec, }, /// Create git tag for release. Tag, } #[derive(Debug, Clone, Args)] pub struct Release { #[command(subcommand)] step: Step, } impl Release { pub fn run(self) -> Result<()> { match self.step { Step::Bump { level } => { let bump = Self::bump(level)?; println!("Bumped version: {bump}"); } Step::Commit { git_commit_args } => { let version = PKG_VER.parse()?; Self::commit(version, git_commit_args)? } Step::Tag => { let stdout = Command::new("git") .arg("describe") .arg("--abbrev=0") .output()? .stdout; let prev = std::str::from_utf8(&stdout)? .trim() .trim_start_matches('v') .parse()?; let next = PKG_VER.parse()?; 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)?; let cargo_update = Command::new("cargo") .arg("update") .arg("--workspace") .status()?; anyhow::ensure!(cargo_update.success(), "Failed to update cargo lockfile"); let git_added = Command::new("git") .arg("add") .arg("./Cargo.lock") .status()?; anyhow::ensure!(git_added.success(), "Failed to add Cargo.lock to git"); Ok(bump) } pub fn commit(version: Version, git_commit_args: Vec) -> Result<()> { let git_commit = Command::new("git") .arg("commit") .arg("-em") .arg(format!("chore: release projectr version {version}")) .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) } }