summaryrefslogtreecommitdiff
path: root/src/cli.rs
blob: ab191a42da0a564692f388334231df8888ebaccf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
use clap::{Parser, Subcommand};
use std::path::PathBuf;

/// Witryna - minimalist Git-based static site deployment orchestrator
#[derive(Debug, Parser)]
#[command(
    name = "witryna",
    version,
    author,
    about = "Minimalist Git-based static site deployment orchestrator",
    long_about = "Minimalist Git-based static site deployment orchestrator.\n\n\
        Witryna listens for webhook HTTP requests, pulls the corresponding Git \
        repository (with automatic Git LFS fetch and submodule initialization), \
        runs a user-defined build command inside an ephemeral container and \
        publishes the resulting assets via atomic symlink switching.\n\n\
        A health-check endpoint is available at GET /health (returns 200 OK).\n\n\
        Witryna does not serve files, terminate TLS, or manage DNS. \
        It is designed to sit behind a reverse proxy (Nginx, Caddy, etc.).",
    subcommand_required = true,
    arg_required_else_help = true
)]
pub struct Cli {
    /// Path to the configuration file.
    /// If not specified, searches: ./witryna.toml, $XDG_CONFIG_HOME/witryna/witryna.toml, /etc/witryna/witryna.toml
    #[arg(long, global = true, value_name = "FILE")]
    pub config: Option<PathBuf>,

    #[command(subcommand)]
    pub command: Command,
}

#[derive(Debug, Subcommand)]
pub enum Command {
    /// Start the deployment server (foreground)
    Serve,
    /// Validate configuration file and print summary
    Validate,
    /// Trigger a one-off build for a site (synchronous, no server)
    Run {
        /// Site name (as defined in witryna.toml)
        site: String,
        /// Stream full build output to stderr in real-time
        #[arg(long, short)]
        verbose: bool,
    },
    /// Show deployment status for configured sites
    Status {
        /// Show last 10 deployments for a single site
        #[arg(long, short)]
        site: Option<String>,
        /// Output in JSON format
        #[arg(long)]
        json: bool,
    },
}

#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::indexing_slicing)]
mod tests {
    use super::*;

    #[test]
    fn run_parses_site_name() {
        let cli = Cli::try_parse_from(["witryna", "run", "my-site"]).unwrap();
        match cli.command {
            Command::Run { site, verbose } => {
                assert_eq!(site, "my-site");
                assert!(!verbose);
            }
            _ => panic!("expected Run command"),
        }
    }

    #[test]
    fn run_parses_verbose_flag() {
        let cli = Cli::try_parse_from(["witryna", "run", "my-site", "--verbose"]).unwrap();
        match cli.command {
            Command::Run { site, verbose } => {
                assert_eq!(site, "my-site");
                assert!(verbose);
            }
            _ => panic!("expected Run command"),
        }
    }

    #[test]
    fn status_parses_without_flags() {
        let cli = Cli::try_parse_from(["witryna", "status"]).unwrap();
        match cli.command {
            Command::Status { site, json } => {
                assert!(site.is_none());
                assert!(!json);
            }
            _ => panic!("expected Status command"),
        }
    }

    #[test]
    fn status_parses_site_filter() {
        let cli = Cli::try_parse_from(["witryna", "status", "--site", "my-site"]).unwrap();
        match cli.command {
            Command::Status { site, json } => {
                assert_eq!(site.as_deref(), Some("my-site"));
                assert!(!json);
            }
            _ => panic!("expected Status command"),
        }
    }

    #[test]
    fn status_parses_json_flag() {
        let cli = Cli::try_parse_from(["witryna", "status", "--json"]).unwrap();
        match cli.command {
            Command::Status { site, json } => {
                assert!(site.is_none());
                assert!(json);
            }
            _ => panic!("expected Status command"),
        }
    }

    #[test]
    fn config_flag_is_optional() {
        let cli = Cli::try_parse_from(["witryna", "status"]).unwrap();
        assert!(cli.config.is_none());
    }

    #[test]
    fn config_flag_explicit_path() {
        let cli =
            Cli::try_parse_from(["witryna", "--config", "/etc/witryna.toml", "status"]).unwrap();
        assert_eq!(cli.config, Some(PathBuf::from("/etc/witryna.toml")));
    }
}