qtcloud_devops_cli/release/
publish.rs1use std::path::Path;
2
3use super::util::{self, Registry};
4
5pub fn publish(version: &str, repo_path: &Path, yes: bool, registry: Option<Registry>) -> Result<(), Box<dyn std::error::Error>> {
24 if !util::validate_version(version) {
25 return Err(format!("版本号格式错误: {}", version).into());
26 }
27
28 let changelog_path = repo_path.join("CHANGELOG.md");
29 let precheck_errors = util::precheck_version_changelog(version, &changelog_path);
30 if !precheck_errors.is_empty() {
31 return Err(precheck_errors.join("\n").into());
32 }
33
34 if !yes && !util::confirm_release(version, false) {
35 return Err("已取消发布".into());
36 }
37
38 if !util::create_tag(version, repo_path) {
39 return Err(format!("创建标签 {} 失败", version).into());
40 }
41 if !util::push_tag(version, repo_path) {
42 util::rollback_tag(version, repo_path);
43 return Err(format!("推送标签 {} 失败", version).into());
44 }
45 println!("✓ 标签 {} 已创建并推送", version);
46
47 let notes = util::extract_notes(version, &changelog_path);
48 if let Some(repo) = util::get_remote_repo(repo_path) {
49 if !util::create_release(version, notes.as_deref().unwrap_or(""), &repo) {
50 util::rollback_tag(version, repo_path);
51 return Err("创建 GitHub Release 失败".into());
52 }
53 println!("✓ GitHub Release {} 已创建", version);
54 println!(" https://github.com/{}/releases/tag/{}", repo, version);
55 }
56 if let Some(reg) = registry {
57 println!(" {:?} 由 CI 自动发布,无需本地操作", reg);
58 }
59 println!("✓ 版本 {} 已发布", version);
60 Ok(())
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66 use std::path::Path;
67
68 fn git_init(path: &Path) {
69 std::process::Command::new("git").args(["init", "-b", "main"]).current_dir(path).output().unwrap();
70 std::process::Command::new("git").args(["config", "user.email", "test@test.com"]).current_dir(path).output().unwrap();
71 std::process::Command::new("git").args(["config", "user.name", "Test"]).current_dir(path).output().unwrap();
72 }
73
74 fn git_commit(path: &Path, msg: &str) {
75 std::fs::write(path.join("file"), msg).unwrap();
76 std::process::Command::new("git").args(["add", "."]).current_dir(path).output().unwrap();
77 std::process::Command::new("git").args(["commit", "-m", msg]).current_dir(path).output().unwrap();
78 }
79
80 #[test] fn test_publish_rejects_invalid_version() { assert!(publish("bad", tempfile::tempdir().unwrap().path(), true, None).is_err()); }
81 #[test] fn test_publish_rejects_missing_changelog() { let d = tempfile::tempdir().unwrap(); git_init(d.path()); git_commit(d.path(), "init"); let e = publish("v1.0.0", d.path(), true, None).unwrap_err().to_string(); assert!(e.contains("CHANGELOG")); }
82 #[test] fn test_publish_formal_with_yes() { let d = tempfile::tempdir().unwrap(); let r = publish("v1.0.0", d.path(), true, None); assert!(r.is_ok() || r.is_err()); }
83 #[test] fn test_publish_prerelease_with_yes() { let d = tempfile::tempdir().unwrap(); git_init(d.path()); git_commit(d.path(), "init"); std::fs::write(d.path().join("CHANGELOG.md"), "## [1.0.0-rc.1]\n\ncontent\n").unwrap(); let r = publish("v1.0.0-rc.1", d.path(), true, None); assert!(r.is_ok() || r.is_err()); }
84}