diff --git a/src/indexer.rs b/src/indexer.rs index 8240763..c7c3fe3 100644 --- a/src/indexer.rs +++ b/src/indexer.rs @@ -1,35 +1,12 @@ -use crate::config::{Config, geode_root}; +use crate::config::{geode_root}; use crate::input::ask_value; use std::fs; use std::path::PathBuf; use git2::{Repository, ResetType, IndexAddOption, Signature}; -use clap::Subcommand; use crate::package::mod_json_from_archive; -use crate::{info, warn, done, fatal}; +use crate::{info, done, fatal, warn}; use colored::Colorize; -#[derive(Subcommand, Debug)] -#[clap(rename_all = "kebab-case")] -pub enum Indexer { - /// Initializes your indexer - Init, - - /// Lists all entries in your indexer - List, - - /// Removes an entry from your indexer - Remove { - /// Mod ID that you want to remove - id: String - }, - - /// Exports an entry to your indexer, updating if it always exists - Export { - /// Path to the .geode file - package: PathBuf - } -} - fn reset_and_commit(repo: &Repository, msg: &str) { let head = repo.head().expect("Broken repository, can't get HEAD"); if !head.is_branch() { @@ -53,31 +30,47 @@ fn reset_and_commit(repo: &Repository, msg: &str) { repo.commit(Some("HEAD"), &sig, &sig, msg, &tree, &[&commit]).expect("Unable to commit"); } -fn initialize() { - let indexer_path = geode_root().join("indexer"); - if indexer_path.exists() { - warn!("Indexer is already initialized. Exiting."); - return; - } +pub fn indexer_path() -> PathBuf { + geode_root().join("indexer") +} - info!("Welcome to the Indexer Setup. Here, we will set up your indexer to be compatible with the Geode index."); - info!("Before continuing, make a github fork of https://github.com/geode-sdk/indexer."); +pub fn is_initialized() -> bool { + indexer_path().exists() +} - let fork_url = ask_value("Enter your forked URL", None, true); - Repository::clone(&fork_url, indexer_path).expect("Unable to clone your repository."); +pub fn initialize() { + if is_initialized() { + done!("Indexer is already initialized"); + return; + } - done!("Successfully initialized"); + info!( + "Before publishing mods on the Geode index, we need to make you a local \ + Indexer, which handles everything related to publishing mods." + ); + info!( + "The mod binaries will be hosted on your Indexer repository, which will \ + automatically request them to be added to the official Mods Index." + ); + info!( + "To get started, log in to Github using your account, and go to \ + https://github.com/geode-sdk/indexer/fork to make a fork of the Indexer." + ); + + let fork_url = ask_value("Enter the URL of your fork", None, true); + Repository::clone(&fork_url, indexer_path()).expect("Unable to clone your repository."); + + done!("Successfully initialized Indexer"); } -fn list_mods() { - let indexer_path = geode_root().join("indexer"); - if !indexer_path.exists() { - fatal!("Indexer has not yet been initialized."); +pub fn list_mods() { + if !is_initialized() { + fatal!("Indexer has not been set up - use `geode indexer init` to set it up"); } - println!("Mod list:"); + println!("Published mods:"); - for dir in fs::read_dir(indexer_path).unwrap() { + for dir in fs::read_dir(indexer_path()).unwrap() { let path = dir.unwrap().path(); if path.is_dir() && path.join("mod.geode").exists() { @@ -86,11 +79,11 @@ fn list_mods() { } } -fn remove_mod(id: String) { - let indexer_path = geode_root().join("indexer"); - if !indexer_path.exists() { - fatal!("Indexer has not yet been initialized."); +pub fn remove_mod(id: String) { + if !is_initialized() { + fatal!("Indexer has not been set up - use `geode indexer init` to set it up"); } + let indexer_path = indexer_path(); let mod_path = indexer_path.join(&id); if !mod_path.exists() { @@ -103,18 +96,18 @@ fn remove_mod(id: String) { reset_and_commit(&repo, &format!("Remove {}", &id)); done!("Succesfully removed {}\n", id); - info!("You will need to force-push this commit yourself. Type: "); - info!("git -C {} push -f", indexer_path.to_str().unwrap()); + info!("You will need to force-push to sync your changes."); + info!("Run `git -C {} push -f` to sync your changes", indexer_path.to_str().unwrap()); } -fn export_mod(package: PathBuf) { - let indexer_path = geode_root().join("indexer"); - if !indexer_path.exists() { - fatal!("Indexer has not yet been initialized."); +pub fn add_mod(package: PathBuf) { + if !is_initialized() { + fatal!("Indexer has not been set up - use `geode indexer init` to set it up"); } + let indexer_path = indexer_path(); if !package.exists() { - fatal!("Path not found"); + fatal!("Package path {} does not exist!", package.display()); } let mut archive = zip::ZipArchive::new(fs::File::open(&package).unwrap()).expect("Unable to read package"); @@ -142,28 +135,45 @@ fn export_mod(package: PathBuf) { let mod_path = indexer_path.join(format!("{}@{}", &mod_id, &major_version)); if !mod_path.exists() { - fs::create_dir(&mod_path).expect("Unable to create folder"); + fs::create_dir(&mod_path) + .expect("Unable to create directory in local indexer for mod"); } - fs::copy(package, mod_path.join("mod.geode")).expect("Unable to copy mod"); + fs::copy(package, mod_path.join("mod.geode")) + .expect("Unable to copy .geode package to local Indexer"); - let repo = Repository::open(&indexer_path).expect("Unable to open repository"); + let repo = Repository::open(&indexer_path) + .expect("Unable to open local Indexer repository"); reset_and_commit(&repo, &format!("Add/Update {}", &mod_id)); - done!("Successfully exported {}@{} to your indexer\n", mod_id, major_version); - - info!("You will need to force-push this commit yourself. Type: "); - info!("git -C {} push -f", indexer_path.to_str().unwrap()); -} - -pub fn subcommand(_config: &mut Config, cmd: Indexer) { - match cmd { - Indexer::Init => initialize(), - - Indexer::List => list_mods(), - - Indexer::Remove { id } => remove_mod(id), - - Indexer::Export { package } => export_mod(package) + match repo.find_remote("origin").and_then(|mut o| o.push(&["main"], None)) { + Ok(_) => { + done!( + "Succesfully added {}@{} to your indexer!", + mod_id, major_version + ); + }, + Err(_) => { + done!("Successfully added {}@{}\n", mod_id, major_version); + warn!( + "Unable to automatically sync the changes to Github. \ + You will need to push this commit yourself." + ); + info!("Run `git -C {} push -f` to push the commit", indexer_path.to_str().unwrap()); + }, + } + if let Some(url) = repo.find_remote("origin").unwrap().url() { + info!( + "To let us know you're ready to publish your mod, please open \ + a Pull Request on your repository: \ + {}/compare/geode-sdk:indexer:main...main", + url + ); } + else { + info!( + "To let us know you're ready to publish your mod, please open \ + a Pull Request on your Indexer fork repository." + ); + }; } diff --git a/src/main.rs b/src/main.rs index e7e7ae0..c24422c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -65,12 +65,6 @@ enum GeodeCommands { Index { #[clap(subcommand)] commands: crate::index::Index, - }, - - /// Subcommand for interacting with your indexer - Indexer { - #[clap(subcommand)] - commands: crate::indexer::Indexer, } } @@ -106,7 +100,6 @@ fn main() { GeodeCommands::Package { commands } => package::subcommand(&mut config, commands), GeodeCommands::Project { commands } => project::subcommand(&mut config, commands), GeodeCommands::Index { commands } => index::subcommand(&mut config, commands), - GeodeCommands::Indexer { commands } => indexer::subcommand(&mut config, commands), } config.save(); diff --git a/src/project.rs b/src/project.rs index 23b8c45..fcdfe66 100644 --- a/src/project.rs +++ b/src/project.rs @@ -2,13 +2,13 @@ use std::{fs, path::{PathBuf, Path}, collections::HashMap}; use clap::Subcommand; use semver::{Version, VersionReq}; -use crate::{util::{config::Config, mod_file::{parse_mod_info, ModFileInfo, Dependency, try_parse_mod_info}}, package::get_working_dir, done, warn, info, index::{update_index, index_mods_dir, install_mod}, fail, file::read_dir_recursive, fatal, template}; +use crate::{util::{config::Config, mod_file::{parse_mod_info, ModFileInfo, Dependency, try_parse_mod_info}}, package::get_working_dir, done, warn, info, index::{update_index, index_mods_dir, install_mod}, fail, file::read_dir_recursive, fatal, template, indexer}; use edit_distance::edit_distance; #[derive(Subcommand, Debug)] #[clap(rename_all = "kebab-case")] pub enum Project { - /// Initialize a new Geode project + /// Initialize a new Geode project (same as `geode new`) New { /// The target directory to create the project in path: Option @@ -32,28 +32,50 @@ pub enum Project { #[clap(long, num_args(0..))] externals: Vec, }, + + /// Publish this project on the Geode mods index + Publish { + /// Path to the project's built .geode file. If you are using Geode + /// v1.0.0-beta.8 or newer, CLI should be able to figure this out + /// automatically, unless you are building multiple mods from the + /// same directory + #[clap(short, long)] + package: Option, + }, + + /// Unpublish a project from the Geode mods index + Unpublish { + /// ID of the mod to unpublish. If not provided, current opened project + /// is used + id: Option, + }, + + /// List all published mods + ListPublished, } -fn find_build_directory(root: &Path, _config: &Config) -> Option { +fn find_build_directory(root: &Path) -> Option { #[cfg(windows)] { // this works for 99% of users. // if you want to parse the CMakeLists.txt file to find the true build // directory 100% of the time, go ahead, but i'm not doing it - for path in [ - root.join("build").join("RelWithDebInfo"), - root.join("build").join("Release"), - root.join("build").join("MinSizeRel"), - ] { - if path.exists() { - return Some(path); - } + if root.join("build").exists() { + return Some(root.join("build")); } } None } -fn clear_cache(dir: &Path, config: &Config) { +/// Get the project's built .geode file. Path argument should point to the +/// directory with the project's mod.json +pub fn get_built_package(root: &Path) -> Option { + let mod_info = try_parse_mod_info(root).ok()?; + let geode_pkg = find_build_directory(root)?.join(format!("{}.geode", mod_info.id)); + geode_pkg.exists().then_some(geode_pkg) +} + +fn clear_cache(dir: &Path) { // Parse mod.json let mod_info = parse_mod_info(dir); @@ -62,7 +84,7 @@ fn clear_cache(dir: &Path, config: &Config) { fs::remove_dir_all(workdir).expect("Unable to remove cache directory"); // Remove cached .geode package - let dir = find_build_directory(dir, config); + let dir = find_build_directory(dir); if let Some(dir) = dir { for file in fs::read_dir(&dir).expect("Unable to read build directory") { let path = file.unwrap().path(); @@ -432,11 +454,40 @@ pub fn check_dependencies(config: &Config, input: PathBuf, output: PathBuf, exte } } +pub fn publish_project(_config: &Config, dir: &Path, package_path: Option) { + let Some(pkg) = package_path.or(get_built_package(dir)) else { + fatal!( + "Unable to find the project's .geode package - please try manually \ + specifying the path to the project's built .geode package using \ + the `--package ` option.\nThis issue is likely caused by \ + an outdated Geode SDK version (at least 1.0.0-beta.8 needed) or \ + by building multiple projects from the same directory." + ); + }; + + // initialize indexer and add mod there + if !indexer::is_initialized() { + indexer::initialize(); + } + indexer::add_mod(pkg); +} + +pub fn unpublish_project(id: Option) { + if !indexer::is_initialized() { + fatal!("You don't seem to have any mods published!"); + } + else { + indexer::remove_mod( + id.unwrap_or_else(|| parse_mod_info(&std::env::current_dir().unwrap()).id) + ); + } +} + pub fn subcommand(config: &mut Config, cmd: Project) { match cmd { Project::New { path } => template::build_template(config, path), Project::ClearCache => clear_cache( - &std::env::current_dir().unwrap(), config + &std::env::current_dir().unwrap() ), Project::Check { install_dir, externals } => check_dependencies( config, @@ -444,5 +495,10 @@ pub fn subcommand(config: &mut Config, cmd: Project) { install_dir.unwrap_or("build".into()), externals ), + Project::Publish { package } => publish_project( + config, &std::env::current_dir().unwrap(), package + ), + Project::Unpublish { id } => unpublish_project(id), + Project::ListPublished => indexer::list_mods(), } }