Skip to main content

geam_cli/
lib.rs

1mod command;
2mod error;
3mod process;
4mod project;
5mod provider;
6mod runner;
7mod standalone;
8
9use clap::Parser;
10use command::{Cli, Command, ProviderCommand};
11use error::CliError;
12use std::env;
13use std::process::ExitCode;
14
15pub fn run() -> ExitCode {
16    let cli = Cli::parse();
17    let result = env::current_dir()
18        .map_err(CliError::CurrentDirectory)
19        .and_then(project::into_utf8_path)
20        .and_then(|current_directory| run_command(cli, current_directory));
21    match result {
22        Ok(()) => ExitCode::SUCCESS,
23        Err(error) => {
24            eprintln!("geam: {error}");
25            ExitCode::FAILURE
26        }
27    }
28}
29
30fn run_command(cli: Cli, current_directory: camino::Utf8PathBuf) -> Result<(), CliError> {
31    let project_root = project::find_project_root(&current_directory)?;
32    match cli.command {
33        Command::Prepare(command) => project::entry_module(&project_root, command.module)
34            .and_then(|module| standalone::prepare(&project_root, module)),
35        Command::Run(command) => {
36            project::entry_module(&project_root, command.module).and_then(|module| {
37                standalone::run(
38                    &project_root,
39                    &current_directory,
40                    module,
41                    command.provider_configs,
42                )
43            })
44        }
45        Command::Provider(command) => match command.command {
46            ProviderCommand::Add(command) => {
47                provider::add(&project_root, current_directory.as_std_path(), command)
48            }
49            ProviderCommand::Remove(command) => provider::remove(&project_root, command),
50        },
51    }
52}
53
54#[cfg(test)]
55mod tests {
56    use super::{Cli, run_command};
57    use crate::error::CliError;
58    use camino::Utf8PathBuf;
59    use clap::Parser;
60    use std::fs;
61    use tempfile::tempdir;
62
63    #[test]
64    fn preserves_entry_resolution_failures_for_prepare_and_run() {
65        let project = tempdir().expect("temporary project should be created");
66        fs::write(project.path().join("gleam.toml"), "invalid")
67            .expect("invalid config should be written");
68        let root = Utf8PathBuf::from_path_buf(project.path().to_path_buf())
69            .expect("temporary path should be valid UTF-8");
70
71        for arguments in [vec!["geam", "prepare"], vec!["geam", "run"]] {
72            let error = run_command(
73                Cli::try_parse_from(arguments).expect("command should parse"),
74                root.clone(),
75            )
76            .expect_err("entry resolution should fail");
77            assert!(matches!(
78                error,
79                CliError::InvalidToml { kind, path, reason }
80                    if kind == "Gleam package config"
81                        && path == root.join("gleam.toml")
82                        && reason.contains("expected")
83            ));
84        }
85    }
86
87    #[test]
88    fn preserves_project_compilation_failures_for_prepare_and_run() {
89        let project = tempdir().expect("temporary project should be created");
90        fs::create_dir(project.path().join("src")).expect("source directory should be created");
91        fs::write(
92            project.path().join("gleam.toml"),
93            "name = \"application\"\nversion = \"1.0.0\"\n",
94        )
95        .expect("package config should be written");
96        fs::write(
97            project.path().join("manifest.toml"),
98            "packages = []\n[requirements]\n",
99        )
100        .expect("manifest should be written");
101        fs::write(
102            project.path().join("src/application.gleam"),
103            "pub fn main() { 1 }\n",
104        )
105        .expect("source should be written");
106        let root = Utf8PathBuf::from_path_buf(project.path().to_path_buf())
107            .expect("temporary path should be valid UTF-8");
108
109        for arguments in [
110            vec!["geam", "prepare", "--module", "missing"],
111            vec!["geam", "run", "--module", "missing"],
112        ] {
113            let error = run_command(
114                Cli::try_parse_from(arguments).expect("command should parse"),
115                root.clone(),
116            )
117            .expect_err("missing entry module should fail");
118            assert!(matches!(
119                error,
120                CliError::Project(geam_core::ProjectError::Frontend(
121                    geam_core::FrontendError::MissingRootModule { package, module }
122                )) if package == "application" && module == "missing"
123            ));
124        }
125    }
126}