1use std::{env, fs, process::exit};
4
5use clap::Parser;
6use dialoguer::{theme::ColorfulTheme, Confirm};
7use gitcc_core::{ChangelogBuildOptions, Config, StatusShow, TEMPLATE_CHANGELOG_STD};
8
9use crate::{error, info, success, warn};
10
11#[derive(Debug, Parser)]
13pub struct ReleaseArgs {
14 #[arg(long)]
16 pub dry_run: bool,
17 #[arg(long)]
19 pub v1: bool,
20}
21
22pub fn run(args: ReleaseArgs) -> anyhow::Result<()> {
24 let cwd = env::current_dir()?;
26 let cfg_file = Config::load_from_fs(&cwd)?;
27 let cfg = if let Some(cfg) = cfg_file {
28 cfg
29 } else {
30 info!("using default config");
31 Config::default()
32 };
33
34 let dirty_files = gitcc_core::git_status(&cwd, StatusShow::IndexAndWorkdir)?;
36 if !dirty_files.is_empty() {
37 warn!("repo is dirty");
38 match Confirm::with_theme(&ColorfulTheme::default())
39 .with_prompt("continue ?")
40 .report(true)
41 .default(false)
42 .interact()?
43 {
44 true => {}
45 false => {
46 exit(1);
47 }
48 }
49 }
50
51 let commit_history = gitcc_core::commit_history(&cwd, &cfg)?;
53 let mut next_version = commit_history.next_version_str();
54 if args.v1 {
55 warn!(format!("forcing 1st stable release"));
56 next_version = "v1.0.0".to_string();
57 }
58 info!(format!("next version: {}", next_version));
59
60 info!("before committing, bump the packages manually, run tests, etc...");
63 match Confirm::with_theme(&ColorfulTheme::default())
64 .with_prompt("continue ?")
65 .report(true)
66 .default(false)
67 .interact()?
68 {
69 true => {}
70 false => {
71 exit(1);
72 }
73 }
74
75 let changelog = gitcc_core::build_changelog(
77 &cwd,
78 &cfg,
79 &commit_history,
80 Some(ChangelogBuildOptions {
81 origin_name: None,
82 all: false,
83 next_version: Some(next_version.clone()),
84 }),
85 )?;
86 let changelog_str = match changelog.render(TEMPLATE_CHANGELOG_STD) {
87 Ok(s) => s,
88 Err(err) => {
89 error!(format!("failed to generate the changelog: {err}"));
90 exit(1);
91 }
92 };
93 if !args.dry_run {
94 let root_dir = gitcc_core::get_root_dir(&cwd).expect("not a git repo");
95 match fs::write(root_dir.join("CHANGELOG.md"), changelog_str) {
96 Ok(_ok) => {
97 success!("changelog written to file")
98 }
99 Err(err) => {
100 error!(format!("failed to write the changelog: {err}"));
101 exit(1);
102 }
103 }
104 } else {
105 info!("(dry-run) changelog not written to file")
106 }
107
108 if !args.dry_run {
110 for cmd in cfg.release.bump_cmds {
111 match gitcc_core::exec_bump_command(&cmd, &next_version) {
112 Ok(_ok) => {
113 success!(format!("executed bump command: {cmd}"));
114 }
115 Err(err) => {
116 error!(format!("failed to bump packages: {err}"));
117 exit(1);
118 }
119 }
120 }
121 } else {
122 info!("(dry-run) skipped executing bump commands")
123 }
124
125 if !args.dry_run {
127 match gitcc_core::add_all_changes(&cwd) {
128 Ok(_ok) => {}
129 Err(err) => {
130 error!(format!("failed to add the changes: {err}"));
131 exit(1);
132 }
133 }
134 match gitcc_core::commit_changes(&cwd, &format!("chore(release): Release {next_version}")) {
135 Ok(_commit) => {
136 success!("commited changes");
137 }
138 Err(err) => {
139 error!(format!("failed to commit: {err}"));
140 exit(1);
141 }
142 }
143 } else {
144 info!("(dry-run) changes not committed");
145 }
146
147 if !args.dry_run {
149 match gitcc_core::set_annotated_tag(&cwd, &next_version, &format!("Release {next_version}"))
150 {
151 Ok(_ok) => {
152 success!(format!("tag {} added", next_version));
153 }
154 Err(err) => {
155 error!(format!("failed to add the changes: {err}"));
156 exit(1);
157 }
158 }
159 } else {
160 info!(format!("(dry-run) tag '{}' not set", next_version));
161 }
162
163 warn!("=> Push the changes with: git push --follow-tags");
165 warn!("=> Create the github release");
166 warn!("=> Publish the updated packages (crates.io, npm, brew, etc...)");
167
168 Ok(())
169}