1pub(crate) mod auth;
4pub mod confluence;
5pub(crate) mod convert;
6pub(crate) mod format;
7pub(crate) mod helpers;
8pub mod jira;
9
10use anyhow::Result;
11use clap::{Args, Parser, Subcommand};
12
13#[derive(Args, Debug, Clone, Default)]
20pub struct InstanceArg {
21 #[arg(long, global = true, value_name = "URL")]
29 pub instance: Option<String>,
30}
31
32impl InstanceArg {
33 pub fn apply(&self) {
37 if let Some(instance) = &self.instance {
38 std::env::set_var(
39 crate::atlassian::auth::ATLASSIAN_INSTANCE_OVERRIDE_ENV,
40 instance,
41 );
42 }
43 }
44}
45
46#[derive(Parser)]
48pub struct AtlassianCommand {
49 #[command(subcommand)]
51 pub command: AtlassianSubcommands,
52}
53
54#[derive(Subcommand)]
56pub enum AtlassianSubcommands {
57 Jira(jira::JiraCommand),
59 Confluence(confluence::ConfluenceCommand),
61 Convert(convert::ConvertCommand),
63 Auth(auth::AuthCommand),
65}
66
67impl AtlassianCommand {
68 pub async fn execute(self) -> Result<()> {
70 match self.command {
71 AtlassianSubcommands::Jira(cmd) => cmd.execute().await,
72 AtlassianSubcommands::Confluence(cmd) => cmd.execute().await,
73 AtlassianSubcommands::Convert(cmd) => cmd.execute(),
74 AtlassianSubcommands::Auth(cmd) => cmd.execute().await,
75 }
76 }
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82
83 const INSTANCE_VAR: &str = "OMNI_DEV_ATLASSIAN_INSTANCE";
86
87 struct InstanceEnvGuard {
89 _lock: std::sync::MutexGuard<'static, ()>,
90 saved: Option<String>,
91 }
92
93 impl InstanceEnvGuard {
94 fn new() -> Self {
95 let lock = crate::claude::ai::claude_cli::CLI_ENV_LOCK
96 .lock()
97 .unwrap_or_else(std::sync::PoisonError::into_inner);
98 let saved = std::env::var(INSTANCE_VAR).ok();
99 std::env::remove_var(INSTANCE_VAR);
100 Self { _lock: lock, saved }
101 }
102 }
103
104 impl Drop for InstanceEnvGuard {
105 fn drop(&mut self) {
106 match &self.saved {
107 Some(v) => std::env::set_var(INSTANCE_VAR, v),
108 None => std::env::remove_var(INSTANCE_VAR),
109 }
110 }
111 }
112
113 #[test]
114 fn instance_apply_sets_env_var() {
115 let _g = InstanceEnvGuard::new();
116 InstanceArg {
117 instance: Some("https://org.atlassian.net".to_string()),
118 }
119 .apply();
120 assert_eq!(
121 std::env::var(INSTANCE_VAR).ok().as_deref(),
122 Some("https://org.atlassian.net")
123 );
124 }
125
126 #[test]
127 fn instance_apply_absent_leaves_env_var() {
128 let _g = InstanceEnvGuard::new();
129 std::env::set_var(INSTANCE_VAR, "https://preset.atlassian.net");
130 InstanceArg::default().apply();
131 assert_eq!(
132 std::env::var(INSTANCE_VAR).ok().as_deref(),
133 Some("https://preset.atlassian.net")
134 );
135 }
136
137 #[test]
138 fn instance_parses_on_readers_and_is_rejected_elsewhere() {
139 use crate::cli::Cli;
140 use clap::Parser;
141
142 let url = "https://x.atlassian.net";
143 for argv in [
144 &[
145 "omni-dev",
146 "atlassian",
147 "jira",
148 "--instance",
149 url,
150 "read",
151 "K-1",
152 ][..],
153 &[
154 "omni-dev",
155 "atlassian",
156 "jira",
157 "read",
158 "K-1",
159 "--instance",
160 url,
161 ],
162 &[
163 "omni-dev",
164 "atlassian",
165 "confluence",
166 "--instance",
167 url,
168 "read",
169 "1",
170 ],
171 &["omni-dev", "atlassian", "auth", "status", "--instance", url],
172 ] {
173 assert!(Cli::try_parse_from(argv).is_ok(), "{argv:?}");
174 }
175 for argv in [
176 &[
177 "omni-dev",
178 "--instance",
179 url,
180 "atlassian",
181 "jira",
182 "read",
183 "K-1",
184 ][..],
185 &[
186 "omni-dev",
187 "atlassian",
188 "--instance",
189 url,
190 "jira",
191 "read",
192 "K-1",
193 ],
194 &["omni-dev", "atlassian", "auth", "login", "--instance", url],
195 &[
196 "omni-dev",
197 "git",
198 "commit",
199 "message",
200 "view",
201 "--instance",
202 url,
203 ],
204 ] {
205 assert!(Cli::try_parse_from(argv).is_err(), "{argv:?}");
206 }
207 }
208
209 #[test]
210 fn atlassian_subcommands_jira_variant() {
211 let cmd = AtlassianCommand {
212 command: AtlassianSubcommands::Jira(jira::JiraCommand {
213 command: jira::JiraSubcommands::Edit(jira::edit::EditCommand {
214 key: "PROJ-1".to_string(),
215 }),
216 instance: crate::cli::atlassian::InstanceArg::default(),
217 }),
218 };
219 assert!(matches!(cmd.command, AtlassianSubcommands::Jira(_)));
220 }
221
222 #[test]
223 fn atlassian_subcommands_confluence_variant() {
224 let cmd = AtlassianCommand {
225 command: AtlassianSubcommands::Confluence(confluence::ConfluenceCommand {
226 command: confluence::ConfluenceSubcommands::Edit(confluence::edit::EditCommand {
227 id: "12345".to_string(),
228 }),
229 instance: crate::cli::atlassian::InstanceArg::default(),
230 }),
231 };
232 assert!(matches!(cmd.command, AtlassianSubcommands::Confluence(_)));
233 }
234
235 #[test]
236 fn atlassian_subcommands_auth_variant() {
237 let cmd = AtlassianCommand {
238 command: AtlassianSubcommands::Auth(auth::AuthCommand {
239 command: auth::AuthSubcommands::Login(auth::LoginCommand {
240 auth_mode: crate::atlassian::auth::AuthMode::Basic,
241 }),
242 }),
243 };
244 assert!(matches!(cmd.command, AtlassianSubcommands::Auth(_)));
245 }
246
247 #[test]
248 fn atlassian_subcommands_convert_variant() {
249 let cmd = AtlassianCommand {
250 command: AtlassianSubcommands::Convert(convert::ConvertCommand {
251 command: convert::ConvertSubcommands::FromAdf(convert::FromAdfCommand {
252 file: None,
253 strip_local_ids: false,
254 }),
255 }),
256 };
257 assert!(matches!(cmd.command, AtlassianSubcommands::Convert(_)));
258 }
259}