1use anyhow::Result;
10
11use changepacks_core::UpdateType;
12use clap::{Parser, Subcommand, ValueEnum};
13
14use crate::{
15 commands::{
16 ChangepackArgs, CheckArgs, ConfigArgs, InitArgs, PublishArgs, UpdateArgs,
17 handle_changepack, handle_check, handle_config, handle_init, handle_publish, handle_update,
18 },
19 options::FilterOptions,
20};
21pub mod commands;
22mod context;
23pub use context::*;
24mod finders;
25pub mod options;
26pub mod prompter;
27
28pub use prompter::UserCancelled;
29
30#[derive(ValueEnum, Debug, Clone)]
31enum CliUpdateType {
32 Major,
33 Minor,
34 Patch,
35}
36
37impl From<CliUpdateType> for UpdateType {
38 fn from(value: CliUpdateType) -> Self {
39 match value {
40 CliUpdateType::Major => Self::Major,
41 CliUpdateType::Minor => Self::Minor,
42 CliUpdateType::Patch => Self::Patch,
43 }
44 }
45}
46
47#[derive(Parser, Debug)]
48#[command(
49 name = "changepacks",
50 author,
51 version,
52 about = "A unified version management and changelog tool for multi-language projects",
53 help_template = "{name} {version}\n{about}\n\n{usage-heading} {usage}\n\n{all-args}"
54)]
55struct Cli {
56 #[command(subcommand)]
57 command: Option<Commands>,
58
59 #[arg(short, long)]
60 filter: Option<FilterOptions>,
61
62 #[arg(short, long, default_value = "false")]
63 remote: bool,
64
65 #[arg(short, long, default_value = "false")]
66 yes: bool,
67
68 #[arg(short, long)]
69 message: Option<String>,
70
71 #[arg(short, long)]
72 update_type: Option<CliUpdateType>,
73}
74
75#[derive(Subcommand, Debug)]
76enum Commands {
77 Init(InitArgs),
78 Check(CheckArgs),
79 Update(UpdateArgs),
80 Config(ConfigArgs),
81 Publish(PublishArgs),
82}
83
84pub async fn main(args: &[String]) -> Result<()> {
87 let cli = Cli::parse_from(args);
88 if let Some(command) = cli.command {
89 match command {
90 Commands::Init(args) => handle_init(&args).await?,
91 Commands::Check(args) => handle_check(&args).await?,
92 Commands::Update(args) => handle_update(&args).await?,
93 Commands::Config(args) => handle_config(&args).await?,
94 Commands::Publish(args) => handle_publish(&args).await?,
95 }
96 } else {
97 handle_changepack(&ChangepackArgs {
98 filter: cli.filter,
99 remote: cli.remote,
100 yes: cli.yes,
101 message: cli.message,
102 update_type: cli.update_type.map(Into::into),
103 })
104 .await?;
105 }
106 Ok(())
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112 use rstest::rstest;
113
114 #[rstest]
115 #[case(CliUpdateType::Major, UpdateType::Major)]
116 #[case(CliUpdateType::Minor, UpdateType::Minor)]
117 #[case(CliUpdateType::Patch, UpdateType::Patch)]
118 fn test_cli_update_type_to_update_type(
119 #[case] cli_type: CliUpdateType,
120 #[case] expected: UpdateType,
121 ) {
122 let result: UpdateType = cli_type.into();
123 assert_eq!(result, expected);
124 }
125
126 #[test]
128 fn test_cli_parsing_init() {
129 use clap::Parser;
130 let cli = Cli::parse_from(["changepacks", "init"]);
131 assert!(matches!(cli.command, Some(Commands::Init(_))));
132 }
133
134 #[test]
135 fn test_cli_parsing_check() {
136 use clap::Parser;
137 let cli = Cli::parse_from(["changepacks", "check"]);
138 assert!(matches!(cli.command, Some(Commands::Check(_))));
139 }
140
141 #[test]
142 fn test_cli_parsing_update() {
143 use clap::Parser;
144 let cli = Cli::parse_from(["changepacks", "update", "--dry-run"]);
145 assert!(matches!(cli.command, Some(Commands::Update(_))));
146 }
147
148 #[test]
149 fn test_cli_parsing_config() {
150 use clap::Parser;
151 let cli = Cli::parse_from(["changepacks", "config"]);
152 assert!(matches!(cli.command, Some(Commands::Config(_))));
153 }
154
155 #[test]
156 fn test_cli_parsing_publish() {
157 use clap::Parser;
158 let cli = Cli::parse_from(["changepacks", "publish", "--dry-run"]);
159 assert!(matches!(cli.command, Some(Commands::Publish(_))));
160 }
161
162 #[test]
163 fn test_cli_parsing_default_with_options() {
164 use clap::Parser;
165 let cli = Cli::parse_from([
166 "changepacks",
167 "--yes",
168 "--message",
169 "test",
170 "--update-type",
171 "patch",
172 ]);
173 assert!(cli.command.is_none());
174 assert!(cli.yes);
175 assert_eq!(cli.message, Some("test".to_string()));
176 assert!(matches!(cli.update_type, Some(CliUpdateType::Patch)));
177 }
178
179 #[test]
180 fn test_cli_parsing_with_filter() {
181 use clap::Parser;
182 let cli = Cli::parse_from(["changepacks", "--filter", "package"]);
183 assert!(cli.command.is_none());
184 assert!(matches!(cli.filter, Some(FilterOptions::Package)));
185 }
186
187 #[test]
188 fn test_cli_parsing_with_remote() {
189 use clap::Parser;
190 let cli = Cli::parse_from(["changepacks", "--remote"]);
191 assert!(cli.remote);
192 }
193}