vership 0.5.0

Multi-target release orchestrator
Documentation
use std::path::{Path, PathBuf};

use super::ProjectType;
use crate::error::{Error, Result};
use crate::git;

pub struct GoProject;

impl GoProject {
    pub fn new() -> Self {
        Self
    }
}

impl Default for GoProject {
    fn default() -> Self {
        Self::new()
    }
}

impl ProjectType for GoProject {
    fn name(&self) -> &str {
        "Go"
    }

    fn read_version(&self, root: &Path) -> Result<semver::Version> {
        let tag = git::latest_semver_tag(root)?;
        match tag {
            Some(tag_str) => {
                let version_str = tag_str.strip_prefix('v').unwrap_or(&tag_str);
                semver::Version::parse(version_str)
                    .map_err(|e| Error::Version(format!("invalid version tag '{tag_str}': {e}")))
            }
            None => Ok(semver::Version::new(0, 0, 0)),
        }
    }

    fn write_version(&self, _root: &Path, _version: &semver::Version) -> Result<()> {
        Ok(())
    }

    fn verify_lockfile(&self, root: &Path) -> Result<()> {
        let status = std::process::Command::new("go")
            .args(["mod", "verify"])
            .current_dir(root)
            .stdout(std::process::Stdio::null())
            .stderr(std::process::Stdio::null())
            .status()
            .map_err(|e| Error::Other(format!("run go mod verify: {e}")))?;
        if status.success() {
            Ok(())
        } else {
            Err(Error::CheckFailed(
                "go mod verify failed. Run `go mod tidy` to fix.".to_string(),
            ))
        }
    }

    fn sync_lockfile(&self, root: &Path) -> Result<()> {
        let status = std::process::Command::new("go")
            .args(["mod", "tidy"])
            .current_dir(root)
            .stdout(std::process::Stdio::null())
            .stderr(std::process::Stdio::null())
            .status()
            .map_err(|e| Error::Other(format!("run go mod tidy: {e}")))?;
        if status.success() {
            Ok(())
        } else {
            Err(Error::CheckFailed(
                "go mod tidy failed after version bump".to_string(),
            ))
        }
    }

    fn run_lint(&self, root: &Path) -> Result<()> {
        let status = std::process::Command::new("go")
            .args(["vet", "./..."])
            .current_dir(root)
            .stdout(std::process::Stdio::null())
            .stderr(std::process::Stdio::null())
            .status()
            .map_err(|e| Error::Other(format!("run go vet: {e}")))?;
        if status.success() {
            Ok(())
        } else {
            Err(Error::CheckFailed("go vet failed".to_string()))
        }
    }

    fn run_tests(&self, root: &Path) -> Result<()> {
        let status = std::process::Command::new("go")
            .args(["test", "./..."])
            .current_dir(root)
            .stdout(std::process::Stdio::null())
            .stderr(std::process::Stdio::null())
            .status()
            .map_err(|e| Error::Other(format!("run go test: {e}")))?;
        if status.success() {
            Ok(())
        } else {
            Err(Error::CheckFailed("go test failed".to_string()))
        }
    }

    fn modified_files(&self) -> Vec<PathBuf> {
        vec![PathBuf::from("go.mod"), PathBuf::from("go.sum")]
    }

    fn is_tag_versioned(&self) -> bool {
        true
    }
}