Skip to main content

nuwax_cli/
cli.rs

1use crate::project_info::{metadata, version_info};
2use clap::{Args, Parser, Subcommand};
3use std::path::PathBuf;
4
5/// Upgrade related parameters
6#[derive(Args, Debug)]
7pub struct UpgradeArgs {
8    /// Force re-download (for corrupted files), will download complete service package
9    #[arg(long)]
10    pub force: bool,
11
12    /// Only check for available upgrade versions, do not download
13    #[arg(long)]
14    pub check: bool,
15}
16
17/// Upgrade subcommands
18#[derive(Subcommand, Debug)]
19pub enum UpgradeSubcommand {
20    /// Download latest Docker service package to local cache
21    Download {
22        /// Compatibility flag; download always produces a full package.
23        #[arg(long, hide = true)]
24        full: bool,
25    },
26    /// Check available upgrades (existing behavior)
27    Check {
28        /// Force re-download (for corrupted files)
29        #[arg(long)]
30        force: bool,
31    },
32}
33
34/// Auto backup related commands
35#[derive(Subcommand, Debug)]
36pub enum AutoBackupCommand {
37    /// Execute a manual backup immediately
38    Run,
39    /// Show backup status and history
40    Status,
41}
42
43/// Auto upgrade deploy related commands
44#[derive(Subcommand, Debug)]
45pub enum AutoUpgradeDeployCommand {
46    /// Execute auto upgrade deploy immediately
47    Run {
48        /// Specify frontend service port (default: port 80)
49        #[arg(
50            long,
51            help = "Specify frontend service port, corresponds to FRONTEND_HOST_PORT variable in docker-compose.yml (default: port 80)"
52        )]
53        port: Option<u16>,
54        /// Specify custom docker-compose config file path
55        #[arg(
56            long,
57            help = "Specify custom docker-compose config file path (default: docker/docker-compose.yml)"
58        )]
59        config: Option<PathBuf>,
60        /// Specify docker-compose project name
61        #[arg(
62            short = 'p',
63            long,
64            help = "Specify docker-compose project name (default: read from compose file or use 'docker')"
65        )]
66        project: Option<String>,
67    },
68    /// Show current auto upgrade configuration
69    Status,
70    /// Offline deployment from local archive (全量升级模式)
71    OfflineDeploy {
72        /// Local archive file path
73        #[arg(long, help = "Path to local archive file")]
74        archive: PathBuf,
75        /// Target version for deployment
76        #[arg(long, help = "Target version for deployment")]
77        version: String,
78        /// Specify frontend service port
79        #[arg(long)]
80        port: Option<u16>,
81        /// Specify custom docker-compose config file path
82        #[arg(long)]
83        config: Option<PathBuf>,
84        /// Specify docker-compose project name
85        #[arg(short = 'p', long)]
86        project: Option<String>,
87    },
88}
89
90/// Client update related commands
91#[derive(Subcommand, Debug)]
92pub enum CheckUpdateCommand {
93    /// Check latest version information
94    Check,
95    /// Install specified version or latest version
96    Install {
97        /// Specify version number (install latest if not specified)
98        #[arg(long)]
99        version: Option<String>,
100        /// Force reinstall even if already latest version
101        #[arg(long)]
102        force: bool,
103    },
104}
105
106#[derive(Subcommand, Debug)]
107pub enum DockerServiceCommand {
108    /// Start Docker services
109    Start {
110        /// Specify docker-compose project name
111        #[arg(
112            short = 'p',
113            long,
114            help = "Specify docker-compose project name (default: read from compose file or use 'docker')"
115        )]
116        project: Option<String>,
117    },
118    /// Stop Docker services
119    Stop {
120        /// Specify docker-compose project name
121        #[arg(
122            short = 'p',
123            long,
124            help = "Specify docker-compose project name (default: read from compose file or use 'docker')"
125        )]
126        project: Option<String>,
127    },
128    /// Restart Docker services
129    Restart {
130        /// Specify docker-compose project name
131        #[arg(
132            short = 'p',
133            long,
134            help = "Specify docker-compose project name (default: read from compose file or use 'docker')"
135        )]
136        project: Option<String>,
137    },
138    /// Check service status
139    Status {
140        /// Specify docker-compose project name
141        #[arg(
142            short = 'p',
143            long,
144            help = "Specify docker-compose project name (default: read from compose file or use 'docker')"
145        )]
146        project: Option<String>,
147    },
148    /// Restart specified container
149    RestartContainer {
150        /// Container name
151        container_name: String,
152    },
153    /// Load Docker images
154    LoadImages,
155    /// Setup image tags
156    SetupTags,
157    /// Show architecture information
158    ArchInfo,
159    /// List Docker images (using ducker)
160    ListImages,
161    /// Check and create mount directories in docker-compose.yml
162    CheckMountDirs,
163}
164
165/// Cache management related commands
166#[derive(Subcommand, Debug)]
167pub enum CacheCommand {
168    /// Clear all cache files
169    Clear,
170    /// Show cache usage
171    Status,
172    /// Clean download cache (keep latest versions)
173    CleanDownloads {
174        /// Number of versions to keep
175        #[arg(long, default_value = "3", help = "Number of versions to keep")]
176        keep: u32,
177    },
178}
179
180/// Nuwax CLI - Docker service management and upgrade tool
181#[derive(Parser)]
182#[command(name = "nuwax-cli")]
183#[command(about = metadata::PROJECT_DESCRIPTION)]
184#[command(version = version_info::CLI_VERSION)]
185#[command(long_about = metadata::display::DESCRIPTION_LONG)]
186#[command(author = metadata::PROJECT_AUTHORS)]
187pub struct Cli {
188    /// Configuration file path
189    #[arg(short, long, default_value = "config.toml")]
190    pub config: PathBuf,
191
192    /// Verbose output
193    #[arg(short, long)]
194    pub verbose: bool,
195
196    /// Language setting (zh-CN, zh-TW, en)
197    #[arg(long, global = true)]
198    pub lang: Option<String>,
199
200    #[command(subcommand)]
201    pub command: Commands,
202}
203
204#[derive(Subcommand)]
205pub enum Commands {
206    /// Show service status and version information
207    Status,
208    /// Initialize client on first use, create configuration file and database
209    Init {
210        /// Force overwrite if configuration file already exists
211        #[arg(long)]
212        force: bool,
213    },
214    /// Check client updates
215    #[command(subcommand)]
216    CheckUpdate(CheckUpdateCommand),
217    /// Show current API configuration
218    ApiInfo,
219    /// Download Docker service files
220    Upgrade {
221        #[command(subcommand)]
222        subcommand: Option<UpgradeSubcommand>,
223
224        #[command(flatten)]
225        args: UpgradeArgs,
226    },
227    /// List all backups
228    ListBackups,
229    /// Restore from backup
230    Rollback {
231        /// Backup ID (optional, will show interactive selection if not provided)
232        backup_id: Option<i64>,
233        /// Force overwrite
234        #[arg(long)]
235        force: bool,
236        /// Output JSON format backup list (for GUI integration)
237        #[arg(long)]
238        list_json: bool,
239        /// Whether to rollback data files, default no
240        #[arg(
241            long,
242            default_value = "false",
243            help = "Whether to rollback data files, default no"
244        )]
245        rollback_data: bool,
246    },
247    /// Docker service related commands
248    #[command(subcommand)]
249    DockerService(DockerServiceCommand),
250
251    /// 🐋 A terminal application for managing Docker containers
252    Ducker {
253        /// Arguments passed to ducker
254        #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
255        args: Vec<String>,
256    },
257
258    /// Auto backup management
259    #[command(subcommand)]
260    AutoBackup(AutoBackupCommand),
261
262    /// Auto upgrade deploy
263    #[command(subcommand)]
264    AutoUpgradeDeploy(AutoUpgradeDeployCommand),
265
266    /// Cache management
267    #[command(subcommand)]
268    Cache(CacheCommand),
269
270    /// Compare two SQL files and generate diff SQL
271    DiffSql {
272        /// Old version SQL file path
273        #[arg(help = "Old version SQL file path")]
274        old_sql: PathBuf,
275        /// New version SQL file path
276        #[arg(help = "New version SQL file path")]
277        new_sql: PathBuf,
278        /// Old version number (optional)
279        #[arg(long, help = "Old version number for diff description")]
280        old_version: Option<String>,
281        /// New version number (optional)
282        #[arg(long, help = "New version number for diff description")]
283        new_version: Option<String>,
284        /// Output file name (optional, default: upgrade_diff.sql)
285        #[arg(
286            long,
287            default_value = "upgrade_diff.sql",
288            help = "Diff SQL output file name"
289        )]
290        output: String,
291    },
292}
293
294#[cfg(test)]
295mod tests {
296    use super::*;
297    use clap::Parser;
298
299    #[test]
300    fn parses_legacy_upgrade_command() {
301        let cli = Cli::parse_from(["nuwax-cli", "upgrade"]);
302
303        match cli.command {
304            Commands::Upgrade { subcommand, args } => {
305                assert!(subcommand.is_none());
306                assert!(!args.check);
307                assert!(!args.force);
308            }
309            _ => panic!("expected upgrade command"),
310        }
311    }
312
313    #[test]
314    fn parses_legacy_upgrade_check_and_force_flags() {
315        let cli = Cli::parse_from(["nuwax-cli", "upgrade", "--check", "--force"]);
316
317        match cli.command {
318            Commands::Upgrade { subcommand, args } => {
319                assert!(subcommand.is_none());
320                assert!(args.check);
321                assert!(args.force);
322            }
323            _ => panic!("expected upgrade command"),
324        }
325    }
326
327    #[test]
328    fn parses_upgrade_check_subcommand() {
329        let cli = Cli::parse_from(["nuwax-cli", "upgrade", "check", "--force"]);
330
331        match cli.command {
332            Commands::Upgrade {
333                subcommand: Some(UpgradeSubcommand::Check { force }),
334                args,
335            } => {
336                assert!(force);
337                assert!(!args.check);
338                assert!(!args.force);
339            }
340            _ => panic!("expected upgrade check command"),
341        }
342    }
343
344    #[test]
345    fn parses_upgrade_download_subcommand() {
346        let cli = Cli::parse_from(["nuwax-cli", "upgrade", "download", "--full"]);
347
348        match cli.command {
349            Commands::Upgrade {
350                subcommand: Some(UpgradeSubcommand::Download { full }),
351                args,
352            } => {
353                assert!(full);
354                assert!(!args.check);
355                assert!(!args.force);
356            }
357            _ => panic!("expected upgrade download command"),
358        }
359    }
360}