diff --git a/src/cli.rs b/src/cli.rs index fea5e72..8677bdf 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -15,6 +15,10 @@ pub struct Cli { #[clap(short, long, action = clap::ArgAction::Count)] pub verbose: u8, + /// Skip all confirmation prompts (assume yes) + #[clap(short, long, global = true)] + pub yes: bool, + #[clap(subcommand)] pub command: Commands, } @@ -107,10 +111,6 @@ pub struct InitArgs { /// Mod loaders (format: name=version, can be specified multiple times) #[clap(short, long = "loaders", value_delimiter = ',')] pub loaders: Option>, - - /// Skip interactive prompts (use defaults) - #[clap(short, long)] - pub yes: bool, } #[derive(Args)] @@ -121,10 +121,6 @@ pub struct ImportArgs { /// Resolve dependencies #[clap(short = 'D', long = "deps")] pub deps: bool, - - /// Skip confirmation prompts - #[clap(short, long)] - pub yes: bool, } #[derive(Args)] @@ -144,10 +140,6 @@ pub struct AddArgs { /// Update if already exists #[clap(short, long)] pub update: bool, - - /// Skip confirmation prompts - #[clap(short, long)] - pub yes: bool, } #[derive(Args)] @@ -195,10 +187,6 @@ pub struct AddPrjArgs { /// Skip resolving dependencies #[clap(short = 'D', long = "no-deps")] pub no_deps: bool, - - /// Skip confirmation prompts - #[clap(short, long)] - pub yes: bool, } #[derive(Args)] @@ -211,10 +199,6 @@ pub struct RmArgs { #[clap(short = 'a', long)] pub all: bool, - /// Skip confirmation prompt - #[clap(short, long)] - pub yes: bool, - /// Skip removing dependent projects #[clap(short = 'D', long = "no-deps")] pub no_deps: bool, @@ -229,10 +213,6 @@ pub struct UpdateArgs { /// Update all projects #[arg(short, long)] pub all: bool, - - /// Skip confirmation prompts - #[arg(short, long)] - pub yes: bool, } #[derive(Args)] diff --git a/src/cli/commands/sync.rs b/src/cli/commands/sync.rs index c5404cf..e806cfc 100644 --- a/src/cli/commands/sync.rs +++ b/src/cli/commands/sync.rs @@ -1,7 +1,6 @@ use std::{ collections::{HashMap, HashSet}, fs, - io::{self, Write}, path::{Path, PathBuf}, }; @@ -22,6 +21,7 @@ enum SyncChange { pub async fn execute( args: SyncArgs, + global_yes: bool, lockfile_path: &Path, config_path: &Path, ) -> Result<()> { @@ -66,7 +66,11 @@ pub async fn execute( for (file_path, _) in &additions { spinner .set_message(format!("Processing addition: {}", file_path.display())); - if prompt_user(&format!("Add {} to lockfile?", file_path.display()))? { + if crate::ui_utils::prompt_yes_no( + &format!("Add {} to lockfile?", file_path.display()), + false, + global_yes, + )? { add_file_to_lockfile(&mut lockfile, file_path, &config).await?; } } @@ -87,7 +91,11 @@ pub async fn execute( .or(project.pakku_id.as_deref()) .unwrap_or("unknown"); spinner.set_message(format!("Processing removal: {name}")); - if prompt_user(&format!("Remove {name} from lockfile?"))? { + if crate::ui_utils::prompt_yes_no( + &format!("Remove {name} from lockfile?"), + false, + global_yes, + )? { lockfile .remove_project(pakku_id) .ok_or_else(|| PakkerError::ProjectNotFound(pakku_id.clone()))?; @@ -174,7 +182,7 @@ async fn add_file_to_lockfile( _config: &Config, ) -> Result<()> { // Try to identify the file by hash lookup - let _modrinth = ModrinthPlatform::new(); + let modrinth = ModrinthPlatform::new(); let curseforge = CurseForgePlatform::new(None); // Compute file hash @@ -186,7 +194,7 @@ async fn add_file_to_lockfile( let hash = format!("{:x}", hasher.finalize()); // Try Modrinth first (SHA-1 hash) - if let Ok(Some(project)) = _modrinth.lookup_by_hash(&hash).await { + if let Ok(Some(project)) = modrinth.lookup_by_hash(&hash).await { lockfile.add_project(project); println!("✓ Added {} (from Modrinth)", file_path.display()); return Ok(()); @@ -202,15 +210,3 @@ async fn add_file_to_lockfile( println!("⚠ Could not identify {}, skipping", file_path.display()); Ok(()) } - -fn prompt_user(message: &str) -> Result { - print!("{message} [y/N] "); - io::stdout().flush().map_err(PakkerError::IoError)?; - - let mut input = String::new(); - io::stdin() - .read_line(&mut input) - .map_err(PakkerError::IoError)?; - - Ok(input.trim().eq_ignore_ascii_case("y")) -} diff --git a/src/main.rs b/src/main.rs index 95f0aed..a322b4c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,12 +18,31 @@ mod resolver; mod ui_utils; mod utils; -use std::path::PathBuf; +use std::{env, path::PathBuf}; use clap::Parser; use cli::{Cli, Commands}; use error::PakkerError; +/// Search for pakker-lock.json in current directory and parent directories +/// Returns the directory containing pakker-lock.json, or None if not found +fn find_working_directory() -> Option { + let mut current_dir = env::current_dir().ok()?; + + loop { + let lockfile = current_dir.join("pakker-lock.json"); + if lockfile.exists() { + return Some(current_dir); + } + + // Try parent directory + if !current_dir.pop() { + // Reached filesystem root + return None; + } + } +} + #[tokio::main] async fn main() -> Result<(), PakkerError> { let cli = Cli::parse(); @@ -43,19 +62,41 @@ async fn main() -> Result<(), PakkerError> { .format_module_path(false) .init(); - let working_dir = PathBuf::from("."); + // Search for pakker-lock.json in current directory and parent directories + let working_dir = + find_working_directory().unwrap_or_else(|| PathBuf::from(".")); let lockfile_path = working_dir.join("pakker-lock.json"); let config_path = working_dir.join("pakker.json"); + let global_yes = cli.yes; + match cli.command { Commands::Init(args) => { - cli::commands::init::execute(args, &lockfile_path, &config_path).await + cli::commands::init::execute( + args, + global_yes, + &lockfile_path, + &config_path, + ) + .await }, Commands::Import(args) => { - cli::commands::import::execute(args, &lockfile_path, &config_path).await + cli::commands::import::execute( + args, + global_yes, + &lockfile_path, + &config_path, + ) + .await }, Commands::Add(args) => { - cli::commands::add::execute(args, &lockfile_path, &config_path).await + cli::commands::add::execute( + args, + global_yes, + &lockfile_path, + &config_path, + ) + .await }, Commands::AddPrj(args) => { cli::commands::add_prj::execute( @@ -70,17 +111,24 @@ async fn main() -> Result<(), PakkerError> { args.aliases, args.export, args.no_deps, - args.yes, + global_yes, &lockfile_path, &config_path, ) .await }, Commands::Rm(args) => { - cli::commands::rm::execute(args, &lockfile_path, &config_path).await + cli::commands::rm::execute(args, global_yes, &lockfile_path, &config_path) + .await }, Commands::Update(args) => { - cli::commands::update::execute(args, &lockfile_path, &config_path).await + cli::commands::update::execute( + args, + global_yes, + &lockfile_path, + &config_path, + ) + .await }, Commands::Ls(args) => cli::commands::ls::execute(args, &lockfile_path), Commands::Set(args) => { @@ -95,7 +143,13 @@ async fn main() -> Result<(), PakkerError> { cli::commands::fetch::execute(args, &lockfile_path, &config_path).await }, Commands::Sync(args) => { - cli::commands::sync::execute(args, &lockfile_path, &config_path).await + cli::commands::sync::execute( + args, + global_yes, + &lockfile_path, + &config_path, + ) + .await }, Commands::Export(args) => { cli::commands::export::execute(args, &lockfile_path, &config_path).await @@ -107,6 +161,7 @@ async fn main() -> Result<(), PakkerError> { Commands::Status(args) => { cli::commands::status::execute( args.parallel, + global_yes, &lockfile_path, &config_path, )