cratestack-cli 0.3.5

Rust-native schema-first framework for typed HTTP APIs, generated clients, and backend services.
mod cli_handlers;
mod cli_support;
mod cli_types;
mod migrate;

use anyhow::Result;
use clap::Parser;

use crate::cli_handlers::run;
use crate::cli_types::Cli;

fn main() -> Result<()> {
    let cli = Cli::parse();
    run(cli)
}

#[cfg(test)]
mod tests {
    use std::path::{Path, PathBuf};

    use clap::Parser;

    use crate::Cli;
    use crate::cli_support::{json_check_failure, json_check_success};
    use crate::cli_types::{Command, StudioCmd};

    #[test]
    fn json_success_payload_has_empty_diagnostics() {
        let payload = json_check_success(&Path::new("schema.cstack").to_path_buf());
        assert_eq!(payload["ok"], true);
        assert_eq!(payload["schema"], "schema.cstack");
        assert_eq!(payload["diagnostics"], serde_json::json!([]));
    }

    #[test]
    fn json_failure_payload_exposes_structured_diagnostic_fields() {
        let error = cratestack_parser::parse_schema("model User {\n  email String\n}\n")
            .expect_err("schema should fail validation");
        let payload = json_check_failure(&Path::new("schema.cstack").to_path_buf(), &error);
        let diagnostic = &payload["diagnostics"][0];

        assert_eq!(payload["ok"], false);
        assert_eq!(diagnostic["line"], 1);
        assert!(diagnostic["start"].as_u64().is_some());
        assert!(diagnostic["end"].as_u64().is_some());
        assert!(
            diagnostic["message"]
                .as_str()
                .expect("message should be a string")
                .contains("missing an @id field")
        );
    }

    #[test]
    fn generate_typescript_clap_defaults() {
        let cli = Cli::parse_from([
            "cratestack",
            "generate-typescript",
            "--schema",
            "schema.cstack",
            "--out",
            "out",
        ]);

        match cli.command {
            Command::GenerateTypeScript {
                schema,
                out,
                package_name,
                base_path,
                template_dir,
            } => {
                assert_eq!(schema, PathBuf::from("schema.cstack"));
                assert_eq!(out, PathBuf::from("out"));
                assert_eq!(package_name, "cratestack-client");
                assert_eq!(base_path, "/api");
                assert_eq!(template_dir, None);
            }
            _ => panic!("expected generate-typescript command"),
        }
    }

    #[test]
    fn studio_run_clap_defaults() {
        let cli = Cli::parse_from(["cratestack", "studio", "run"]);
        match cli.command {
            Command::Studio {
                cmd: StudioCmd::Run { config, bind },
            } => {
                assert_eq!(config, PathBuf::from("studio.toml"));
                assert!(bind.is_none());
            }
            _ => panic!("expected studio run command"),
        }
    }

    #[test]
    fn studio_init_clap_defaults() {
        let cli = Cli::parse_from(["cratestack", "studio", "init"]);
        match cli.command {
            Command::Studio {
                cmd: StudioCmd::Init { out, force },
            } => {
                assert_eq!(out, PathBuf::from("."));
                assert!(!force);
            }
            _ => panic!("expected studio init command"),
        }
    }

    #[test]
    fn studio_eject_requires_out() {
        let result = Cli::try_parse_from(["cratestack", "studio", "eject"]);
        assert!(
            result.is_err(),
            "studio eject must require --out, got {:?}",
            result.map(|cli| cli.command)
        );
    }
}