1use crate::cmd::{run_cmd, RunCommandError};
12use crate::{
13 get_github_sha, CrateRegistry, GetCrateVersionsError, GetLocalVersionError,
14 Package, Repo, VarError,
15};
16use std::fmt::{self, Display, Formatter};
17use std::process::Command;
18
19#[derive(Debug)]
21pub enum ReleasePackagesError {
22 Env(VarError),
24
25 Git(Box<dyn std::error::Error + Send + Sync + 'static>),
27
28 Package {
30 package: String,
32 cause: ReleasePackageError,
34 },
35}
36
37impl Display for ReleasePackagesError {
38 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
39 match self {
40 Self::Env(_) => write!(f, "environment error"),
41 Self::Git(_) => write!(f, "git error"),
42 Self::Package { package, .. } => {
43 write!(f, "failed to release package {package}")
44 }
45 }
46 }
47}
48
49impl std::error::Error for ReleasePackagesError {
50 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
51 match self {
52 Self::Env(err) => Some(err),
53 Self::Git(err) => Some(&**err),
54 Self::Package { cause, .. } => Some(cause),
55 }
56 }
57}
58
59pub fn release_packages(
68 packages: &[Package],
69) -> Result<(), ReleasePackagesError> {
70 let commit_sha = get_github_sha().map_err(ReleasePackagesError::Env)?;
71
72 let repo =
73 Repo::open().map_err(|err| ReleasePackagesError::Git(Box::new(err)))?;
74 repo.fetch_git_tags()
75 .map_err(|err| ReleasePackagesError::Git(Box::new(err)))?;
76
77 for package in packages {
78 auto_release_package(&repo, package, &commit_sha).map_err(|err| {
79 ReleasePackagesError::Package {
80 package: package.name().to_string(),
81 cause: err,
82 }
83 })?;
84 }
85
86 Ok(())
87}
88
89#[derive(Debug)]
91pub enum ReleasePackageError {
92 LocalVersion(GetLocalVersionError),
94
95 RemoteVersions(GetCrateVersionsError),
97
98 Publish(RunCommandError),
100
101 Git(RunCommandError),
103}
104
105impl Display for ReleasePackageError {
106 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
107 match self {
108 Self::LocalVersion(_) => {
109 write!(f, "failed to get local package version")
110 }
111 Self::RemoteVersions(_) => {
112 write!(f, "failed to get the published package versions")
113 }
114 Self::Publish(_) => write!(f, "failed to publish the crate"),
115 Self::Git(_) => write!(f, "git error"),
116 }
117 }
118}
119
120impl std::error::Error for ReleasePackageError {
121 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
122 match self {
123 Self::LocalVersion(err) => Some(err),
124 Self::RemoteVersions(err) => Some(err),
125 Self::Publish(err) => Some(err),
126 Self::Git(err) => Some(err),
127 }
128 }
129}
130
131pub fn auto_release_package(
136 repo: &Repo,
137 package: &Package,
138 commit_sha: &str,
139) -> Result<(), ReleasePackageError> {
140 let local_version = package
141 .get_local_version()
142 .map_err(ReleasePackageError::LocalVersion)?;
143 println!("local version of {} is {local_version}", package.name());
144
145 if does_crates_io_release_exist(package, &local_version)
147 .map_err(ReleasePackageError::RemoteVersions)?
148 {
149 println!(
150 "{}-{local_version} has already been published",
151 package.name()
152 );
153 } else {
154 publish_package(package).map_err(ReleasePackageError::Publish)?;
155 }
156
157 let tag = package.get_git_tag_name(&local_version);
159 if repo
160 .does_git_tag_exist(&tag)
161 .map_err(ReleasePackageError::Git)?
162 {
163 println!("git tag {tag} already exists");
164 } else {
165 repo.make_and_push_git_tag(&tag, commit_sha)
166 .map_err(ReleasePackageError::Git)?;
167 }
168
169 Ok(())
170}
171
172pub fn does_crates_io_release_exist(
174 package: &Package,
175 local_version: &str,
176) -> Result<bool, GetCrateVersionsError> {
177 let cargo = CrateRegistry::new();
178 let remote_versions = match cargo.get_crate_versions(package.name()) {
179 Ok(v) => v,
180 Err(GetCrateVersionsError::NotPublished) => Vec::new(),
181 Err(err) => return Err(err),
182 };
183
184 if remote_versions.contains(&local_version.to_string()) {
185 return Ok(true);
186 }
187
188 Ok(false)
189}
190
191pub fn publish_package(package: &Package) -> Result<(), RunCommandError> {
193 let mut cmd = Command::new("cargo");
194 cmd.args(["publish", "--package", package.name()]);
195 run_cmd(cmd)
196}