diff --git a/server/src/main.rs b/server/src/main.rs index dbd2fd4..a053685 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -34,6 +34,32 @@ use std::path::PathBuf; use std::sync::Arc; use tokio::sync::broadcast; +/// What the first CLI argument means. +#[derive(Debug, PartialEq)] +enum CliDirective { + /// `--help` / `-h` + Help, + /// `--version` / `-V` + Version, + /// An unrecognised flag (starts with `-`). + UnknownFlag(String), + /// A positional path argument. + Path, + /// No arguments at all. + None, +} + +/// Inspect the raw CLI arguments and return the directive they imply. +fn classify_cli_args(args: &[String]) -> CliDirective { + match args.first().map(String::as_str) { + None => CliDirective::None, + Some("--help" | "-h") => CliDirective::Help, + Some("--version" | "-V") => CliDirective::Version, + Some(a) if a.starts_with('-') => CliDirective::UnknownFlag(a.to_string()), + Some(_) => CliDirective::Path, + } +} + /// Resolve the optional positional path argument (everything after the binary /// name) into an absolute `PathBuf`. Returns `None` when no argument was /// supplied so that the caller can fall back to the auto-detect behaviour. @@ -53,8 +79,61 @@ async fn main() -> Result<(), std::io::Error> { // Collect CLI args, skipping the binary name (argv[0]). let cli_args: Vec = std::env::args().skip(1).collect(); + + // Handle CLI flags before treating anything as a project path. + match classify_cli_args(&cli_args) { + CliDirective::Help => { + println!("storkit [PATH]"); + println!(); + println!("Serve a storkit project."); + println!(); + println!("USAGE:"); + println!(" storkit [PATH]"); + println!(); + println!("ARGS:"); + println!( + " PATH Path to an existing project directory. \ + If omitted, storkit searches parent directories for a .storkit/ root." + ); + println!(); + println!("OPTIONS:"); + println!(" -h, --help Print this help and exit"); + println!(" -V, --version Print the version and exit"); + std::process::exit(0); + } + CliDirective::Version => { + println!("storkit {}", env!("CARGO_PKG_VERSION")); + std::process::exit(0); + } + CliDirective::UnknownFlag(flag) => { + eprintln!("error: unknown option: {flag}"); + eprintln!("Run 'storkit --help' for usage."); + std::process::exit(1); + } + CliDirective::Path | CliDirective::None => {} + } + let explicit_path = parse_project_path_arg(&cli_args, &cwd); + // When a path is given explicitly on the CLI, it must already exist as a + // directory. We do not create directories from the command line. + if let Some(ref path) = explicit_path { + if !path.exists() { + eprintln!( + "error: path does not exist: {}", + path.display() + ); + std::process::exit(1); + } + if !path.is_dir() { + eprintln!( + "error: path is not a directory: {}", + path.display() + ); + std::process::exit(1); + } + } + if let Some(explicit_root) = explicit_path { // An explicit path was given on the command line. // Open it directly — scaffold .storkit/ if it is missing — and @@ -399,6 +478,61 @@ name = "coder" .unwrap_or_else(|e| panic!("Invalid project.toml: {e}")); } + // ── classify_cli_args ───────────────────────────────────────────────── + + #[test] + fn classify_none_when_no_args() { + assert_eq!(classify_cli_args(&[]), CliDirective::None); + } + + #[test] + fn classify_help_long() { + assert_eq!( + classify_cli_args(&["--help".to_string()]), + CliDirective::Help + ); + } + + #[test] + fn classify_help_short() { + assert_eq!( + classify_cli_args(&["-h".to_string()]), + CliDirective::Help + ); + } + + #[test] + fn classify_version_long() { + assert_eq!( + classify_cli_args(&["--version".to_string()]), + CliDirective::Version + ); + } + + #[test] + fn classify_version_short() { + assert_eq!( + classify_cli_args(&["-V".to_string()]), + CliDirective::Version + ); + } + + #[test] + fn classify_unknown_flag() { + assert_eq!( + classify_cli_args(&["--serve".to_string()]), + CliDirective::UnknownFlag("--serve".to_string()) + ); + } + + #[test] + fn classify_path() { + assert_eq!( + classify_cli_args(&["/some/path".to_string()]), + CliDirective::Path + ); + } + // ── parse_project_path_arg ──────────────────────────────────────────── #[test]