initial commit
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: Ife1391ed23a1e7f388b1b5eca90b9ea76a6a6964
This commit is contained in:
commit
ef28bdaeb4
63 changed files with 17292 additions and 0 deletions
227
src/cli/commands/add.rs
Normal file
227
src/cli/commands/add.rs
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use crate::{
|
||||
error::{PakkerError, Result},
|
||||
model::{Config, LockFile, Project},
|
||||
platform::create_platform,
|
||||
resolver::DependencyResolver,
|
||||
};
|
||||
|
||||
fn get_loaders(lockfile: &LockFile) -> Vec<String> {
|
||||
lockfile.loaders.keys().cloned().collect()
|
||||
}
|
||||
|
||||
pub fn create_all_platforms()
|
||||
-> Result<HashMap<String, Box<dyn crate::platform::PlatformClient>>> {
|
||||
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);
|
||||
}
|
||||
|
||||
Ok(platforms)
|
||||
}
|
||||
|
||||
async fn resolve_input(
|
||||
input: &str,
|
||||
platforms: &HashMap<String, Box<dyn crate::platform::PlatformClient>>,
|
||||
lockfile: &LockFile,
|
||||
) -> Result<Project> {
|
||||
for platform in platforms.values() {
|
||||
if let Ok(project) = platform
|
||||
.request_project_with_files(
|
||||
input,
|
||||
&lockfile.mc_versions,
|
||||
&get_loaders(lockfile),
|
||||
)
|
||||
.await
|
||||
{
|
||||
return Ok(project);
|
||||
}
|
||||
}
|
||||
|
||||
Err(PakkerError::ProjectNotFound(input.to_string()))
|
||||
}
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use crate::{cli::AddArgs, model::fork::LocalConfig};
|
||||
|
||||
pub async fn execute(
|
||||
args: AddArgs,
|
||||
lockfile_path: &Path,
|
||||
config_path: &Path,
|
||||
) -> Result<()> {
|
||||
log::info!("Adding projects: {:?}", args.inputs);
|
||||
|
||||
// Load lockfile
|
||||
// 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("."));
|
||||
|
||||
// Check if lockfile exists (try both pakker-lock.json and pakku-lock.json)
|
||||
let lockfile_exists =
|
||||
lockfile_path.exists() || lockfile_dir.join("pakku-lock.json").exists();
|
||||
|
||||
if !lockfile_exists {
|
||||
// Try to load config from both pakker.json and pakku.json
|
||||
let local_config = LocalConfig::load(config_dir).or_else(|_| {
|
||||
let legacy_config_path = config_dir.join("pakku.json");
|
||||
if legacy_config_path.exists() {
|
||||
LocalConfig::load(&config_dir.join("pakku.json"))
|
||||
} else {
|
||||
Err(PakkerError::IoError(std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
"No pakker.json found",
|
||||
)))
|
||||
}
|
||||
})?;
|
||||
|
||||
if local_config.has_parent() {
|
||||
log::info!("Creating minimal fork lockfile with parent metadata...");
|
||||
|
||||
// Check for parent lockfile (try both pakker-lock.json and
|
||||
// pakku-lock.json)
|
||||
let parent_paths = [
|
||||
lockfile_dir.join(".pakku/parent/pakker-lock.json"),
|
||||
lockfile_dir.join(".pakku/parent/pakku-lock.json"),
|
||||
];
|
||||
|
||||
let parent_found = parent_paths.iter().any(|path| path.exists());
|
||||
if !parent_found {
|
||||
return Err(PakkerError::IoError(std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
"Fork configured but parent lockfile not found at \
|
||||
.pakku/parent/pakker-lock.json or .pakku/parent/pakku-lock.json",
|
||||
)));
|
||||
}
|
||||
|
||||
// Load parent lockfile to get metadata
|
||||
let parent_lockfile = parent_paths
|
||||
.iter()
|
||||
.find(|path| path.exists())
|
||||
.and_then(|path| LockFile::load(path.parent().unwrap()).ok())
|
||||
.ok_or_else(|| {
|
||||
PakkerError::IoError(std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
"Failed to load parent lockfile metadata",
|
||||
))
|
||||
})?;
|
||||
|
||||
let minimal_lockfile = LockFile {
|
||||
target: parent_lockfile.target,
|
||||
mc_versions: parent_lockfile.mc_versions,
|
||||
loaders: parent_lockfile.loaders,
|
||||
projects: Vec::new(),
|
||||
lockfile_version: 1,
|
||||
};
|
||||
minimal_lockfile.save_without_validation(lockfile_dir)?;
|
||||
} else {
|
||||
return Err(PakkerError::IoError(std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
"pakker-lock.json not found and no fork configured. Run 'pakker init' \
|
||||
first.",
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
let mut lockfile = LockFile::load_with_validation(lockfile_dir, false)?;
|
||||
|
||||
// Load config if available
|
||||
let _config = Config::load(config_dir).ok();
|
||||
|
||||
// Create platforms
|
||||
let platforms = create_all_platforms()?;
|
||||
|
||||
let mut new_projects = Vec::new();
|
||||
|
||||
// Resolve each input
|
||||
for input in &args.inputs {
|
||||
let project = resolve_input(input, &platforms, &lockfile).await?;
|
||||
|
||||
// Check if already exists by matching platform IDs (not pakku_id which is
|
||||
// random)
|
||||
let already_exists = lockfile.projects.iter().any(|p| {
|
||||
// Check if any platform ID matches
|
||||
project.id.iter().any(|(platform, id)| {
|
||||
p.id
|
||||
.get(platform)
|
||||
.is_some_and(|existing_id| existing_id == id)
|
||||
})
|
||||
});
|
||||
|
||||
if already_exists {
|
||||
if args.update {
|
||||
log::info!("Updating existing project: {}", project.get_name());
|
||||
// Find and replace the existing project
|
||||
if let Some(pos) = lockfile.projects.iter().position(|p| {
|
||||
project.id.iter().any(|(platform, id)| {
|
||||
p.id
|
||||
.get(platform)
|
||||
.is_some_and(|existing_id| existing_id == id)
|
||||
})
|
||||
}) {
|
||||
lockfile.projects[pos] = project;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
log::info!("Project already exists: {}", project.get_name());
|
||||
continue;
|
||||
}
|
||||
|
||||
new_projects.push(project);
|
||||
}
|
||||
|
||||
// Resolve dependencies unless --no-deps is specified
|
||||
if !args.no_deps {
|
||||
log::info!("Resolving dependencies...");
|
||||
|
||||
let mut resolver = DependencyResolver::new();
|
||||
let mut all_new_projects = new_projects.clone();
|
||||
|
||||
for project in &mut new_projects {
|
||||
let deps = resolver.resolve(project, &mut lockfile, &platforms).await?;
|
||||
|
||||
for dep in deps {
|
||||
if !lockfile.projects.iter().any(|p| p.pakku_id == dep.pakku_id)
|
||||
&& !all_new_projects.iter().any(|p| p.pakku_id == dep.pakku_id)
|
||||
{
|
||||
// Prompt user for confirmation unless --yes flag is set
|
||||
if !args.yes {
|
||||
let prompt_msg = format!(
|
||||
"Add dependency '{}' required by '{}'?",
|
||||
dep.get_name(),
|
||||
project.get_name()
|
||||
);
|
||||
if !crate::ui_utils::prompt_yes_no(&prompt_msg, true)? {
|
||||
log::info!("Skipping dependency: {}", dep.get_name());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
log::info!("Adding dependency: {}", dep.get_name());
|
||||
all_new_projects.push(dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new_projects = all_new_projects;
|
||||
}
|
||||
|
||||
// Add projects to lockfile (updates already handled above)
|
||||
for project in new_projects {
|
||||
lockfile.add_project(project);
|
||||
}
|
||||
|
||||
// Save lockfile
|
||||
lockfile.save(lockfile_dir)?;
|
||||
|
||||
log::info!("Successfully added {} project(s)", args.inputs.len());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue