pakker/src/cli/commands/init.rs
NotAShelf 61ced09d25
treewide: fix clippy lints
Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I411be69ff31f9cb39cd4cdebc8985b366a6a6964
2026-04-21 19:27:36 +03:00

196 lines
5.9 KiB
Rust

use std::{collections::HashMap, path::Path};
use crate::{
cli::InitArgs,
error::PakkerError,
model::{Config, LockFile, ResolvedCredentials, Target},
ui_utils::{
prompt_curseforge_api_key,
prompt_input,
prompt_select,
prompt_yes_no,
},
};
pub fn execute(
args: InitArgs,
global_yes: bool,
lockfile_path: &Path,
config_path: &Path,
) -> Result<(), PakkerError> {
let skip_prompts = global_yes;
if lockfile_path.exists() {
return Err(PakkerError::AlreadyExists(
"Lock file already exists".into(),
));
}
// Interactive mode: prompt for values not provided via CLI and --yes not set
let is_interactive = !skip_prompts && 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,
_ => {
return Err(PakkerError::InvalidInput(format!(
"Invalid target: {target}"
)));
},
};
// 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()]
};
// 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: 2,
};
// Save expects directory path, so get parent directory
let lockfile_dir = lockfile_path.parent().unwrap_or_else(|| Path::new("."));
lockfile.save(lockfile_dir)?;
let config = Config {
name: name.clone(),
version: version.clone(),
description: None,
author: None,
overrides: vec!["overrides".to_string()],
server_overrides: None,
client_overrides: None,
paths: HashMap::new(),
projects: None,
export_profiles: None,
export_server_side_projects_to_client: None,
file_count_preference: None,
};
let config_dir = config_path.parent().unwrap_or_else(|| Path::new("."));
config.save(config_dir)?;
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();
let has_cf_key = credentials.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,
skip_prompts,
)
.map_err(|e| PakkerError::InvalidInput(e.to_string()))?
&& let Ok(Some(api_key)) = prompt_curseforge_api_key(skip_prompts)
{
// 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(())
}