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