use crate::error::Result;
use command_group::CommandGroup;
use governor_core::traits::registry::CratePackage;
use serde_json::json;
use std::time::Duration;
use tokio::time::sleep;
#[derive(Debug, Clone, serde::Serialize)]
#[allow(dead_code)] pub struct PublishedCrate {
pub name: String,
pub version: String,
pub publish_time_ms: u64,
pub crates_io_url: String,
}
#[derive(Debug, Clone, serde::Serialize)]
#[allow(dead_code)] pub struct PublishSkipped {
pub name: String,
pub version: String,
pub reason: String,
}
pub async fn check_if_published(name: &str, version: &str) -> Result<bool> {
use std::process::Command;
let url = format!("https://crates.io/api/v1/crates/{name}/{version}");
let output = Command::new("curl")
.args(["-s", "-f", &url])
.output()
.map_err(|e| crate::error::Error::Io(format!("Failed to run curl: {e}")))?;
Ok(output.status.success())
}
#[allow(dead_code)] pub async fn publish_crate_with_retries(
package: &CratePackage,
max_retries: usize,
) -> Result<Option<String>> {
let mut last_error = None;
for attempt in 0..max_retries {
if attempt > 0 {
sleep(Duration::from_secs(5)).await;
}
match publish_crate(package) {
Ok(url) => return Ok(Some(url)),
Err(e) => last_error = Some(e.to_string()),
}
if last_error.is_some() && attempt == max_retries - 1 {
return Err(crate::error::Error::Registry(
last_error.unwrap_or_default(),
));
}
}
Err(crate::error::Error::Registry(
last_error.unwrap_or_default(),
))
}
#[allow(dead_code)] fn publish_crate(package: &CratePackage) -> Result<String> {
let start = std::time::Instant::now();
if package.dry_run {
return Ok(format!(
"https://crates.io/crates/{}/{}",
package.name, package.version
));
}
let args = vec!["publish", "--allow-dirty", "-p", &package.name];
let output = std::process::Command::new("cargo")
.args(&args)
.current_dir(
package
.manifest_path
.parent()
.unwrap_or_else(|| std::path::Path::new(".")),
)
.group_spawn()
.map_err(|e| crate::error::Error::Io(format!("Failed to spawn cargo publish: {e}")))?
.wait_with_output()
.map_err(|e| crate::error::Error::Io(format!("Failed to wait for cargo publish: {e}")))?;
let _duration = start.elapsed().as_millis().try_into().unwrap_or(u64::MAX);
if output.status.success() {
Ok(format!(
"https://crates.io/crates/{}/{}",
package.name, package.version
))
} else {
let stderr = String::from_utf8_lossy(&output.stderr);
Err(crate::error::Error::Registry(format!(
"Failed to publish {}: {}",
package.name, stderr
)))
}
}
#[allow(dead_code)] pub struct PublishRegistryConfig {
#[allow(dead_code)]
pub delay: u64,
#[allow(dead_code)]
pub max_retries: usize,
}
impl PublishRegistryConfig {
#[allow(dead_code)]
pub fn new(delay: u64, max_retries: usize, _token: Option<String>) -> Self {
Self { delay, max_retries }
}
}
#[allow(dead_code)]
pub fn print_publish_error(check: &str, message: &str) {
let response = json!({
"success": false,
"command": "publish",
"error": {
"code": "PRE_PUBLISH_CHECK_FAILED",
"category": "test_failed",
"message": format!("Pre-publish {check} check failed: {}", message),
}
});
println!("{}", serde_json::to_string_pretty(&response).unwrap());
}