cli: make init and cfg interactive by default
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I7596bb9913a8d98133bdf3c531241bf06a6a6964
This commit is contained in:
parent
788bdb0f1b
commit
cce952698a
2 changed files with 179 additions and 18 deletions
|
|
@ -2,7 +2,11 @@ use std::path::Path;
|
|||
|
||||
use yansi::Paint;
|
||||
|
||||
use crate::{error::Result, model::config::Config};
|
||||
use crate::{
|
||||
error::Result,
|
||||
model::config::Config,
|
||||
ui_utils::prompt_input_optional,
|
||||
};
|
||||
|
||||
pub fn execute(
|
||||
config_path: &Path,
|
||||
|
|
@ -85,11 +89,50 @@ pub fn execute(
|
|||
}
|
||||
|
||||
if !changed {
|
||||
eprintln!(
|
||||
// Interactive mode: prompt for values if none were specified
|
||||
println!(
|
||||
"{}",
|
||||
"No changes specified. Use --help for options.".yellow()
|
||||
"No changes specified. Enter values interactively (press Enter to skip):"
|
||||
.yellow()
|
||||
);
|
||||
return Ok(());
|
||||
println!();
|
||||
|
||||
// Prompt for each configurable field
|
||||
if let Ok(Some(new_name)) = prompt_input_optional(" Name") {
|
||||
config.name = new_name.clone();
|
||||
println!("{}", format!(" ✓ 'name' set to '{new_name}'").green());
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if let Ok(Some(new_version)) = prompt_input_optional(" Version") {
|
||||
config.version = new_version.clone();
|
||||
println!(
|
||||
"{}",
|
||||
format!(" ✓ 'version' set to '{new_version}'").green()
|
||||
);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if let Ok(Some(new_description)) = prompt_input_optional(" Description") {
|
||||
config.description = Some(new_description.clone());
|
||||
println!(
|
||||
"{}",
|
||||
format!(" ✓ 'description' set to '{new_description}'").green()
|
||||
);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if let Ok(Some(new_author)) = prompt_input_optional(" Author") {
|
||||
config.author = Some(new_author.clone());
|
||||
println!("{}", format!(" ✓ 'author' set to '{new_author}'").green());
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if !changed {
|
||||
println!();
|
||||
println!("{}", "No changes made.".dim());
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
// Config::save expects directory path, not file path
|
||||
|
|
|
|||
|
|
@ -3,7 +3,13 @@ use std::{collections::HashMap, path::Path};
|
|||
use crate::{
|
||||
cli::InitArgs,
|
||||
error::PakkerError,
|
||||
model::{Config, LockFile, Target},
|
||||
model::{Config, LockFile, ResolvedCredentials, Target},
|
||||
ui_utils::{
|
||||
prompt_curseforge_api_key,
|
||||
prompt_input,
|
||||
prompt_select,
|
||||
prompt_yes_no,
|
||||
},
|
||||
};
|
||||
|
||||
pub async fn execute(
|
||||
|
|
@ -17,8 +23,42 @@ pub async fn execute(
|
|||
));
|
||||
}
|
||||
|
||||
let target = args.target.as_str();
|
||||
let target_enum = match target {
|
||||
// Interactive mode: prompt for values not provided via CLI and --yes not set
|
||||
let is_interactive = !args.yes && args.name.is_none();
|
||||
|
||||
// Get modpack name
|
||||
let name = if let Some(name) = args.name.clone() {
|
||||
name
|
||||
} else if is_interactive {
|
||||
prompt_input("Modpack name", Some("My Modpack"))
|
||||
.map_err(|e| PakkerError::InvalidInput(e.to_string()))?
|
||||
} else {
|
||||
"My Modpack".to_string()
|
||||
};
|
||||
|
||||
// Get modpack version
|
||||
let version = if let Some(version) = args.version.clone() {
|
||||
version
|
||||
} else if is_interactive {
|
||||
prompt_input("Version", Some("1.0.0"))
|
||||
.map_err(|e| PakkerError::InvalidInput(e.to_string()))?
|
||||
} else {
|
||||
"1.0.0".to_string()
|
||||
};
|
||||
|
||||
// Get target platform
|
||||
let target = if let Some(target) = args.target.clone() {
|
||||
target
|
||||
} else if is_interactive {
|
||||
let targets = ["multiplatform", "curseforge", "modrinth"];
|
||||
let idx = prompt_select("Target platform", &targets)
|
||||
.map_err(|e| PakkerError::InvalidInput(e.to_string()))?;
|
||||
targets[idx].to_string()
|
||||
} else {
|
||||
"multiplatform".to_string()
|
||||
};
|
||||
|
||||
let target_enum = match target.as_str() {
|
||||
"curseforge" => Target::CurseForge,
|
||||
"modrinth" => Target::Modrinth,
|
||||
"multiplatform" => Target::Multiplatform,
|
||||
|
|
@ -29,17 +69,56 @@ pub async fn execute(
|
|||
},
|
||||
};
|
||||
|
||||
let mc_versions = vec![args.mc_version];
|
||||
// Get Minecraft versions (supports multiple)
|
||||
let mc_versions = if let Some(versions) = args.mc_versions.clone() {
|
||||
versions
|
||||
} else if is_interactive {
|
||||
let input =
|
||||
prompt_input("Minecraft versions (space-separated)", Some("1.20.1"))
|
||||
.map_err(|e| PakkerError::InvalidInput(e.to_string()))?;
|
||||
input.split_whitespace().map(String::from).collect()
|
||||
} else {
|
||||
vec!["1.20.1".to_string()]
|
||||
};
|
||||
|
||||
let mut loaders = HashMap::new();
|
||||
loaders.insert(args.loader, args.loader_version);
|
||||
// Get mod loaders (supports multiple in name=version format)
|
||||
let loaders: HashMap<String, String> = if let Some(loader_strs) = args.loaders
|
||||
{
|
||||
let mut map = HashMap::new();
|
||||
for loader_str in loader_strs {
|
||||
let parts: Vec<&str> = loader_str.splitn(2, '=').collect();
|
||||
if parts.len() == 2 {
|
||||
map.insert(parts[0].to_string(), parts[1].to_string());
|
||||
} else {
|
||||
// If no version specified, use "latest"
|
||||
map.insert(loader_str, "latest".to_string());
|
||||
}
|
||||
}
|
||||
map
|
||||
} else if is_interactive {
|
||||
let loader_options = ["fabric", "forge", "neoforge", "quilt"];
|
||||
let idx = prompt_select("Mod loader", &loader_options)
|
||||
.map_err(|e| PakkerError::InvalidInput(e.to_string()))?;
|
||||
let loader = loader_options[idx].to_string();
|
||||
|
||||
let loader_version = prompt_input("Loader version", Some("latest"))
|
||||
.map_err(|e| PakkerError::InvalidInput(e.to_string()))?;
|
||||
|
||||
let mut map = HashMap::new();
|
||||
map.insert(loader, loader_version);
|
||||
map
|
||||
} else {
|
||||
let mut map = HashMap::new();
|
||||
map.insert("fabric".to_string(), "latest".to_string());
|
||||
map
|
||||
};
|
||||
|
||||
let lockfile = LockFile {
|
||||
target: Some(target_enum),
|
||||
mc_versions,
|
||||
loaders,
|
||||
projects: Vec::new(),
|
||||
lockfile_version: 1,
|
||||
lockfile_version: 2,
|
||||
};
|
||||
|
||||
// Save expects directory path, so get parent directory
|
||||
|
|
@ -47,12 +126,8 @@ pub async fn execute(
|
|||
lockfile.save(lockfile_dir)?;
|
||||
|
||||
let config = Config {
|
||||
name: args
|
||||
.name
|
||||
.unwrap_or_else(|| "My Modpack".to_string()),
|
||||
version: args
|
||||
.version
|
||||
.unwrap_or_else(|| "1.0.0".to_string()),
|
||||
name: name.clone(),
|
||||
version: version.clone(),
|
||||
description: None,
|
||||
author: None,
|
||||
overrides: vec!["overrides".to_string()],
|
||||
|
|
@ -67,6 +142,49 @@ pub async fn execute(
|
|||
let config_dir = config_path.parent().unwrap_or(Path::new("."));
|
||||
config.save(config_dir)?;
|
||||
|
||||
println!("Initialized new modpack with target: {target}");
|
||||
println!("Initialized new modpack '{name}' v{version}");
|
||||
println!(" Target: {target}");
|
||||
println!(" Minecraft: {}", lockfile.mc_versions.join(", "));
|
||||
println!(
|
||||
" Loaders: {}",
|
||||
lockfile
|
||||
.loaders
|
||||
.iter()
|
||||
.map(|(k, v)| format!("{k}={v}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
);
|
||||
|
||||
// Check if CurseForge API key is needed and prompt if interactive
|
||||
if is_interactive && (target == "curseforge" || target == "multiplatform") {
|
||||
let credentials = ResolvedCredentials::load().ok();
|
||||
let has_cf_key = credentials
|
||||
.as_ref()
|
||||
.is_some_and(|c| c.curseforge_api_key().is_some());
|
||||
|
||||
if !has_cf_key {
|
||||
println!();
|
||||
if prompt_yes_no("Would you like to set up CurseForge API key now?", true)
|
||||
.map_err(|e| PakkerError::InvalidInput(e.to_string()))?
|
||||
&& let Ok(Some(api_key)) = prompt_curseforge_api_key()
|
||||
{
|
||||
// Save to credentials file
|
||||
let creds_path = std::env::var("HOME").map_or_else(
|
||||
|_| Path::new(".pakku").to_path_buf(),
|
||||
|h| Path::new(&h).join(".pakku"),
|
||||
);
|
||||
|
||||
std::fs::create_dir_all(&creds_path).ok();
|
||||
|
||||
let creds_file = creds_path.join("credentials");
|
||||
let content =
|
||||
format!("# Pakku/Pakker credentials\nCURSEFORGE_API_KEY={api_key}\n");
|
||||
if std::fs::write(&creds_file, content).is_ok() {
|
||||
println!("CurseForge API key saved to ~/.pakku/credentials");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue