cli: prompt for missing CurseForge credentials on add

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: Id0bbdc9ed62bc8b9582ccb89158e53786a6a6964
This commit is contained in:
raf 2026-05-01 23:33:42 +03:00
commit e7c6da593d
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
2 changed files with 101 additions and 3 deletions

View file

@ -1,16 +1,99 @@
use std::collections::HashMap; use std::{collections::HashMap, time::Duration};
use crate::{ use crate::{
error::{MultiError, PakkerError, Result}, error::{MultiError, PakkerError, Result},
model::{Config, LockFile, Project, credentials::ResolvedCredentials}, http,
model::{
Config,
LockFile,
PakkerCredentialsFile,
Project,
Target,
credentials::ResolvedCredentials,
set_keyring_secret,
},
platform::create_platform, platform::create_platform,
resolver::DependencyResolver, resolver::DependencyResolver,
ui_utils::prompt_curseforge_api_key,
}; };
fn get_loaders(lockfile: &LockFile) -> Vec<String> { fn get_loaders(lockfile: &LockFile) -> Vec<String> {
lockfile.loaders.keys().cloned().collect() lockfile.loaders.keys().cloned().collect()
} }
fn needs_curseforge(target: Option<&Target>) -> bool {
matches!(
target,
Some(Target::CurseForge) | Some(Target::Multiplatform)
)
}
async fn ensure_curseforge_credentials() -> Result<bool> {
let creds = ResolvedCredentials::load();
if creds.curseforge_api_key().is_some() {
return Ok(true);
}
if let Some(key) = prompt_curseforge_api_key(false)? {
// Verify the key before saving
let client = http::create_http_client();
let response = client
.get("https://api.curseforge.com/v1/mods/238222")
.header("x-api-key", &key)
.timeout(Duration::from_secs(10))
.send()
.await;
match response {
Ok(resp) if resp.status().is_success() => {
let mut creds_file = PakkerCredentialsFile::load()?;
set_keyring_secret("curseforge_api_key", &key)?;
creds_file.curseforge_api_key = Some(key.clone());
creds_file.save()?;
println!("CurseForge API key verified and saved.");
Ok(true)
},
Ok(resp) => {
println!(
"Warning: CurseForge API key verification failed (HTTP {}).",
resp.status()
);
if crate::ui_utils::prompt_yes_no(
"Save this key anyway?",
false,
false,
)? {
let mut creds_file = PakkerCredentialsFile::load()?;
set_keyring_secret("curseforge_api_key", &key)?;
creds_file.curseforge_api_key = Some(key);
creds_file.save()?;
Ok(true)
} else {
Ok(false)
}
},
Err(e) => {
println!("Warning: Could not verify CurseForge API key: {e}");
if crate::ui_utils::prompt_yes_no(
"Save this key anyway?",
false,
false,
)? {
let mut creds_file = PakkerCredentialsFile::load()?;
set_keyring_secret("curseforge_api_key", &key)?;
creds_file.curseforge_api_key = Some(key);
creds_file.save()?;
Ok(true)
} else {
Ok(false)
}
},
}
} else {
Ok(false)
}
}
pub fn create_all_platforms() pub fn create_all_platforms()
-> HashMap<String, Box<dyn crate::platform::PlatformClient>> { -> HashMap<String, Box<dyn crate::platform::PlatformClient>> {
let mut platforms = HashMap::new(); let mut platforms = HashMap::new();
@ -166,6 +249,11 @@ pub async fn execute(
let mut lockfile = LockFile::load_with_validation(lockfile_dir, false)?; let mut lockfile = LockFile::load_with_validation(lockfile_dir, false)?;
// Prompt for missing CurseForge credentials when needed
if needs_curseforge(lockfile.target.as_ref()) && !skip_prompts {
let _ = ensure_curseforge_credentials().await;
}
// Load config if available // Load config if available
let _config = Config::load(config_dir).ok(); let _config = Config::load(config_dir).ok();

View file

@ -11,6 +11,7 @@ use crate::{
}, },
platform::create_platform, platform::create_platform,
resolver::DependencyResolver, resolver::DependencyResolver,
ui_utils::prompt_curseforge_api_key,
}; };
/// Parse a common project argument (slug or ID with optional file ID) /// Parse a common project argument (slug or ID with optional file ID)
@ -100,7 +101,16 @@ pub async fn execute(
log::info!("Fetching from CurseForge: {cf_input}"); log::info!("Fetching from CurseForge: {cf_input}");
let (input, file_id) = parse_common_arg(&cf_input); let (input, file_id) = parse_common_arg(&cf_input);
let cf_api_key = std::env::var("CURSEFORGE_API_KEY").ok(); let credentials = ResolvedCredentials::load();
let mut cf_api_key = credentials.curseforge_api_key().map(String::from);
// Prompt for missing CurseForge credentials
if cf_api_key.is_none() && !yes {
if let Some(key) = prompt_curseforge_api_key(false)? {
cf_api_key = Some(key);
}
}
let platform = create_platform("curseforge", cf_api_key)?; let platform = create_platform("curseforge", cf_api_key)?;
let mut project = platform let mut project = platform