cli: fix global -y flag conflicts in add-prj` and sync commands

`AddPrjArgs` had a local `-y` flag that conflicted with the global flag,
causing runtime panics. Removed the local field and updated callers to
use `global_yes` consistently.

The sync command now respects the global `-y` flag by accepting, you
guessed it, the `global_yes` parameter.

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I7b7c42fabbca0e363bd18a1d8b6b3bb76a6a6964
This commit is contained in:
raf 2026-02-27 22:26:38 +03:00
commit b0a594e892
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
3 changed files with 81 additions and 50 deletions

View file

@ -15,6 +15,10 @@ pub struct Cli {
#[clap(short, long, action = clap::ArgAction::Count)] #[clap(short, long, action = clap::ArgAction::Count)]
pub verbose: u8, pub verbose: u8,
/// Skip all confirmation prompts (assume yes)
#[clap(short, long, global = true)]
pub yes: bool,
#[clap(subcommand)] #[clap(subcommand)]
pub command: Commands, pub command: Commands,
} }
@ -107,10 +111,6 @@ pub struct InitArgs {
/// Mod loaders (format: name=version, can be specified multiple times) /// Mod loaders (format: name=version, can be specified multiple times)
#[clap(short, long = "loaders", value_delimiter = ',')] #[clap(short, long = "loaders", value_delimiter = ',')]
pub loaders: Option<Vec<String>>, pub loaders: Option<Vec<String>>,
/// Skip interactive prompts (use defaults)
#[clap(short, long)]
pub yes: bool,
} }
#[derive(Args)] #[derive(Args)]
@ -121,10 +121,6 @@ pub struct ImportArgs {
/// Resolve dependencies /// Resolve dependencies
#[clap(short = 'D', long = "deps")] #[clap(short = 'D', long = "deps")]
pub deps: bool, pub deps: bool,
/// Skip confirmation prompts
#[clap(short, long)]
pub yes: bool,
} }
#[derive(Args)] #[derive(Args)]
@ -144,10 +140,6 @@ pub struct AddArgs {
/// Update if already exists /// Update if already exists
#[clap(short, long)] #[clap(short, long)]
pub update: bool, pub update: bool,
/// Skip confirmation prompts
#[clap(short, long)]
pub yes: bool,
} }
#[derive(Args)] #[derive(Args)]
@ -195,10 +187,6 @@ pub struct AddPrjArgs {
/// Skip resolving dependencies /// Skip resolving dependencies
#[clap(short = 'D', long = "no-deps")] #[clap(short = 'D', long = "no-deps")]
pub no_deps: bool, pub no_deps: bool,
/// Skip confirmation prompts
#[clap(short, long)]
pub yes: bool,
} }
#[derive(Args)] #[derive(Args)]
@ -211,10 +199,6 @@ pub struct RmArgs {
#[clap(short = 'a', long)] #[clap(short = 'a', long)]
pub all: bool, pub all: bool,
/// Skip confirmation prompt
#[clap(short, long)]
pub yes: bool,
/// Skip removing dependent projects /// Skip removing dependent projects
#[clap(short = 'D', long = "no-deps")] #[clap(short = 'D', long = "no-deps")]
pub no_deps: bool, pub no_deps: bool,
@ -229,10 +213,6 @@ pub struct UpdateArgs {
/// Update all projects /// Update all projects
#[arg(short, long)] #[arg(short, long)]
pub all: bool, pub all: bool,
/// Skip confirmation prompts
#[arg(short, long)]
pub yes: bool,
} }
#[derive(Args)] #[derive(Args)]

View file

@ -1,7 +1,6 @@
use std::{ use std::{
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
fs, fs,
io::{self, Write},
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
@ -22,6 +21,7 @@ enum SyncChange {
pub async fn execute( pub async fn execute(
args: SyncArgs, args: SyncArgs,
global_yes: bool,
lockfile_path: &Path, lockfile_path: &Path,
config_path: &Path, config_path: &Path,
) -> Result<()> { ) -> Result<()> {
@ -66,7 +66,11 @@ pub async fn execute(
for (file_path, _) in &additions { for (file_path, _) in &additions {
spinner spinner
.set_message(format!("Processing addition: {}", file_path.display())); .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?; add_file_to_lockfile(&mut lockfile, file_path, &config).await?;
} }
} }
@ -87,7 +91,11 @@ pub async fn execute(
.or(project.pakku_id.as_deref()) .or(project.pakku_id.as_deref())
.unwrap_or("unknown"); .unwrap_or("unknown");
spinner.set_message(format!("Processing removal: {name}")); 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 lockfile
.remove_project(pakku_id) .remove_project(pakku_id)
.ok_or_else(|| PakkerError::ProjectNotFound(pakku_id.clone()))?; .ok_or_else(|| PakkerError::ProjectNotFound(pakku_id.clone()))?;
@ -174,7 +182,7 @@ async fn add_file_to_lockfile(
_config: &Config, _config: &Config,
) -> Result<()> { ) -> Result<()> {
// Try to identify the file by hash lookup // Try to identify the file by hash lookup
let _modrinth = ModrinthPlatform::new(); let modrinth = ModrinthPlatform::new();
let curseforge = CurseForgePlatform::new(None); let curseforge = CurseForgePlatform::new(None);
// Compute file hash // Compute file hash
@ -186,7 +194,7 @@ async fn add_file_to_lockfile(
let hash = format!("{:x}", hasher.finalize()); let hash = format!("{:x}", hasher.finalize());
// Try Modrinth first (SHA-1 hash) // 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); lockfile.add_project(project);
println!("✓ Added {} (from Modrinth)", file_path.display()); println!("✓ Added {} (from Modrinth)", file_path.display());
return Ok(()); return Ok(());
@ -202,15 +210,3 @@ async fn add_file_to_lockfile(
println!("⚠ Could not identify {}, skipping", file_path.display()); println!("⚠ Could not identify {}, skipping", file_path.display());
Ok(()) Ok(())
} }
fn prompt_user(message: &str) -> Result<bool> {
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"))
}

View file

@ -18,12 +18,31 @@ mod resolver;
mod ui_utils; mod ui_utils;
mod utils; mod utils;
use std::path::PathBuf; use std::{env, path::PathBuf};
use clap::Parser; use clap::Parser;
use cli::{Cli, Commands}; use cli::{Cli, Commands};
use error::PakkerError; 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<PathBuf> {
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] #[tokio::main]
async fn main() -> Result<(), PakkerError> { async fn main() -> Result<(), PakkerError> {
let cli = Cli::parse(); let cli = Cli::parse();
@ -43,19 +62,41 @@ async fn main() -> Result<(), PakkerError> {
.format_module_path(false) .format_module_path(false)
.init(); .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 lockfile_path = working_dir.join("pakker-lock.json");
let config_path = working_dir.join("pakker.json"); let config_path = working_dir.join("pakker.json");
let global_yes = cli.yes;
match cli.command { match cli.command {
Commands::Init(args) => { 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) => { 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) => { 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) => { Commands::AddPrj(args) => {
cli::commands::add_prj::execute( cli::commands::add_prj::execute(
@ -70,17 +111,24 @@ async fn main() -> Result<(), PakkerError> {
args.aliases, args.aliases,
args.export, args.export,
args.no_deps, args.no_deps,
args.yes, global_yes,
&lockfile_path, &lockfile_path,
&config_path, &config_path,
) )
.await .await
}, },
Commands::Rm(args) => { 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) => { 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::Ls(args) => cli::commands::ls::execute(args, &lockfile_path),
Commands::Set(args) => { Commands::Set(args) => {
@ -95,7 +143,13 @@ async fn main() -> Result<(), PakkerError> {
cli::commands::fetch::execute(args, &lockfile_path, &config_path).await cli::commands::fetch::execute(args, &lockfile_path, &config_path).await
}, },
Commands::Sync(args) => { 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) => { Commands::Export(args) => {
cli::commands::export::execute(args, &lockfile_path, &config_path).await cli::commands::export::execute(args, &lockfile_path, &config_path).await
@ -107,6 +161,7 @@ async fn main() -> Result<(), PakkerError> {
Commands::Status(args) => { Commands::Status(args) => {
cli::commands::status::execute( cli::commands::status::execute(
args.parallel, args.parallel,
global_yes,
&lockfile_path, &lockfile_path,
&config_path, &config_path,
) )