use crate::cmd::{run_cmd, RunCommandError};
use crate::{
get_github_sha, CrateRegistry, GetCrateVersionsError, GetLocalVersionError,
Package, Repo, VarError,
};
use std::fmt::{self, Display, Formatter};
use std::process::Command;
#[derive(Debug)]
pub enum ReleasePackagesError {
Env(VarError),
Git(Box<dyn std::error::Error + Send + Sync + 'static>),
Package {
package: String,
cause: ReleasePackageError,
},
}
impl Display for ReleasePackagesError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::Env(_) => write!(f, "environment error"),
Self::Git(_) => write!(f, "git error"),
Self::Package { package, .. } => {
write!(f, "failed to release package {package}")
}
}
}
}
impl std::error::Error for ReleasePackagesError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Env(err) => Some(err),
Self::Git(err) => Some(&**err),
Self::Package { cause, .. } => Some(cause),
}
}
}
pub fn release_packages(
packages: &[Package],
) -> Result<(), ReleasePackagesError> {
let commit_sha = get_github_sha().map_err(ReleasePackagesError::Env)?;
let repo =
Repo::open().map_err(|err| ReleasePackagesError::Git(Box::new(err)))?;
repo.fetch_git_tags()
.map_err(|err| ReleasePackagesError::Git(Box::new(err)))?;
for package in packages {
auto_release_package(&repo, package, &commit_sha).map_err(|err| {
ReleasePackagesError::Package {
package: package.name().to_string(),
cause: err,
}
})?;
}
Ok(())
}
#[derive(Debug)]
pub enum ReleasePackageError {
LocalVersion(GetLocalVersionError),
RemoteVersions(GetCrateVersionsError),
Publish(RunCommandError),
Git(RunCommandError),
}
impl Display for ReleasePackageError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Self::LocalVersion(_) => {
write!(f, "failed to get local package version")
}
Self::RemoteVersions(_) => {
write!(f, "failed to get the published package versions")
}
Self::Publish(_) => write!(f, "failed to publish the crate"),
Self::Git(_) => write!(f, "git error"),
}
}
}
impl std::error::Error for ReleasePackageError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::LocalVersion(err) => Some(err),
Self::RemoteVersions(err) => Some(err),
Self::Publish(err) => Some(err),
Self::Git(err) => Some(err),
}
}
}
pub fn auto_release_package(
repo: &Repo,
package: &Package,
commit_sha: &str,
) -> Result<(), ReleasePackageError> {
let local_version = package
.get_local_version()
.map_err(ReleasePackageError::LocalVersion)?;
println!("local version of {} is {local_version}", package.name());
if does_crates_io_release_exist(package, &local_version)
.map_err(ReleasePackageError::RemoteVersions)?
{
println!(
"{}-{local_version} has already been published",
package.name()
);
} else {
publish_package(package).map_err(ReleasePackageError::Publish)?;
}
let tag = package.get_git_tag_name(&local_version);
if repo
.does_git_tag_exist(&tag)
.map_err(ReleasePackageError::Git)?
{
println!("git tag {tag} already exists");
} else {
repo.make_and_push_git_tag(&tag, commit_sha)
.map_err(ReleasePackageError::Git)?;
}
Ok(())
}
pub fn does_crates_io_release_exist(
package: &Package,
local_version: &str,
) -> Result<bool, GetCrateVersionsError> {
let cargo = CrateRegistry::new();
let remote_versions = cargo.get_crate_versions(package.name())?;
if remote_versions.contains(&local_version.to_string()) {
return Ok(true);
}
Ok(false)
}
pub fn publish_package(package: &Package) -> Result<(), RunCommandError> {
let mut cmd = Command::new("cargo");
cmd.args(["publish", "--package", package.name()]);
run_cmd(cmd)
}