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::{CliLanguage, 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 #[arg(short, long, value_enum)]
76 language: Vec<CliLanguage>,
77}
78
79#[derive(Subcommand, Debug)]
80enum Commands {
81 Init(InitArgs),
82 Check(CheckArgs),
83 Update(UpdateArgs),
84 Config(ConfigArgs),
85 Publish(PublishArgs),
86}
87
88pub async fn main(args: &[String]) -> Result<()> {
91 let cli = Cli::parse_from(args);
92 if let Some(command) = cli.command {
93 match command {
94 Commands::Init(args) => handle_init(&args).await?,
95 Commands::Check(args) => handle_check(&args).await?,
96 Commands::Update(args) => handle_update(&args).await?,
97 Commands::Config(args) => handle_config(&args).await?,
98 Commands::Publish(args) => handle_publish(&args).await?,
99 }
100 } else {
101 handle_changepack(&ChangepackArgs {
102 filter: cli.filter,
103 remote: cli.remote,
104 yes: cli.yes,
105 message: cli.message,
106 update_type: cli.update_type.map(Into::into),
107 language: cli.language,
108 })
109 .await?;
110 }
111 Ok(())
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117 use rstest::rstest;
118
119 #[rstest]
120 #[case(CliUpdateType::Major, UpdateType::Major)]
121 #[case(CliUpdateType::Minor, UpdateType::Minor)]
122 #[case(CliUpdateType::Patch, UpdateType::Patch)]
123 fn test_cli_update_type_to_update_type(
124 #[case] cli_type: CliUpdateType,
125 #[case] expected: UpdateType,
126 ) {
127 let result: UpdateType = cli_type.into();
128 assert_eq!(result, expected);
129 }
130
131 #[test]
133 fn test_cli_parsing_init() {
134 use clap::Parser;
135 let cli = Cli::parse_from(["changepacks", "init"]);
136 assert!(matches!(cli.command, Some(Commands::Init(_))));
137 }
138
139 #[test]
140 fn test_cli_parsing_check() {
141 use clap::Parser;
142 let cli = Cli::parse_from(["changepacks", "check"]);
143 assert!(matches!(cli.command, Some(Commands::Check(_))));
144 }
145
146 #[test]
147 fn test_cli_parsing_update() {
148 use clap::Parser;
149 let cli = Cli::parse_from(["changepacks", "update", "--dry-run"]);
150 assert!(matches!(cli.command, Some(Commands::Update(_))));
151 }
152
153 #[test]
154 fn test_cli_parsing_config() {
155 use clap::Parser;
156 let cli = Cli::parse_from(["changepacks", "config"]);
157 assert!(matches!(cli.command, Some(Commands::Config(_))));
158 }
159
160 #[test]
161 fn test_cli_parsing_publish() {
162 use clap::Parser;
163 let cli = Cli::parse_from(["changepacks", "publish", "--dry-run"]);
164 assert!(matches!(cli.command, Some(Commands::Publish(_))));
165 }
166
167 #[test]
168 fn test_cli_parsing_default_with_options() {
169 use clap::Parser;
170 let cli = Cli::parse_from([
171 "changepacks",
172 "--yes",
173 "--message",
174 "test",
175 "--update-type",
176 "patch",
177 ]);
178 assert!(cli.command.is_none());
179 assert!(cli.yes);
180 assert_eq!(cli.message, Some("test".to_string()));
181 assert!(matches!(cli.update_type, Some(CliUpdateType::Patch)));
182 }
183
184 #[test]
185 fn test_cli_parsing_with_filter() {
186 use clap::Parser;
187 let cli = Cli::parse_from(["changepacks", "--filter", "package"]);
188 assert!(cli.command.is_none());
189 assert!(matches!(cli.filter, Some(FilterOptions::Package)));
190 }
191
192 #[test]
193 fn test_cli_parsing_with_remote() {
194 use clap::Parser;
195 let cli = Cli::parse_from(["changepacks", "--remote"]);
196 assert!(cli.remote);
197 }
198
199 #[test]
200 fn test_cli_parsing_with_language() {
201 use clap::Parser;
202 let cli = Cli::parse_from(["changepacks", "--language", "node"]);
203 assert_eq!(cli.language.len(), 1);
204 }
205
206 #[test]
207 fn test_cli_parsing_with_multiple_languages() {
208 use clap::Parser;
209 let cli = Cli::parse_from(["changepacks", "--language", "node", "--language", "rust"]);
210 assert_eq!(cli.language.len(), 2);
211 }
212}