feat: begin designing cli
This commit is contained in:
Generated
+1512
-18
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,15 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.100"
|
||||
clap = { version = "4.5.54", features = ["derive"] }
|
||||
console = "0.16.2"
|
||||
dialoguer = "0.12.0"
|
||||
droplet-rs = { git = "https://github.com/Drop-OSS/droplet-rs.git", version = "0.11.1" }
|
||||
indicatif = "0.18.3"
|
||||
reqwest = { version = "0.13.1", features = ["json"] }
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.148"
|
||||
tokio = { version = "1.48.0", features = ["macros"] }
|
||||
url = "2.5.8"
|
||||
webbrowser = "1.0.6"
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(version, about, long_about = None)]
|
||||
pub struct Cli {
|
||||
#[command(subcommand)]
|
||||
pub command: Commands,
|
||||
|
||||
/// Specify data file path
|
||||
#[arg(short, long)]
|
||||
pub data: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub enum Commands {
|
||||
/// Configures a new Drop server
|
||||
Configure {
|
||||
/// Endpoint of the Drop server
|
||||
url: String,
|
||||
/// API token for non-interactive configuration.
|
||||
#[arg(short, long)]
|
||||
token: Option<String>
|
||||
},
|
||||
/// Uploads new game version to depot
|
||||
Upload {
|
||||
/// Path of new version
|
||||
path: bool,
|
||||
/// ID of game to attach to
|
||||
game_id: String,
|
||||
/// Version ID to attach to
|
||||
version_id: String,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
use dialoguer::{Confirm, Input, theme::ColorfulTheme};
|
||||
use reqwest::Client;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
const TOKEN_CREATE_PAYLOAD: &str =
|
||||
"eyJuYW1lIjoiZG93bnBvdXIgKGNsaSkiLCJhY2xzIjpbImRlcG90Om5ldyJdfQ==";
|
||||
|
||||
static CLIENT: LazyLock<Client> = LazyLock::new(|| reqwest::Client::new());
|
||||
const REQUIRED_ACLS: [&str; 1] = ["depot:new"];
|
||||
|
||||
pub async fn interactive_configure(url: String) -> Result<()> {
|
||||
let base_url = Url::parse(&url)?;
|
||||
let mut token_create_url = base_url.join("/admin/settings/tokens")?;
|
||||
{
|
||||
let mut query = token_create_url.query_pairs_mut();
|
||||
query.append_pair("payload", TOKEN_CREATE_PAYLOAD);
|
||||
};
|
||||
|
||||
let confirm = Confirm::with_theme(&ColorfulTheme::default())
|
||||
.with_prompt(format!(
|
||||
"Open \"{}\" in your default browser?",
|
||||
token_create_url.as_str()
|
||||
))
|
||||
.interact()?;
|
||||
|
||||
if !confirm {
|
||||
return Err(anyhow!("User cancelled action"));
|
||||
}
|
||||
|
||||
webbrowser::open(token_create_url.as_str())?;
|
||||
|
||||
let token: String = Input::with_theme(&ColorfulTheme::default())
|
||||
.with_prompt("API token")
|
||||
.interact_text()?;
|
||||
|
||||
validate_configuration(url, token).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn validate_configuration(url: String, token: String) -> Result<()> {
|
||||
let base_url = Url::parse(&url)?;
|
||||
let token_check_url = base_url.join("/api/v1/token")?;
|
||||
|
||||
let acl_check = CLIENT
|
||||
.get(token_check_url)
|
||||
.bearer_auth(token)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if !acl_check.status().is_success() {
|
||||
return Err(anyhow!(
|
||||
"ACL check failed with response code: {}",
|
||||
acl_check.status()
|
||||
));
|
||||
}
|
||||
|
||||
let acls: Vec<String> = acl_check.json().await?;
|
||||
|
||||
for acl in REQUIRED_ACLS {
|
||||
if !acls.contains(&acl.to_string()) {
|
||||
return Err(anyhow!("Token missing {} acl", acl));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct ServerConfiguration {
|
||||
pub endpoint: String,
|
||||
pub token: String,
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod configure;
|
||||
+26
-31
@@ -1,40 +1,35 @@
|
||||
use std::{env, path::PathBuf};
|
||||
use std::{env, path::PathBuf, sync::LazyLock};
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use droplet_rs::manifest::generate_manifest_rusty;
|
||||
use indicatif::{ProgressBar, ProgressStyle};
|
||||
use tokio::runtime::Handle;
|
||||
|
||||
use crate::{cli::{Cli, Commands}, commands::configure::interactive_configure};
|
||||
|
||||
mod cli;
|
||||
mod commands;
|
||||
|
||||
pub static CLI: LazyLock<Cli> = LazyLock::new(|| Cli::parse());
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let threads = Handle::current().metrics().num_workers();
|
||||
println!("using {} workers", threads);
|
||||
let args: Vec<String> = env::args().collect();
|
||||
let path = &args[1];
|
||||
async fn main() -> Result<()> {
|
||||
|
||||
let path = PathBuf::from(path);
|
||||
if !path.exists() {
|
||||
panic!("{} does not exist", path.display());
|
||||
}
|
||||
|
||||
println!("using path {}", path.display());
|
||||
|
||||
let progress_bar = ProgressBar::new(100_00).with_style(
|
||||
ProgressStyle::template(
|
||||
ProgressStyle::default_bar(),
|
||||
"[{elapsed_precise}] [ETA {eta}] {wide_bar} {percent_precise}%",
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
let manifest = generate_manifest_rusty(
|
||||
&path,
|
||||
|progress| {
|
||||
let progress_int = (progress * 100.0f32).round() as u64;
|
||||
progress_bar.set_position(progress_int);
|
||||
match &CLI.command {
|
||||
Commands::Configure { url, token } => {
|
||||
if let Some(token) = token {
|
||||
todo!()
|
||||
} else {
|
||||
interactive_configure(url.to_string()).await?;
|
||||
}
|
||||
},
|
||||
|log| {
|
||||
progress_bar.println(log);
|
||||
},
|
||||
)
|
||||
.await
|
||||
.expect("failed to generate manifest");
|
||||
Commands::Upload {
|
||||
path,
|
||||
game_id,
|
||||
version_id,
|
||||
} => todo!(),
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user