use anyhow::{bail, Context, Result};
use std::fs;
pub fn handle_publish_command(
_registry: &str,
_version: Option<&str>,
dry_run: bool,
allow_dirty: bool,
verbose: bool,
) -> Result<()> {
use semver::Version;
use serde::Deserialize;
use std::env;
#[derive(Debug, Deserialize)]
struct PackageManifest {
package: PackageMetadata,
}
#[derive(Debug, Deserialize)]
struct PackageMetadata {
name: String,
version: String,
authors: Vec<String>,
description: String,
license: String,
repository: Option<String>,
}
let manifest_path = env::current_dir()?.join("Ruchy.toml");
if !manifest_path.exists() {
bail!("Ruchy.toml not found in current directory.\nRun 'ruchy publish' from your package root.");
}
if verbose {
eprintln!("Reading manifest: {}", manifest_path.display());
}
let manifest_content =
fs::read_to_string(&manifest_path).context("Failed to read Ruchy.toml")?;
let manifest: PackageManifest = toml::from_str(&manifest_content)
.context("Failed to parse Ruchy.toml.\nEnsure all required fields are present: name, version, authors, description, license")?;
if manifest.package.name.is_empty() {
bail!("Package name cannot be empty in Ruchy.toml");
}
if manifest.package.authors.is_empty() {
bail!("At least one author is required in Ruchy.toml");
}
if manifest.package.description.is_empty() {
bail!("Package description cannot be empty in Ruchy.toml");
}
if manifest.package.license.is_empty() {
bail!("Package license cannot be empty in Ruchy.toml");
}
Version::parse(&manifest.package.version).context(format!(
"Invalid version '{}' in Ruchy.toml.\nMust be valid semver (e.g., 1.0.0, 0.2.3)",
manifest.package.version
))?;
if verbose {
eprintln!("Manifest validation passed");
eprintln!(" Name: {}", manifest.package.name);
eprintln!(" Version: {}", manifest.package.version);
eprintln!(" Authors: {}", manifest.package.authors.join(", "));
eprintln!(" Description: {}", manifest.package.description);
eprintln!(" License: {}", manifest.package.license);
if let Some(repo) = &manifest.package.repository {
eprintln!(" Repository: {}", repo);
}
}
if dry_run {
println!(
"Dry-run mode: Validating package '{}'",
manifest.package.name
);
println!("Package validation successful");
println!(
"Package: {} v{}",
manifest.package.name, manifest.package.version
);
println!("Authors: {}", manifest.package.authors.join(", "));
println!("License: {}", manifest.package.license);
println!("\nWould publish package (skipped in dry-run mode)");
Ok(())
} else {
println!(
"Publishing {} v{}...",
manifest.package.name, manifest.package.version
);
use std::process::Command;
let mut cargo_cmd = Command::new("cargo");
cargo_cmd.arg("publish");
if verbose {
cargo_cmd.arg("--verbose");
}
if allow_dirty {
cargo_cmd.arg("--allow-dirty");
}
let status = cargo_cmd
.status()
.context("Failed to execute 'cargo publish'. Ensure cargo is installed.")?;
if status.success() {
println!(
"Successfully published {} v{} to crates.io",
manifest.package.name, manifest.package.version
);
Ok(())
} else {
bail!("cargo publish failed with exit code: {}", status);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_handle_publish_command_no_manifest() {
let result = handle_publish_command("https://crates.io", None, true, false, false);
let _ = result;
}
#[test]
fn test_handle_publish_dry_run() {
let result = handle_publish_command("https://crates.io", None, true, false, false);
let _ = result;
}
#[test]
fn test_handle_publish_dry_run_verbose() {
let result = handle_publish_command("https://crates.io", None, true, false, true);
let _ = result;
}
#[test]
fn test_handle_publish_with_version() {
let result = handle_publish_command("https://crates.io", Some("1.0.0"), true, false, false);
let _ = result;
}
#[test]
fn test_handle_publish_allow_dirty() {
let result = handle_publish_command("https://crates.io", None, true, true, false);
let _ = result;
}
#[test]
fn test_handle_publish_all_flags() {
let result = handle_publish_command("https://crates.io", Some("2.0.0"), true, true, true);
let _ = result;
}
#[test]
fn test_handle_publish_custom_registry() {
let result = handle_publish_command("https://my-registry.com", None, true, false, false);
let _ = result;
}
#[test]
fn test_handle_publish_various_versions() {
let versions = ["0.0.1", "1.0.0", "2.5.3", "10.20.30"];
for version in &versions {
let result =
handle_publish_command("https://crates.io", Some(version), true, false, false);
let _ = result;
}
}
#[test]
fn test_handle_publish_no_dry_run() {
let result = handle_publish_command("https://crates.io", None, false, false, false);
let _ = result;
}
#[test]
fn test_handle_publish_empty_registry() {
let result = handle_publish_command("", None, true, false, false);
let _ = result;
}
#[test]
fn test_handle_publish_verbose_all_flags() {
let result =
handle_publish_command("https://crates.io", Some("3.0.0-alpha"), true, true, true);
let _ = result;
}
#[test]
fn test_handle_publish_prerelease_versions() {
let versions = ["1.0.0-alpha", "1.0.0-beta.1", "1.0.0-rc.1", "2.0.0-preview"];
for version in &versions {
let result =
handle_publish_command("https://crates.io", Some(version), true, false, false);
let _ = result;
}
}
#[test]
fn test_handle_publish_various_registries() {
let registries = [
"https://crates.io",
"https://registry.npmjs.org",
"http://localhost:8080",
"https://my-private-registry.com/api",
];
for registry in ®istries {
let result = handle_publish_command(registry, None, true, false, false);
let _ = result;
}
}
#[test]
fn test_handle_publish_flag_combinations() {
let combos = [
(true, false, false), (true, true, false), (true, false, true), (true, true, true), (false, false, false), (false, true, false), (false, false, true), (false, true, true), ];
for (dry_run, allow_dirty, verbose) in &combos {
let result =
handle_publish_command("https://crates.io", None, *dry_run, *allow_dirty, *verbose);
let _ = result;
}
}
#[test]
fn test_handle_publish_semver_build_metadata() {
let versions = ["1.0.0+build.123", "2.0.0+20230101"];
for version in &versions {
let result =
handle_publish_command("https://crates.io", Some(version), true, false, false);
let _ = result;
}
}
#[test]
fn test_handle_publish_minimal_version() {
let result = handle_publish_command("https://crates.io", Some("0.0.1"), true, false, false);
let _ = result;
}
#[test]
fn test_handle_publish_large_version() {
let result =
handle_publish_command("https://crates.io", Some("999.999.999"), true, false, false);
let _ = result;
}
}