use crate::commands::{make_client, split_name_spec};
use clap::{Args, Subcommand};
use miette::{Context, miette};
#[derive(Debug, Args)]
pub struct DistTagArgs {
#[command(subcommand)]
pub command: DistTagCommand,
#[command(flatten)]
pub network: crate::cli_args::NetworkArgs,
}
#[derive(Debug, Subcommand)]
pub enum DistTagCommand {
Add {
spec: String,
tag: Option<String>,
},
Ls {
package: Option<String>,
},
#[command(visible_alias = "remove")]
Rm {
package: String,
tag: String,
},
}
pub async fn run(args: DistTagArgs) -> miette::Result<()> {
args.network.install_overrides();
match args.command {
DistTagCommand::Add { spec, tag } => add(&spec, tag.as_deref()).await,
DistTagCommand::Rm { package, tag } => rm(&package, &tag).await,
DistTagCommand::Ls { package } => ls(package.as_deref()).await,
}
}
async fn add(spec: &str, tag: Option<&str>) -> miette::Result<()> {
let (name, version) = split_name_spec(spec);
let version = version.ok_or_else(|| {
miette!(
"expected `name@version`, got `{spec}`\nhelp: `aube dist-tag add` needs an exact version, e.g. `react@18.2.0`"
)
})?;
if version.is_empty() {
return Err(miette!(
"version is empty in `{spec}`\nhelp: specify an exact version like `react@18.2.0`"
));
}
let tag = tag.unwrap_or("latest");
let cwd = crate::dirs::project_root_or_cwd().unwrap_or_else(|_| std::path::PathBuf::from("."));
let client = make_client(&cwd);
client
.put_dist_tag(name, tag, version)
.await
.map_err(|e| match e {
aube_registry::Error::NotFound(n) => miette!("package not found: {n}"),
aube_registry::Error::Unauthorized => miette!(
"authentication required for {name}\nhelp: run `aube login` first, then retry"
),
other => miette!("failed to set {name}@{tag} -> {version}: {other}"),
})?;
println!("+{tag}: {name}@{version}");
Ok(())
}
async fn rm(package: &str, tag: &str) -> miette::Result<()> {
let (name, version_spec) = split_name_spec(package);
if version_spec.is_some() {
return Err(miette!(
"expected a bare package name, got `{package}`\nhelp: `aube dist-tag rm` takes just the package name — the tag to remove is a separate argument"
));
}
let cwd = crate::dirs::project_root_or_cwd().unwrap_or_else(|_| std::path::PathBuf::from("."));
let client = make_client(&cwd);
client
.delete_dist_tag(name, tag)
.await
.map_err(|e| match e {
aube_registry::Error::NotFound(_) => {
miette!("no such tag: {name}@{tag}")
}
aube_registry::Error::Unauthorized => miette!(
"authentication required for {name}\nhelp: run `aube login` first, then retry"
),
other => miette!("failed to remove {name}@{tag}: {other}"),
})?;
println!("-{tag}: {name}");
Ok(())
}
async fn ls(package: Option<&str>) -> miette::Result<()> {
let cwd = crate::dirs::project_root_or_cwd().unwrap_or_else(|_| std::path::PathBuf::from("."));
let name: String = match package {
Some(pkg) => {
let (n, version_spec) = split_name_spec(pkg);
if version_spec.is_some() {
return Err(miette!(
"expected a bare package name, got `{pkg}`\nhelp: `aube dist-tag ls` takes just the package name"
));
}
n.to_string()
}
None => {
let manifest_path = cwd.join("package.json");
let manifest = aube_manifest::PackageJson::from_path(&manifest_path)
.map_err(miette::Report::new)
.wrap_err_with(|| format!("failed to read {}", manifest_path.display()))?;
manifest.name.ok_or_else(|| {
miette!(
"package.json has no `name` field\nhelp: pass a package name explicitly (`aube dist-tag ls <name>`)"
)
})?
}
};
let client = make_client(&cwd);
let tags = client.fetch_dist_tags(&name).await.map_err(|e| match e {
aube_registry::Error::NotFound(n) => miette!("package not found: {n}"),
aube_registry::Error::Unauthorized => {
miette!("authentication required for {name}\nhelp: run `aube login` first, then retry")
}
other => miette!("failed to fetch dist-tags for {name}: {other}"),
})?;
if tags.is_empty() {
return Err(miette!(
"no dist-tags found for {name}\nhelp: this package has never been published"
));
}
let width = tags.keys().map(|k| k.len()).max().unwrap_or(0);
for (tag, version) in &tags {
println!("{tag:<width$}: {version}");
}
Ok(())
}