use std::{thread, time::Duration};
use crate::utils::{
basic_checks, cargo, create_http_client, dag, filter_private, info, is_published,
package_registry, should_remove_dev_deps, warn, DevDependencyRemover, Error, RegistryOpt,
Result, VersionOpt, INTERNAL_ERR,
};
use camino::Utf8PathBuf;
use cargo_metadata::Metadata;
use clap::Parser;
#[derive(Debug, Parser)]
#[clap(next_help_heading = "PUBLISH OPTIONS")]
pub struct Publish {
#[clap(flatten)]
version: VersionOpt,
#[clap(long, alias = "from-git")]
publish_as_is: bool,
#[clap(long, hide = true)]
skip_published: bool,
#[clap(long)]
no_verify: bool,
#[clap(long)]
allow_dirty: bool,
#[clap(long)]
no_remove_dev_deps: bool,
#[clap(long)]
dry_run: bool,
#[clap(flatten)]
registry: RegistryOpt,
#[clap(long)]
locked: bool,
#[clap(long, value_name = "SECONDS")]
publish_interval: Option<u64>,
}
impl Publish {
pub fn run(mut self, metadata: Metadata) -> Result {
if self.dry_run {
warn!(
"Dry run doesn't check that all dependencies have been published.",
""
);
if !self.publish_as_is {
warn!("Dry run doesn't perform versioning.", "");
self.publish_as_is = true;
}
}
let pkgs = if !self.publish_as_is {
self.version
.do_versioning(&metadata)?
.iter()
.map(|x| {
(
metadata
.packages
.iter()
.find(|y| x.0 == &y.name)
.expect(INTERNAL_ERR)
.clone(),
x.1.to_string(),
)
})
.collect::<Vec<_>>()
} else {
metadata
.packages
.iter()
.map(|x| (x.clone(), x.version.to_string()))
.collect()
};
let (names, visited) = dag(&pkgs);
let visited = filter_private(visited, &pkgs);
let http_client = create_http_client(&metadata.workspace_root, &self.registry.token)?;
for p in &visited {
let (pkg, version) = names.get(p).expect(INTERNAL_ERR);
let name = pkg.name.clone();
if self.dry_run {
info!("checking", name);
if !self.no_verify && !self.build(&metadata.workspace_root, p)? {
warn!("build failed", "");
}
basic_checks(pkg)?;
}
let mut args = vec!["publish"];
let name_ver = format!("{} v{}", name, version);
let index_url = package_registry(&metadata, self.registry.registry.as_ref(), pkg)?;
if is_published(&http_client, index_url, &name, version)? {
info!("already published", name_ver);
continue;
}
if self.dry_run {
args.push("--dry-run");
}
if self.no_verify || self.dry_run {
args.push("--no-verify");
}
if self.locked {
args.push("--locked");
}
if let Some(ref registry) = self.registry.registry {
args.push("--registry");
args.push(registry);
}
if let Some(ref token) = self.registry.token {
args.push("--token");
args.push(token);
}
if let Some(interval) = self.publish_interval {
if interval > 0 && !self.dry_run {
info!(
"waiting",
format!("{} seconds before publishing {}", interval, name_ver)
);
thread::sleep(Duration::from_secs(interval));
}
}
let dev_deps_remover =
if self.no_remove_dev_deps || !should_remove_dev_deps(&pkg.dependencies, &pkgs) {
None
} else {
warn!(
"removing dev-deps since some refer to workspace members with versions",
name_ver
);
Some(DevDependencyRemover::remove_dev_deps(p.as_std_path())?)
};
if dev_deps_remover.is_some() || self.allow_dirty {
args.push("--allow-dirty");
}
args.push("--manifest-path");
args.push(p.as_str());
let (_, stderr) = cargo(&metadata.workspace_root, &args, &[])?;
drop(dev_deps_remover);
if !stderr.contains("Uploading") || stderr.contains("error:") {
if self.dry_run {
warn!("publish failed", name_ver);
} else {
return Err(Error::Publish(name));
}
}
if !self.dry_run {
info!("published", name_ver);
}
}
info!("success", "ok");
Ok(())
}
fn build(&self, workspace_root: &Utf8PathBuf, manifest_path: &Utf8PathBuf) -> Result<bool> {
let mut args = vec!["build"];
args.push("--manifest-path");
args.push(manifest_path.as_str());
let (_stdout, stderr) = cargo(workspace_root, &args, &[])?;
if stderr.contains("could not compile") {
return Ok(false);
}
Ok(true)
}
}