From a57808f3bbbf76462cf6b62e7d9ff134ab135910 Mon Sep 17 00:00:00 2001 From: Erik Chou Date: Wed, 11 Feb 2026 14:48:57 -0800 Subject: [PATCH 1/3] Add config management and version bump CLI commands Add `wvdsh config` to display current wavedash.toml settings, `wvdsh config set ` to update fields with validation, and `wvdsh version bump [patch|minor|major]` for semver auto-increment. Uses toml_edit for lossless TOML editing that preserves formatting. Co-Authored-By: Claude Opus 4.6 --- Cargo.lock | 1 + Cargo.toml | 1 + src/config.rs | 55 ++++++++++++++++++++++++++++++++++++++++++ src/config_cmd.rs | 47 ++++++++++++++++++++++++++++++++++++ src/main.rs | 59 ++++++++++++++++++++++++++++++++++++++++++++++ src/version_cmd.rs | 43 +++++++++++++++++++++++++++++++++ 6 files changed, 206 insertions(+) create mode 100644 src/config_cmd.rs create mode 100644 src/version_cmd.rs diff --git a/Cargo.lock b/Cargo.lock index 55fb93e..8ba378a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3146,6 +3146,7 @@ dependencies = [ "tiny_http", "tokio", "toml", + "toml_edit", "tower 0.4.13", "tower-http 0.5.2", "url", diff --git a/Cargo.toml b/Cargo.toml index 577c03d..799d654 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ dialoguer = "0.11" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" toml = "0.8" +toml_edit = "0.22" # Error Handling anyhow = "1.0" diff --git a/src/config.rs b/src/config.rs index 0595a65..af221b8 100644 --- a/src/config.rs +++ b/src/config.rs @@ -160,6 +160,61 @@ impl WavedashConfig { Ok(config) } + pub fn display_summary(&self) -> String { + let engine_line = if let Some(ref godot) = self.godot { + format!("engine = godot (v{})", godot.version) + } else if let Some(ref unity) = self.unity { + format!("engine = unity (v{})", unity.version) + } else if let Some(ref custom) = self.custom { + format!( + "engine = custom (v{}, entrypoint: {})", + custom.version, custom.entrypoint + ) + } else { + "engine = (none)".to_string() + }; + + format!( + "game_id = {}\n\ + branch = {}\n\ + upload_dir = {}\n\ + version = {}\n\ + {}", + self.game_id, + self.branch, + self.upload_dir.display(), + self.version, + engine_line, + ) + } + + /// Update a top-level field in the TOML file using toml_edit for lossless editing. + pub fn update_field(config_path: &PathBuf, key: &str, value: &str) -> Result { + let content = std::fs::read_to_string(config_path).map_err(|e| { + anyhow::anyhow!( + "Failed to read config file at {}: {}", + config_path.display(), + e + ) + })?; + + let mut doc = content + .parse::() + .map_err(|e| anyhow::anyhow!("Failed to parse config file: {}", e))?; + + let old_value = doc + .get(key) + .and_then(|v| v.as_str()) + .map(|s| s.to_string()) + .ok_or_else(|| anyhow::anyhow!("Key '{}' not found in config", key))?; + + doc[key] = toml_edit::value(value); + + std::fs::write(config_path, doc.to_string())?; + + Ok(old_value) + } + pub fn engine_type(&self) -> Result { match ( self.godot.is_some(), diff --git a/src/config_cmd.rs b/src/config_cmd.rs new file mode 100644 index 0000000..5975ce2 --- /dev/null +++ b/src/config_cmd.rs @@ -0,0 +1,47 @@ +use anyhow::Result; +use std::path::PathBuf; + +use crate::config::WavedashConfig; + +pub fn handle_config_show(config_path: PathBuf) -> Result<()> { + let config = WavedashConfig::load(&config_path)?; + println!("{}", config.display_summary()); + Ok(()) +} + +pub fn handle_config_set(config_path: PathBuf, key: String, value: String) -> Result<()> { + let supported_keys = ["branch", "version", "upload_dir", "game_id"]; + if !supported_keys.contains(&key.as_str()) { + anyhow::bail!( + "Unsupported key '{}'. Supported keys: {}", + key, + supported_keys.join(", ") + ); + } + + // Validate + match key.as_str() { + "version" => { + let parts: Vec<&str> = value.split('.').collect(); + if parts.len() != 3 || parts.iter().any(|p| p.parse::().is_err()) { + anyhow::bail!("Version must be in major.minor.patch format (e.g. 1.2.3)"); + } + } + "game_id" => { + if value.is_empty() { + anyhow::bail!("game_id must be non-empty"); + } + } + "upload_dir" => { + let path = PathBuf::from(&value); + if !path.exists() { + eprintln!("Warning: directory '{}' does not exist yet", value); + } + } + _ => {} + } + + let old_value = WavedashConfig::update_field(&config_path, &key, &value)?; + println!("Updated {}: {} → {}", key, old_value, value); + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index 3be2be0..14b11a6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,12 @@ mod auth; mod builds; mod config; +mod config_cmd; mod dev; mod file_staging; mod init; mod updater; +mod version_cmd; use anyhow::Result; use auth::{login_with_browser, AuthManager, AuthSource}; @@ -69,6 +71,18 @@ enum Commands { #[arg(long = "no-open", help = "Don't open the sandbox URL in the browser")] no_open: bool, }, + #[command(about = "Show or update wavedash.toml configuration")] + Config { + #[command(subcommand)] + action: Option, + #[arg( + short = 'c', + long = "config", + help = "Path to wavedash.toml config file", + default_value = "./wavedash.toml" + )] + config: PathBuf, + }, #[command(about = "Initialize a new wavedash.toml config file")] Init { #[arg(long)] @@ -80,6 +94,18 @@ enum Commands { }, #[command(about = "Check for and install updates")] Update, + #[command(about = "Version management")] + Version { + #[command(subcommand)] + action: VersionCommands, + #[arg( + short = 'c', + long = "config", + help = "Path to wavedash.toml config file", + default_value = "./wavedash.toml" + )] + config: PathBuf, + }, } #[derive(Subcommand)] @@ -107,6 +133,26 @@ enum BuildCommands { }, } +#[derive(Subcommand)] +enum ConfigCommands { + #[command(about = "Set a config field value")] + Set { + #[arg(help = "Config key (branch, version, upload_dir, game_id)")] + key: String, + #[arg(help = "New value")] + value: String, + }, +} + +#[derive(Subcommand)] +enum VersionCommands { + #[command(about = "Bump the version (patch, minor, or major)")] + Bump { + #[arg(value_enum, default_value = "patch")] + level: version_cmd::BumpLevel, + }, +} + #[tokio::main] async fn main() -> Result<()> { // Install rustls crypto provider for TLS @@ -180,6 +226,14 @@ async fn main() -> Result<()> { Commands::Dev { config, no_open } => { handle_dev(config, cli.verbose, no_open).await?; } + Commands::Config { action, config } => match action { + Some(ConfigCommands::Set { key, value }) => { + config_cmd::handle_config_set(config, key, value)?; + } + None => { + config_cmd::handle_config_show(config)?; + } + }, Commands::Init { game_id, branch, @@ -190,6 +244,11 @@ async fn main() -> Result<()> { Commands::Update => { updater::run_update().await?; } + Commands::Version { action, config } => match action { + VersionCommands::Bump { level } => { + version_cmd::handle_version_bump(config, level)?; + } + }, } // Wait for background update check to complete diff --git a/src/version_cmd.rs b/src/version_cmd.rs new file mode 100644 index 0000000..33a1b67 --- /dev/null +++ b/src/version_cmd.rs @@ -0,0 +1,43 @@ +use anyhow::Result; +use std::path::PathBuf; + +use crate::config::WavedashConfig; + +#[derive(Clone, clap::ValueEnum)] +pub enum BumpLevel { + Patch, + Minor, + Major, +} + +pub fn handle_version_bump(config_path: PathBuf, level: BumpLevel) -> Result<()> { + let config = WavedashConfig::load(&config_path)?; + let old_version = &config.version; + + let parts: Vec = old_version + .split('.') + .map(|p| { + p.parse::() + .map_err(|_| anyhow::anyhow!("Invalid version format: {}", old_version)) + }) + .collect::>>()?; + + if parts.len() != 3 { + anyhow::bail!( + "Version must be in major.minor.patch format, got: {}", + old_version + ); + } + + let (major, minor, patch) = (parts[0], parts[1], parts[2]); + + let new_version = match level { + BumpLevel::Patch => format!("{}.{}.{}", major, minor, patch + 1), + BumpLevel::Minor => format!("{}.{}.0", major, minor + 1), + BumpLevel::Major => format!("{}.0.0", major + 1), + }; + + WavedashConfig::update_field(&config_path, "version", &new_version)?; + println!("Bumped version: {} → {}", old_version, new_version); + Ok(()) +} From 31b9b74fdb2ee507de79aa9d73ad890db2bdc3d3 Mon Sep 17 00:00:00 2001 From: Erik Chou Date: Tue, 17 Feb 2026 14:56:31 -0800 Subject: [PATCH 2/3] Show build progress and game build ID after push Print version/branch before uploading and the game build ID after successful push, so users can construct the playable URL. Co-Authored-By: Claude Opus 4.6 --- src/builds.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/builds.rs b/src/builds.rs index 82575bb..3dcf56e 100644 --- a/src/builds.rs +++ b/src/builds.rs @@ -171,6 +171,11 @@ pub async fn handle_build_push(config_path: PathBuf, verbose: bool, message: Opt anyhow::bail!("Source must be a directory: {}", upload_dir.display()); } + println!( + "Pushing v{} to {} ...", + wavedash_config.version, wavedash_config.branch + ); + // Get temporary R2 credentials let engine_kind = wavedash_config.engine_type()?; let creds = get_temp_credentials( @@ -214,5 +219,7 @@ pub async fn handle_build_push(config_path: PathBuf, verbose: bool, message: Opt ) .await?; + println!("Build ID: {}", creds.game_build_id); + Ok(()) } From b7b433d07c5f629f42e77953db649d3e6ab9a161 Mon Sep 17 00:00:00 2001 From: Erik Chou Date: Wed, 18 Feb 2026 12:28:31 -0800 Subject: [PATCH 3/3] Add example for running CLI locally to CLAUDE.md Co-Authored-By: Claude Opus 4.6 --- CLAUDE.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 2b361f8..f555d67 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,4 +2,5 @@ - The goal of this repository is to create the wavedash cli, which helps game developers upload their assets to the site (similar to steams "steampipe" cli). ## Development -- Environment variables are managed by Doppler. Always use `doppler run --` as a prefix when running cargo commands (build, check, clippy, run, test, etc.). For example: `doppler run -- cargo check`, `doppler run -- cargo clippy`. \ No newline at end of file +- Environment variables are managed by Doppler. Always use `doppler run --` as a prefix when running cargo commands (build, check, clippy, run, test, etc.). For example: `doppler run -- cargo check`, `doppler run -- cargo clippy`. +- To run the CLI locally, use `doppler run -- cargo run `. For example: `doppler run -- cargo run build push`. \ No newline at end of file