cli: add --all flag to update; wire UpdateStrategy enforcement
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I9570557396ac46e82cbabbd8e39be0936a6a6964
This commit is contained in:
parent
79a82d6ab8
commit
1ecf0fae00
1 changed files with 144 additions and 36 deletions
|
|
@ -4,10 +4,10 @@ use indicatif::{ProgressBar, ProgressStyle};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
cli::UpdateArgs,
|
cli::UpdateArgs,
|
||||||
error::PakkerError,
|
error::{MultiError, PakkerError},
|
||||||
model::{Config, LockFile},
|
model::{Config, LockFile, UpdateStrategy},
|
||||||
platform::create_platform,
|
platform::create_platform,
|
||||||
ui_utils::prompt_select,
|
ui_utils::{prompt_select, prompt_typo_suggestion, prompt_yes_no},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn execute(
|
pub async fn execute(
|
||||||
|
|
@ -33,6 +33,22 @@ pub async fn execute(
|
||||||
platforms.insert("curseforge".to_string(), platform);
|
platforms.insert("curseforge".to_string(), platform);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Collect all known project identifiers for typo suggestions
|
||||||
|
let all_slugs: Vec<String> = lockfile
|
||||||
|
.projects
|
||||||
|
.iter()
|
||||||
|
.flat_map(|p| {
|
||||||
|
let mut ids = Vec::new();
|
||||||
|
if let Some(ref pakku_id) = p.pakku_id {
|
||||||
|
ids.push(pakku_id.clone());
|
||||||
|
}
|
||||||
|
ids.extend(p.slug.values().cloned());
|
||||||
|
ids.extend(p.name.values().cloned());
|
||||||
|
ids.extend(p.aliases.iter().cloned());
|
||||||
|
ids
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
let project_indices: Vec<_> = if args.inputs.is_empty() {
|
let project_indices: Vec<_> = if args.inputs.is_empty() {
|
||||||
(0..lockfile.projects.len()).collect()
|
(0..lockfile.projects.len()).collect()
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -46,14 +62,29 @@ pub async fn execute(
|
||||||
{
|
{
|
||||||
indices.push(idx);
|
indices.push(idx);
|
||||||
} else {
|
} else {
|
||||||
|
// Try typo suggestion
|
||||||
|
if let Ok(Some(suggestion)) = prompt_typo_suggestion(input, &all_slugs)
|
||||||
|
&& let Some((idx, _)) = lockfile
|
||||||
|
.projects
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(_, p)| p.matches_input(&suggestion))
|
||||||
|
{
|
||||||
|
log::info!("Using suggested project: {suggestion}");
|
||||||
|
indices.push(idx);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
return Err(PakkerError::ProjectNotFound(input.clone()));
|
return Err(PakkerError::ProjectNotFound(input.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
indices
|
indices
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Capture count before consuming the iterator
|
||||||
|
let total_projects = project_indices.len();
|
||||||
|
|
||||||
// Create progress bar
|
// Create progress bar
|
||||||
let pb = ProgressBar::new(project_indices.len() as u64);
|
let pb = ProgressBar::new(total_projects as u64);
|
||||||
pb.set_style(
|
pb.set_style(
|
||||||
ProgressStyle::default_bar()
|
ProgressStyle::default_bar()
|
||||||
.template("{spinner:.green} [{bar:40.cyan/blue}] {pos}/{len} {msg}")
|
.template("{spinner:.green} [{bar:40.cyan/blue}] {pos}/{len} {msg}")
|
||||||
|
|
@ -61,8 +92,23 @@ pub async fn execute(
|
||||||
.progress_chars("#>-"),
|
.progress_chars("#>-"),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let mut skipped_pinned = 0;
|
||||||
|
let mut update_errors = MultiError::new();
|
||||||
|
|
||||||
for idx in project_indices {
|
for idx in project_indices {
|
||||||
let old_project = &lockfile.projects[idx];
|
let old_project = &lockfile.projects[idx];
|
||||||
|
|
||||||
|
// Skip projects with UpdateStrategy::None (pinned)
|
||||||
|
if old_project.update_strategy == UpdateStrategy::None {
|
||||||
|
pb.println(format!(
|
||||||
|
" {} - Skipped (update strategy: NONE)",
|
||||||
|
old_project.get_name()
|
||||||
|
));
|
||||||
|
skipped_pinned += 1;
|
||||||
|
pb.inc(1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
pb.set_message(format!("Updating {}...", old_project.get_name()));
|
pb.set_message(format!("Updating {}...", old_project.get_name()));
|
||||||
|
|
||||||
let slug = old_project
|
let slug = old_project
|
||||||
|
|
@ -87,21 +133,46 @@ pub async fn execute(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if updated_project.is_none() {
|
||||||
|
// Failed to fetch update info from any platform
|
||||||
|
update_errors.push(PakkerError::PlatformApiError(format!(
|
||||||
|
"Failed to check updates for '{}'",
|
||||||
|
old_project.get_name()
|
||||||
|
)));
|
||||||
|
pb.inc(1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(mut updated_project) = updated_project
|
if let Some(mut updated_project) = updated_project
|
||||||
&& !updated_project.files.is_empty()
|
&& !updated_project.files.is_empty()
|
||||||
&& let Some(old_file) = lockfile.projects[idx].files.first()
|
&& let Some(old_file) = lockfile.projects[idx].files.first()
|
||||||
{
|
{
|
||||||
let new_file = updated_project.files.first().unwrap();
|
// Clone data needed for comparisons to avoid borrow issues
|
||||||
|
let new_file_id = updated_project.files.first().unwrap().id.clone();
|
||||||
|
let new_file_name =
|
||||||
|
updated_project.files.first().unwrap().file_name.clone();
|
||||||
|
let old_file_name = old_file.file_name.clone();
|
||||||
|
let project_name = old_project.get_name();
|
||||||
|
|
||||||
if new_file.id == old_file.id {
|
if new_file_id == old_file.id {
|
||||||
pb.println(format!(
|
pb.println(format!(" {project_name} - Already up to date"));
|
||||||
" {} - Already up to date",
|
|
||||||
old_project.get_name()
|
|
||||||
));
|
|
||||||
} else {
|
} else {
|
||||||
// Interactive version selection if not using --yes flag
|
// Interactive confirmation and version selection if not using --yes
|
||||||
if !args.yes && updated_project.files.len() > 1 {
|
// flag
|
||||||
|
let mut should_update = args.yes || args.all;
|
||||||
|
let mut selected_idx: Option<usize> = None;
|
||||||
|
|
||||||
|
if !args.yes && !args.all {
|
||||||
pb.suspend(|| {
|
pb.suspend(|| {
|
||||||
|
// First, confirm the update
|
||||||
|
let prompt_msg = format!(
|
||||||
|
"Update '{project_name}' from {old_file_name} to \
|
||||||
|
{new_file_name}?"
|
||||||
|
);
|
||||||
|
should_update = prompt_yes_no(&prompt_msg, true).unwrap_or(false);
|
||||||
|
|
||||||
|
// If confirmed and multiple versions available, offer selection
|
||||||
|
if should_update && updated_project.files.len() > 1 {
|
||||||
let choices: Vec<String> = updated_project
|
let choices: Vec<String> = updated_project
|
||||||
.files
|
.files
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -111,30 +182,67 @@ pub async fn execute(
|
||||||
let choice_refs: Vec<&str> =
|
let choice_refs: Vec<&str> =
|
||||||
choices.iter().map(std::string::String::as_str).collect();
|
choices.iter().map(std::string::String::as_str).collect();
|
||||||
|
|
||||||
if let Ok(selected_idx) = prompt_select(
|
if let Ok(idx) = prompt_select(
|
||||||
&format!("Select version for {}:", old_project.get_name()),
|
&format!("Select version for {project_name}:"),
|
||||||
&choice_refs,
|
&choice_refs,
|
||||||
) {
|
) {
|
||||||
// Move selected file to front
|
selected_idx = Some(idx);
|
||||||
if selected_idx > 0 {
|
|
||||||
updated_project.files.swap(0, selected_idx);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply file selection outside the closure
|
||||||
|
if let Some(idx) = selected_idx
|
||||||
|
&& idx > 0
|
||||||
|
{
|
||||||
|
updated_project.files.swap(0, idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if should_update {
|
||||||
let selected_file = updated_project.files.first().unwrap();
|
let selected_file = updated_project.files.first().unwrap();
|
||||||
pb.println(format!(
|
pb.println(format!(
|
||||||
" {} -> {}",
|
" {} -> {}",
|
||||||
old_file.file_name, selected_file.file_name
|
old_file_name, selected_file.file_name
|
||||||
));
|
));
|
||||||
lockfile.projects[idx] = updated_project;
|
lockfile.projects[idx] = updated_project;
|
||||||
|
} else {
|
||||||
|
pb.println(format!(" {project_name} - Skipped by user"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pb.inc(1);
|
pb.inc(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if skipped_pinned > 0 {
|
||||||
|
pb.finish_with_message(format!(
|
||||||
|
"Update complete ({skipped_pinned} pinned projects skipped)"
|
||||||
|
));
|
||||||
|
} else {
|
||||||
pb.finish_with_message("Update complete");
|
pb.finish_with_message("Update complete");
|
||||||
|
}
|
||||||
lockfile.save(lockfile_dir)?;
|
lockfile.save(lockfile_dir)?;
|
||||||
|
|
||||||
|
// Report any errors that occurred during updates
|
||||||
|
if !update_errors.is_empty() {
|
||||||
|
let error_list = update_errors.errors();
|
||||||
|
log::warn!(
|
||||||
|
"{} project(s) encountered errors during update check",
|
||||||
|
error_list.len()
|
||||||
|
);
|
||||||
|
for err in error_list {
|
||||||
|
log::warn!(" - {err}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extend with any additional collected errors and check if we should fail
|
||||||
|
let all_errors = update_errors.into_errors();
|
||||||
|
if all_errors.len() == total_projects {
|
||||||
|
// All projects failed - return error
|
||||||
|
let mut multi = MultiError::new();
|
||||||
|
multi.extend(all_errors);
|
||||||
|
return multi.into_result(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue