mirror of
https://github.com/Drop-OSS/drop.git
synced 2026-07-25 17:24:48 +10:00
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"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[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" }
|
droplet-rs = { git = "https://github.com/Drop-OSS/droplet-rs.git", version = "0.11.1" }
|
||||||
indicatif = "0.18.3"
|
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"] }
|
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 droplet_rs::manifest::generate_manifest_rusty;
|
||||||
use indicatif::{ProgressBar, ProgressStyle};
|
use indicatif::{ProgressBar, ProgressStyle};
|
||||||
use tokio::runtime::Handle;
|
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]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() -> Result<()> {
|
||||||
let threads = Handle::current().metrics().num_workers();
|
|
||||||
println!("using {} workers", threads);
|
|
||||||
let args: Vec<String> = env::args().collect();
|
|
||||||
let path = &args[1];
|
|
||||||
|
|
||||||
let path = PathBuf::from(path);
|
match &CLI.command {
|
||||||
if !path.exists() {
|
Commands::Configure { url, token } => {
|
||||||
panic!("{} does not exist", path.display());
|
if let Some(token) = token {
|
||||||
}
|
todo!()
|
||||||
|
} else {
|
||||||
println!("using path {}", path.display());
|
interactive_configure(url.to_string()).await?;
|
||||||
|
}
|
||||||
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);
|
|
||||||
},
|
},
|
||||||
|log| {
|
Commands::Upload {
|
||||||
progress_bar.println(log);
|
path,
|
||||||
},
|
game_id,
|
||||||
)
|
version_id,
|
||||||
.await
|
} => todo!(),
|
||||||
.expect("failed to generate manifest");
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user