Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I411be69ff31f9cb39cd4cdebc8985b366a6a6964
196 lines
5.9 KiB
Rust
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(())
|
|
}
|