initial commit

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: Ife1391ed23a1e7f388b1b5eca90b9ea76a6a6964
This commit is contained in:
raf 2026-01-29 19:36:25 +03:00
commit ef28bdaeb4
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
63 changed files with 17292 additions and 0 deletions

140
src/cli/commands/update.rs Normal file
View file

@ -0,0 +1,140 @@
use std::{collections::HashMap, path::Path};
use indicatif::{ProgressBar, ProgressStyle};
use crate::{
cli::UpdateArgs,
error::PakkerError,
model::{Config, LockFile},
platform::create_platform,
ui_utils::prompt_select,
};
pub async fn execute(
args: UpdateArgs,
lockfile_path: &Path,
config_path: &Path,
) -> Result<(), PakkerError> {
// Load expects directory path, so get parent directory
let lockfile_dir = lockfile_path.parent().unwrap_or(Path::new("."));
let config_dir = config_path.parent().unwrap_or(Path::new("."));
let mut lockfile = LockFile::load(lockfile_dir)?;
let _config = Config::load(config_dir)?;
// Create platforms
let mut platforms = HashMap::new();
if let Ok(platform) = create_platform("modrinth", None) {
platforms.insert("modrinth".to_string(), platform);
}
if let Ok(platform) =
create_platform("curseforge", std::env::var("CURSEFORGE_API_KEY").ok())
{
platforms.insert("curseforge".to_string(), platform);
}
let project_indices: Vec<_> = if args.inputs.is_empty() {
(0..lockfile.projects.len()).collect()
} else {
let mut indices = Vec::new();
for input in &args.inputs {
if let Some((idx, _)) = lockfile
.projects
.iter()
.enumerate()
.find(|(_, p)| p.matches_input(input))
{
indices.push(idx);
} else {
return Err(PakkerError::ProjectNotFound(input.clone()));
}
}
indices
};
// Create progress bar
let pb = ProgressBar::new(project_indices.len() as u64);
pb.set_style(
ProgressStyle::default_bar()
.template("{spinner:.green} [{bar:40.cyan/blue}] {pos}/{len} {msg}")
.unwrap()
.progress_chars("#>-"),
);
for idx in project_indices {
let old_project = &lockfile.projects[idx];
pb.set_message(format!("Updating {}...", old_project.get_name()));
let slug = old_project
.slug
.values()
.next()
.ok_or_else(|| PakkerError::InvalidProject("No slug found".into()))?;
// Find updated project from one of the platforms
let mut updated_project = None;
for platform in platforms.values() {
if let Ok(project) = platform
.request_project_with_files(
slug,
&lockfile.mc_versions,
&lockfile.loaders.keys().cloned().collect::<Vec<_>>(),
)
.await
{
updated_project = Some(project);
break;
}
}
if let Some(mut updated_project) = updated_project
&& !updated_project.files.is_empty()
&& let Some(old_file) = lockfile.projects[idx].files.first()
{
let new_file = updated_project.files.first().unwrap();
if new_file.id == old_file.id {
pb.println(format!(
" {} - Already up to date",
old_project.get_name()
));
} else {
// Interactive version selection if not using --yes flag
if !args.yes && updated_project.files.len() > 1 {
pb.suspend(|| {
let choices: Vec<String> = updated_project
.files
.iter()
.map(|f| format!("{} ({})", f.file_name, f.id))
.collect();
let choice_refs: Vec<&str> =
choices.iter().map(std::string::String::as_str).collect();
if let Ok(selected_idx) = prompt_select(
&format!("Select version for {}:", old_project.get_name()),
&choice_refs,
) {
// Move selected file to front
if selected_idx > 0 {
updated_project.files.swap(0, selected_idx);
}
}
});
}
let selected_file = updated_project.files.first().unwrap();
pb.println(format!(
" {} -> {}",
old_file.file_name, selected_file.file_name
));
lockfile.projects[idx] = updated_project;
}
}
pb.inc(1);
}
pb.finish_with_message("Update complete");
lockfile.save(lockfile_dir)?;
Ok(())
}