diff --git a/rivet-cli/Cargo.toml b/rivet-cli/Cargo.toml index e2434b6a..a1c1f75b 100644 --- a/rivet-cli/Cargo.toml +++ b/rivet-cli/Cargo.toml @@ -3,10 +3,13 @@ name = "rivet-cli" version = "0.1.0" edition = "2021" +[[bin]] +name = "rivet" +path = "src/main.rs" + [dependencies] clap = { version = "4.5.9", features = ["derive"] } global-error = { git = "https://github.com/rivet-gg/rivet.git", rev = "22baf31efa3ffcdad65ecc72ce25425ab61b9c6f" } toolchain = { version = "0.1.0", path = "../rivet-toolchain", package = "rivet-toolchain" } tokio = { version = "1.38.1", features = ["full"] } -serde_json = "1.0.120" - +serde_json = "1.0.120" \ No newline at end of file diff --git a/rivet-cli/src/commands/deploy.rs b/rivet-cli/src/commands/deploy.rs new file mode 100644 index 00000000..676a5c8f --- /dev/null +++ b/rivet-cli/src/commands/deploy.rs @@ -0,0 +1,78 @@ +use clap::Parser; +use std::process::ExitCode; +use toolchain::{ + tasks::{deploy, get_bootstrap_data, RunConfig}, + util::task::run_task, +}; + +#[derive(Parser)] +pub struct Opts { + environment: String, + #[clap(long, conflicts_with = "only_backend")] + only_game_server: bool, + #[clap(long, conflicts_with = "only_game_server")] + only_backend: bool, +} + +impl Opts { + pub async fn execute(&self) -> ExitCode { + let run_config = RunConfig::empty(); + + let bootstrap_data = match run_task::( + run_config.clone(), + get_bootstrap_data::Input {}, + ) + .await + { + Ok(x) => x, + Err(e) => { + eprintln!("Error getting bootstrap: {e}"); + return ExitCode::FAILURE; + } + }; + + // Find environment + let environment = match bootstrap_data + .backend_environments + .iter() + .find(|env| env.name_id == self.environment) + { + Some(env) => env, + None => { + eprintln!( + "Environment '{}' not found. Available environments:", + self.environment + ); + for env in &bootstrap_data.backend_environments { + eprintln!("- {}", env.name_id); + } + return ExitCode::FAILURE; + } + }; + + match run_task::( + run_config, + deploy::Input { + cwd: std::env::current_dir() + .unwrap_or_default() + .to_string_lossy() + .to_string(), + environment_id: environment.environment_id.to_string(), + game_server: !self.only_backend, + backend: !self.only_game_server, + }, + ) + .await + { + Ok(_) => { + println!("Deployment completed successfully."); + ExitCode::SUCCESS + } + Err(e) => { + eprintln!("Error during deployment: {e}"); + ExitCode::FAILURE + } + } + } +} + diff --git a/rivet-cli/src/commands/login.rs b/rivet-cli/src/commands/login.rs index 6272e514..a8421125 100644 --- a/rivet-cli/src/commands/login.rs +++ b/rivet-cli/src/commands/login.rs @@ -1,50 +1,65 @@ use clap::Parser; -use global_error::prelude::*; +use std::process::ExitCode; use toolchain::{ - tasks::{check_login_state, start_device_link, wait_for_login, RunConfig}, - util::task::run_task, + tasks::{check_login_state, start_device_link, wait_for_login, RunConfig}, + util::task::run_task, }; #[derive(Parser)] pub struct Opts { - #[clap(long, default_value = "https://api.rivet.gg")] - api_endpoint: String, + #[clap(long, default_value = "https://api.rivet.gg")] + api_endpoint: String, } impl Opts { - pub async fn execute(&self) -> GlobalResult<()> { - let run_config = RunConfig::empty(); + pub async fn execute(&self) -> ExitCode { + let run_config = RunConfig::empty(); - // Check if linked - let output = - run_task::(run_config.clone(), check_login_state::Input {}) - .await?; - if output.logged_in { - eprintln!("Already logged in. Sign out with `rivet unlink`."); - return Ok(()); - } + // Check if linked + match run_task::(run_config.clone(), check_login_state::Input {}).await { + Ok(output) => { + if output.logged_in { + eprintln!("Already logged in. Sign out with `rivet unlink`."); + return ExitCode::SUCCESS; + } + } + Err(e) => { + eprintln!("Error checking login state: {}", e); + return ExitCode::from(1); + } + } - // Start device link - let output = run_task::( - run_config.clone(), - start_device_link::Input { - api_endpoint: self.api_endpoint.clone(), - }, - ) - .await?; - eprintln!("{}", output.device_link_url); + // Start device link + let device_link_output = match run_task::( + run_config.clone(), + start_device_link::Input { + api_endpoint: self.api_endpoint.clone(), + }, + ).await { + Ok(output) => output, + Err(e) => { + eprintln!("Error starting device link: {}", e); + return ExitCode::from(2); + } + }; + eprintln!("{}", device_link_output.device_link_url); - // Wait for finish - run_task::( - run_config.clone(), - wait_for_login::Input { - api_endpoint: self.api_endpoint.clone(), - device_link_token: output.device_link_token, - }, - ) - .await?; - eprintln!("Logged in"); - - Ok(()) - } -} + // Wait for finish + match run_task::( + run_config.clone(), + wait_for_login::Input { + api_endpoint: self.api_endpoint.clone(), + device_link_token: device_link_output.device_link_token, + }, + ).await { + Ok(_) => { + eprintln!("Logged in"); + ExitCode::SUCCESS + } + Err(e) => { + eprintln!("Error waiting for login: {}", e); + ExitCode::from(3) + } + } + } +} \ No newline at end of file diff --git a/rivet-cli/src/commands/logout.rs b/rivet-cli/src/commands/logout.rs index f0b0c7b5..3f265f18 100644 --- a/rivet-cli/src/commands/logout.rs +++ b/rivet-cli/src/commands/logout.rs @@ -1,20 +1,26 @@ use clap::Parser; -use global_error::prelude::*; +use std::process::ExitCode; use toolchain::{ - tasks::{unlink, RunConfig}, - util::task::run_task, + tasks::{unlink, RunConfig}, + util::task::run_task, }; #[derive(Parser)] pub struct Opts {} impl Opts { - pub async fn execute(&self) -> GlobalResult<()> { - let run_config = RunConfig::empty(); + pub async fn execute(&self) -> ExitCode { + let run_config = RunConfig::empty(); - run_task::(run_config.clone(), unlink::Input {}).await?; - eprintln!("Logged out"); - - Ok(()) - } -} + match run_task::(run_config.clone(), unlink::Input {}).await { + Ok(_) => { + eprintln!("Logged out"); + ExitCode::SUCCESS + } + Err(e) => { + eprintln!("Error logging out: {}", e); + ExitCode::from(1) + } + } + } +} \ No newline at end of file diff --git a/rivet-cli/src/commands/mod.rs b/rivet-cli/src/commands/mod.rs index 6bd5ec5b..e27a2412 100644 --- a/rivet-cli/src/commands/mod.rs +++ b/rivet-cli/src/commands/mod.rs @@ -1,26 +1,29 @@ pub mod login; pub mod logout; pub mod task; +pub mod deploy; use clap::Parser; -use global_error::prelude::*; +use std::process::ExitCode; #[derive(Parser)] pub enum SubCommand { - Login(login::Opts), - Task { - #[clap(subcommand)] - subcommand: task::SubCommand, - }, - Logout(logout::Opts), + Login(login::Opts), + Task { + #[clap(subcommand)] + subcommand: task::SubCommand, + }, + Logout(logout::Opts), + Deploy(deploy::Opts), } impl SubCommand { - pub async fn execute(&self) -> GlobalResult<()> { - match self { - SubCommand::Login(opts) => opts.execute().await, - SubCommand::Task { subcommand } => subcommand.execute().await, - SubCommand::Logout(opts) => opts.execute().await, - } - } -} + pub async fn execute(&self) -> ExitCode { + match self { + SubCommand::Login(opts) => opts.execute().await, + SubCommand::Task { subcommand } => subcommand.execute().await, + SubCommand::Logout(opts) => opts.execute().await, + SubCommand::Deploy(opts) => opts.execute().await, + } + } +} \ No newline at end of file diff --git a/rivet-cli/src/commands/task.rs b/rivet-cli/src/commands/task.rs index fde7b84c..2129ff30 100644 --- a/rivet-cli/src/commands/task.rs +++ b/rivet-cli/src/commands/task.rs @@ -1,5 +1,5 @@ use clap::Parser; -use global_error::prelude::*; +use std::process::ExitCode; /// EXPERIMENTAL #[derive(Parser)] @@ -8,7 +8,7 @@ pub enum SubCommand { } impl SubCommand { - pub async fn execute(&self) -> GlobalResult<()> { + pub async fn execute(&self) -> ExitCode { match self { SubCommand::Run(opts) => opts.execute().await, } @@ -26,10 +26,24 @@ pub struct RunOpts { } impl RunOpts { - pub async fn execute(&self) -> GlobalResult<()> { - let run_config = serde_json::from_str(&self.run_config)?; - let output = toolchain::tasks::run_task_json(run_config, &self.name, &self.input).await; - println!("{output}"); - Ok(()) + pub async fn execute(&self) -> ExitCode { + match serde_json::from_str(&self.run_config) { + Ok(run_config) => { + let result = + toolchain::tasks::run_task_json(run_config, &self.name, &self.input).await; + println!("{}", result.output); + + if result.success { + ExitCode::SUCCESS + } else { + ExitCode::FAILURE + } + } + Err(e) => { + eprintln!("Error parsing run_config: {}", e); + ExitCode::from(2) + } + } } } + diff --git a/rivet-cli/src/main.rs b/rivet-cli/src/main.rs index 9368d47f..6e88e5e3 100644 --- a/rivet-cli/src/main.rs +++ b/rivet-cli/src/main.rs @@ -1,7 +1,7 @@ pub mod commands; use clap::Parser; -use global_error::GlobalResult; +use std::process::ExitCode; #[derive(Parser)] #[clap(author, version, about, long_about = None)] @@ -11,9 +11,7 @@ struct Cli { } #[tokio::main] -async fn main() -> GlobalResult<()> { +async fn main() -> ExitCode { let cli = Cli::parse(); - cli.command.execute().await?; - Ok(()) + cli.command.execute().await } - diff --git a/rivet-toolchain/src/tasks/deploy/mod.rs b/rivet-toolchain/src/tasks/deploy/mod.rs index 2ce92e16..e0976ae8 100644 --- a/rivet-toolchain/src/tasks/deploy/mod.rs +++ b/rivet-toolchain/src/tasks/deploy/mod.rs @@ -9,10 +9,10 @@ use crate::util::task::TaskCtx; #[derive(Deserialize)] pub struct Input { - cwd: String, - environment_id: String, - game_server: bool, - backend: bool, + pub cwd: String, + pub environment_id: String, + pub game_server: bool, + pub backend: bool, } #[derive(Serialize)] diff --git a/rivet-toolchain/src/tasks/mod.rs b/rivet-toolchain/src/tasks/mod.rs index 41f3d87a..ff3aa257 100644 --- a/rivet-toolchain/src/tasks/mod.rs +++ b/rivet-toolchain/src/tasks/mod.rs @@ -44,18 +44,28 @@ pub trait Task { fn run(task: TaskCtx, input: Self::Input) -> impl Future>; } +pub struct RunTaskJsonOutput { + pub output: String, + pub success: bool, +} + /// Used to run tasks with raw input/output string. This is useful for binding tasks to non-Rust /// environments, such as raw dylibs or odd engines. macro_rules! gen_run_task { ( $( $task:ty ),* $(,)? ) => { - pub async fn run_task_json(run_config: RunConfig, name: &str, input_json: &str) -> String { + pub async fn run_task_json(run_config: RunConfig, name: &str, input_json: &str) -> RunTaskJsonOutput { $( if name == <$task>::name() { let input = serde_json::from_str::<<$task as Task>::Input>(&input_json) .expect("deserialize task input"); let output = run_task::<$task>(run_config, input).await; + let success = output.is_ok(); let output_serialize = output.map_err(|x| x.to_string()); - return serde_json::to_string(&output_serialize).expect("serialize task output"); + let output_json = serde_json::to_string(&output_serialize).expect("serialize task output"); + return RunTaskJsonOutput { + output: output_json, + success, + }; } )*