diff options
author | Yehonal <yehonal.azeroth@gmail.com> | 2025-09-06 11:22:22 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-09-06 11:22:22 +0200 |
commit | d3a6c09b3164d5ad0f05924bdad30f98416e6503 (patch) | |
tree | a931e0ffca2782de26c5f791e04dfca566686dc9 /apps/installer/includes | |
parent | 725b475dd4522b34b2182c9721671f1a49ca1c80 (diff) |
feat(config): add support for excluding modules during installation and updates (#22793)
Diffstat (limited to 'apps/installer/includes')
-rw-r--r-- | apps/installer/includes/modules-manager/README.md | 145 | ||||
-rw-r--r-- | apps/installer/includes/modules-manager/modules.sh | 308 |
2 files changed, 377 insertions, 76 deletions
diff --git a/apps/installer/includes/modules-manager/README.md b/apps/installer/includes/modules-manager/README.md index f478035beb..93496a91a6 100644 --- a/apps/installer/includes/modules-manager/README.md +++ b/apps/installer/includes/modules-manager/README.md @@ -9,6 +9,10 @@ This directory contains the module management system for AzerothCore, providing - **Custom Directory Naming**: Prevent conflicts with custom directory names - **Duplicate Prevention**: Smart detection and prevention of duplicate installations - **Multi-Host Support**: GitHub, GitLab, and other Git hosts +- **Module Exclusion**: Support for excluding modules via environment variable +- **Interactive Menu System**: Easy-to-use menu interface for module management +- **Colored Output**: Enhanced terminal output with color support (respects NO_COLOR) +- **Flat Directory Structure**: Uses flat module installation (no owner subfolders) ## 📁 File Structure @@ -100,10 +104,33 @@ repo[:dirname][@branch[:commit]] # Search with multiple terms ./acore.sh module search auction house -# Show all available modules +# Search with input prompt ./acore.sh module search ``` +### Listing Installed Modules + +```bash +# List all installed modules +./acore.sh module list +``` + +### Interactive Menu + +```bash +# Start interactive menu system +./acore.sh module + +# Menu options: +# s - Search for available modules +# i - Install one or more modules +# u - Update installed modules +# r - Remove installed modules +# l - List installed modules +# h - Show detailed help +# q - Close this menu +``` + ## 🔍 Cross-Format Recognition The system intelligently recognizes the same module across different specification formats: @@ -129,13 +156,55 @@ The system prevents common conflicts: ```bash # If 'mod-transmog' directory already exists: $ ./acore.sh module install mod-transmog:mod-transmog -Error: Directory 'mod-transmog' already exists. Possible solutions: 1. Use a different directory name: mod-transmog:my-custom-name 2. Remove the existing directory first 3. Use the update command if this is the same module ``` +### Duplicate Module Prevention +The system uses intelligent owner/name matching to prevent installing the same module multiple times, even when specified in different formats. + +## 🚫 Module Exclusion + +You can exclude modules from installation using the `MODULES_EXCLUDE_LIST` environment variable: + +```bash +# Exclude specific modules (space-separated) +export MODULES_EXCLUDE_LIST="mod-test-module azerothcore/mod-dev-only" +./acore.sh module install --all # Will skip excluded modules + +# Supports cross-format matching +export MODULES_EXCLUDE_LIST="https://github.com/azerothcore/mod-transmog.git" +./acore.sh module install mod-transmog # Will be skipped as excluded +``` + +The exclusion system: +- Uses the same cross-format recognition as other module operations +- Works with all installation methods (`install`, `install --all`) +- Provides clear feedback when modules are skipped +- Supports URLs, owner/name format, and simple names + +## 🎨 Color Support + +The module manager provides enhanced terminal output with colors: + +- **Info**: Cyan text for informational messages +- **Success**: Green text for successful operations +- **Warning**: Yellow text for warnings +- **Error**: Red text for errors +- **Headers**: Bold cyan text for section headers + +Color support is automatically disabled when: +- Output is not to a terminal (piped/redirected) +- `NO_COLOR` environment variable is set +- Terminal doesn't support colors + +You can force color output with: +```bash +export FORCE_COLOR=1 +``` + ## 🔄 Integration ### Including in Scripts @@ -165,24 +234,78 @@ azerothcore/mod-transmog master abc123def456 https://github.com/custom/mod-custom.git develop def456abc789 mod-eluna:custom-eluna-dir main 789abc123def ``` + +The list maintains: +- **Alphabetical ordering** by normalized owner/name for consistency +- **Original format preservation** of how modules were specified +- **Automatic deduplication** across different specification formats +- **Custom directory tracking** when specified + ## 🔧 Configuration ### Environment Variables -- `MODULES_LIST_FILE`: Override default modules list path -- `J_PATH_MODULES`: Modules installation directory -- `AC_PATH_ROOT`: AzerothCore root path + +| Variable | Description | Default | +|----------|-------------|---------| +| `MODULES_LIST_FILE` | Override default modules list path | `$AC_PATH_ROOT/conf/modules.list` | +| `MODULES_EXCLUDE_LIST` | Space-separated list of modules to exclude | - | +| `J_PATH_MODULES` | Modules installation directory | `$AC_PATH_ROOT/modules` | +| `AC_PATH_ROOT` | AzerothCore root path | - | +| `NO_COLOR` | Disable colored output | - | +| `FORCE_COLOR` | Force colored output even when not TTY | - | ### Default Paths -- Modules list: `$AC_PATH_ROOT/conf/modules.list` +- **Modules list**: `$AC_PATH_ROOT/conf/modules.list` +- **Installation directory**: `$J_PATH_MODULES` (flat structure, no owner subfolders) + +## 🏗️ Architecture + +### Core Functions + +| Function | Purpose | +|----------|---------| +| `inst_module()` | Main dispatcher and interactive menu | +| `inst_parse_module_spec()` | Parse advanced module syntax | +| `inst_extract_owner_name()` | Normalize modules for cross-format recognition | +| `inst_mod_list_*()` | Module list management (read/write/update) | +| `inst_module_*()` | Module operations (install/update/remove/search) | + +### Key Features + +- **Flat Directory Structure**: All modules install directly under `modules/` without owner subdirectories +- **Smart Conflict Detection**: Prevents directory name conflicts with helpful suggestions +- **Cross-Platform Compatibility**: Works on Linux, macOS, and Windows (Git Bash) +- **Version Compatibility**: Checks `acore-module.json` for AzerothCore version compatibility +- **Git Integration**: Uses Joiner system for Git repository management + +### Debug Mode + +For debugging module operations, you can examine the generated commands: +```bash +# Check what Joiner commands would be executed +tail -f /tmp/joiner_called.txt # In test environments +``` ## 🤝 Contributing When modifying the module manager: -1. Maintain backwards compatibility -2. Update tests in `test_module_commands.bats` -3. Update this documentation -4. Test cross-format recognition thoroughly -5. Ensure helpful error messages +1. **Maintain backwards compatibility** with existing module list format +2. **Update tests** in `test_module_commands.bats` for new functionality +3. **Update this documentation** for any new features or changes +4. **Test cross-format recognition** thoroughly across all supported formats +5. **Ensure helpful error messages** for common user mistakes +6. **Test exclusion functionality** with various module specification formats +7. **Verify color output** works correctly in different terminal environments + +### Testing Guidelines +```bash +# Run all module-related tests +cd apps/installer +bats test/test_module_commands.bats +# Test with different environments +NO_COLOR=1 ./acore.sh module list +FORCE_COLOR=1 ./acore.sh module help +``` diff --git a/apps/installer/includes/modules-manager/modules.sh b/apps/installer/includes/modules-manager/modules.sh index 88cadf01e2..787d07677c 100644 --- a/apps/installer/includes/modules-manager/modules.sh +++ b/apps/installer/includes/modules-manager/modules.sh @@ -28,6 +28,49 @@ source "$CURRENT_PATH/../../../bash_shared/includes.sh" source "$CURRENT_PATH/../includes.sh" source "$AC_PATH_APPS/bash_shared/menu_system.sh" +# ----------------------------------------------------------------------------- +# Color support (disabled when not a TTY or NO_COLOR is set) +# ----------------------------------------------------------------------------- +if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then + if command -v tput >/dev/null 2>&1; then + _ac_cols=$(tput colors 2>/dev/null || echo 0) + else + _ac_cols=0 + fi +else + _ac_cols=0 +fi + +if [ "${FORCE_COLOR:-}" != "" ] || [ "${_ac_cols}" -ge 8 ]; then + C_RESET='\033[0m' + C_BOLD='\033[1m' + C_DIM='\033[2m' + C_RED='\033[31m' + C_GREEN='\033[32m' + C_YELLOW='\033[33m' + C_BLUE='\033[34m' + C_MAGENTA='\033[35m' + C_CYAN='\033[36m' +else + C_RESET='' + C_BOLD='' + C_DIM='' + C_RED='' + C_GREEN='' + C_YELLOW='' + C_BLUE='' + C_MAGENTA='' + C_CYAN='' +fi + +# Simple helpers for consistent colored output +function print_info() { printf "%b\n" "${C_CYAN}$*${C_RESET}"; } +function print_warn() { printf "%b\n" "${C_YELLOW}$*${C_RESET}"; } +function print_error() { printf "%b\n" "${C_RED}$*${C_RESET}"; } +function print_success() { printf "%b\n" "${C_GREEN}$*${C_RESET}"; } +function print_skip() { printf "%b\n" "${C_BLUE}$*${C_RESET}"; } +function print_header() { printf "%b\n" "${C_BOLD}${C_CYAN}$*${C_RESET}"; } + # Module management menu definition # Format: "key|short|description" module_menu_items=( @@ -65,11 +108,11 @@ function handle_module_command() { inst_module_help ;; "quit") - echo "Exiting module manager..." + print_info "Exiting module manager..." return 0 ;; *) - echo "Invalid option. Use 'help' to see available commands." + print_error "Invalid option. Use 'help' to see available commands." return 1 ;; esac @@ -77,7 +120,7 @@ function handle_module_command() { # Show detailed module help function inst_module_help() { - echo "AzerothCore Module Manager Help" + print_header "AzerothCore Module Manager Help" echo "===============================" echo "" echo "Usage:" @@ -106,20 +149,20 @@ function inst_module_help() { # List installed modules function inst_module_list() { - echo "Installed Modules:" + print_header "Installed Modules" echo "==================" local count=0 while read -r repo_ref branch commit; do [[ -z "$repo_ref" ]] && continue count=$((count + 1)) - echo " $count. $repo_ref ($branch)" + printf " %s. %b (%s)%b\n" "$count" "${C_GREEN}${repo_ref}" "${branch}" "${C_RESET}" if [[ "$commit" != "-" ]]; then - echo " Commit: $commit" + printf " %bCommit:%b %s\n" "${C_DIM}" "${C_RESET}" "$commit" fi done < <(inst_mod_list_read) if [[ $count -eq 0 ]]; then - echo " No modules installed." + print_warn " No modules installed." fi echo "" } @@ -160,8 +203,7 @@ function inst_module() { inst_module_list "${args[@]}" ;; *) - echo "Unknown module command: $cmd" - echo "Use 'help' to see available commands." + print_error "Unknown module command: $cmd. Use 'help' to see available commands." return 1 ;; esac @@ -188,20 +230,72 @@ function inst_parse_module_spec() { # Parse the new syntax: repo[:dirname][@branch[:commit]] - # First, extract custom directory name if present (format: repo:dirname@branch) + # First, check if this is a URL (contains :// or starts with git@) + local is_url=0 + if [[ "$spec" =~ :// ]] || [[ "$spec" =~ ^git@ ]]; then + is_url=1 + fi + + # Parse directory and branch differently for URLs vs simple names local repo_with_branch="$spec" - if [[ "$spec" =~ ^([^@:]+):([^@:]+)(@.*)?$ ]]; then - repo_with_branch="${BASH_REMATCH[1]}${BASH_REMATCH[3]}" - dirname="${BASH_REMATCH[2]}" + if [[ $is_url -eq 1 ]]; then + # For URLs, look for :dirname pattern, but be careful about ports + # Strategy: only match :dirname if it's clearly after the repository path + + # Look for :dirname patterns at the end, but not if it looks like a port + if [[ "$spec" =~ ^(.*\.git):([^@/:]+)(@.*)?$ ]]; then + # Repo ending with .git:dirname + repo_with_branch="${BASH_REMATCH[1]}${BASH_REMATCH[3]}" + dirname="${BASH_REMATCH[2]}" + elif [[ "$spec" =~ ^(.*://[^/]+/[^:]*[^0-9]):([^@/:]+)(@.*)?$ ]]; then + # URL with path ending in non-digit:dirname (avoid matching ports) + repo_with_branch="${BASH_REMATCH[1]}${BASH_REMATCH[3]}" + dirname="${BASH_REMATCH[2]}" + fi + # If no custom dirname found, repo_with_branch remains the original spec + else + # For simple names, use the original logic + if [[ "$spec" =~ ^([^@:]+):([^@:]+)(@.*)?$ ]]; then + repo_with_branch="${BASH_REMATCH[1]}${BASH_REMATCH[3]}" + dirname="${BASH_REMATCH[2]}" + fi fi # Now parse branch and commit from the repo part - if [[ "$repo_with_branch" =~ ^([^@]+)@([^:]+)(:(.+))?$ ]]; then - repo_part="${BASH_REMATCH[1]}" - branch="${BASH_REMATCH[2]}" - commit="${BASH_REMATCH[4]:-}" + # Be careful not to confuse URL @ with branch @ + if [[ "$repo_with_branch" =~ :// ]]; then + # For URLs, look for @ after the authority part + if [[ "$repo_with_branch" =~ ^[^/]*//[^/]+/.*@([^:]+)(:(.+))?$ ]]; then + # @ found in path part - treat as branch + repo_part="${repo_with_branch%@*}" + branch="${BASH_REMATCH[1]}" + commit="${BASH_REMATCH[3]:-}" + elif [[ "$repo_with_branch" =~ ^([^@]*@[^/]+/.*)@([^:]+)(:(.+))?$ ]]; then + # @ found after URL authority @ - treat as branch + repo_part="${BASH_REMATCH[1]}" + branch="${BASH_REMATCH[2]}" + commit="${BASH_REMATCH[4]:-}" + else + repo_part="$repo_with_branch" + fi + elif [[ "$repo_with_branch" =~ ^git@ ]]; then + # Git SSH format - look for @ after the initial git@host: part + if [[ "$repo_with_branch" =~ ^git@[^:]+:.*@([^:]+)(:(.+))?$ ]]; then + repo_part="${repo_with_branch%@*}" + branch="${BASH_REMATCH[1]}" + commit="${BASH_REMATCH[3]:-}" + else + repo_part="$repo_with_branch" + fi else - repo_part="$repo_with_branch" + # Non-URL format - use original logic + if [[ "$repo_with_branch" =~ ^([^@]+)@([^:]+)(:(.+))?$ ]]; then + repo_part="${BASH_REMATCH[1]}" + branch="${BASH_REMATCH[2]}" + commit="${BASH_REMATCH[4]:-}" + else + repo_part="$repo_with_branch" + fi fi # Normalize repo reference and extract owner/name. @@ -210,10 +304,39 @@ function inst_parse_module_spec() { # If repo_ref is a URL, extract owner/name from path when possible if [[ "$repo_ref" =~ :// ]] || [[ "$repo_ref" =~ ^git@ ]]; then - # Extract owner/name (last two path components) - owner_repo=$(echo "$repo_ref" | sed -E 's#(git@[^:]+:|https?://[^/]+/|ssh://[^/]+/)?(.*?)(\.git)?$#\2#') - owner="$(echo "$owner_repo" | awk -F'/' '{print $(NF-1)}')" - name="$(echo "$owner_repo" | awk -F'/' '{print $NF}' | sed -E 's/\.git$//')" + # Handle various URL formats + local path_part="" + if [[ "$repo_ref" =~ ^https?://[^/]+:?[0-9]*/(.+)$ ]]; then + # HTTPS URL (with or without port) + path_part="${BASH_REMATCH[1]}" + elif [[ "$repo_ref" =~ ^ssh://.*@[^/]+:?[0-9]*/(.+)$ ]]; then + # SSH URL with user@host:port/path format + path_part="${BASH_REMATCH[1]}" + elif [[ "$repo_ref" =~ ^ssh://[^@/]+:?[0-9]*/(.+)$ ]]; then + # SSH URL with host:port/path format (no user@) + path_part="${BASH_REMATCH[1]}" + elif [[ "$repo_ref" =~ ^git@[^:]+:(.+)$ ]]; then + # Git SSH format (git@host:path) + path_part="${BASH_REMATCH[1]}" + fi + + # Extract owner/name from path + if [[ -n "$path_part" ]]; then + # Remove .git suffix and any :dirname suffix + path_part="${path_part%.git}" + path_part="${path_part%:*}" + + if [[ "$path_part" == *"/"* ]]; then + owner="$(echo "$path_part" | awk -F'/' '{print $(NF-1)}')" + name="$(echo "$path_part" | awk -F'/' '{print $NF}')" + else + owner="unknown" + name="$path_part" + fi + else + owner="unknown" + name="unknown" + fi else owner_repo="$repo_ref" if [[ "$owner_repo" == *"/"* ]]; then @@ -266,21 +389,28 @@ function inst_extract_owner_name { base_ref="${repo_ref%%:*}" fi - if [[ "$base_ref" =~ ^https?://github\.com/([^/]+)/([^/]+)(\.git)?(/.*)?$ ]]; then - # HTTPS URL format - check this first before owner/name pattern + # Handle various URL formats with possible ports + if [[ "$base_ref" =~ ^https?://[^/]+:?[0-9]*/([^/]+)/([^/?]+) ]]; then + # HTTPS URL format (with or without port) - matches github.com, gitlab.com, custom hosts + local owner="${BASH_REMATCH[1]}" local name="${BASH_REMATCH[2]}" - name="${name%.git}" # Remove .git suffix if present - echo "${BASH_REMATCH[1]}/$name" - elif [[ "$base_ref" =~ ^https?://gitlab\.com/([^/]+)/([^/]+)(\.git)?(/.*)?$ ]]; then - # GitLab URL format + name="${name%:*}" # Remove any :dirname suffix first + name="${name%.git}" # Then remove .git suffix if present + echo "$owner/$name" + elif [[ "$base_ref" =~ ^ssh://[^/]+:?[0-9]*/([^/]+)/([^/?]+) ]]; then + # SSH URL format (with or without port) + local owner="${BASH_REMATCH[1]}" local name="${BASH_REMATCH[2]}" - name="${name%.git}" # Remove .git suffix if present - echo "${BASH_REMATCH[1]}/$name" - elif [[ "$base_ref" =~ ^git@github\.com:([^/]+)/([^/]+)(\.git)?$ ]]; then - # SSH URL format + name="${name%:*}" # Remove any :dirname suffix first + name="${name%.git}" # Then remove .git suffix if present + echo "$owner/$name" + elif [[ "$base_ref" =~ ^git@[^:]+:([^/]+)/([^/?]+) ]]; then + # Git SSH format (git@host:owner/repo) + local owner="${BASH_REMATCH[1]}" local name="${BASH_REMATCH[2]}" - name="${name%.git}" # Remove .git suffix if present - echo "${BASH_REMATCH[1]}/$name" + name="${name%:*}" # Remove any :dirname suffix first + name="${name%.git}" # Then remove .git suffix if present + echo "$owner/$name" elif [[ "$base_ref" =~ ^[^/]+/[^/]+$ ]]; then # Format: owner/name (check after URL patterns) echo "$base_ref" @@ -330,6 +460,40 @@ function inst_mod_list_read() { done < "$file" } +# Check whether a module spec matches the exclusion list. +# - Reads space/newline separated items from env var MODULES_EXCLUDE_LIST +# - Supports cross-format matching via inst_extract_owner_name +# Returns 0 if excluded, 1 otherwise. +function inst_mod_is_excluded() { + local spec="$1" + local target_owner_name + target_owner_name=$(inst_extract_owner_name "$spec") + + # No exclusions configured + if [[ -z "${MODULES_EXCLUDE_LIST:-}" ]]; then + return 1 + fi + + # Split on default IFS (space, tab, newline) + local items=() + # Use mapfile to split MODULES_EXCLUDE_LIST on newlines; fallback to space if no newlines + if [[ "${MODULES_EXCLUDE_LIST}" == *$'\n'* ]]; then + mapfile -t items <<< "${MODULES_EXCLUDE_LIST}" + else + read -r -a items <<< "${MODULES_EXCLUDE_LIST}" + fi + + local it it_owner + for it in "${items[@]}"; do + [[ -z "$it" ]] && continue + it_owner=$(inst_extract_owner_name "$it") + if [[ "$it_owner" == "$target_owner_name" ]]; then + return 0 + fi + done + return 1 +} + # Add or update an entry in the list: repo_ref branch commit # Removes any existing entries with the same owner/name to avoid duplicates function inst_mod_list_upsert() { @@ -448,12 +612,12 @@ function inst_check_module_conflict { local repo_ref="$2" if [ -d "$J_PATH_MODULES/$dirname" ]; then - echo "Error: Directory '$dirname' already exists." - echo "Possible solutions:" - echo " 1. Use a different directory name: $repo_ref:my-custom-name" - echo " 2. Remove the existing directory first" - echo " 3. Use the update command if this is the same module" - return 1 + print_error "Error: Directory '$dirname' already exists." + print_warn "Possible solutions:" + echo " 1. Use a different directory name: $repo_ref:my-custom-name" + echo " 2. Remove the existing directory first" + echo " 3. Use the update command if this is the same module" + return 1 fi return 0 } @@ -511,7 +675,7 @@ function inst_module_search { local CATALOG_URL="https://www.azerothcore.org/data/catalogue.json" - echo "Searching ${terms[*]}..." + print_header "Searching ${terms[*]}..." echo "" # Build candidate list from catalogue (full_name = owner/repo) @@ -567,9 +731,9 @@ function inst_module_search { read v b < <(inst_getVersionBranch "https://raw.githubusercontent.com/${mod_full}/master/acore-module.json") if [[ "$b" != "none" ]]; then - echo "-> $mod (tested with AC version: $v)" + printf "%b -> %b (tested with AC version: %s)%b\n" "" "${C_GREEN}${mod}${C_RESET}" "$v" "" else - echo "-> $mod (NOTE: The module latest tested AC revision is Unknown)" + printf "%b -> %b %b(NOTE: The module latest tested AC revision is Unknown)%b\n" "" "${C_GREEN}${mod}${C_RESET}" "${C_YELLOW}" "${C_RESET}" fi done @@ -590,7 +754,7 @@ function inst_module_install { local modules=("$@") - echo "Installing modules: ${modules[*]}" + print_header "Installing modules: ${modules[*]}" if $use_all; then # Install all modules from the list (respecting recorded branch and commit). @@ -602,14 +766,18 @@ function inst_module_install { local dup_error=0 while read -r repo_ref branch commit; do [ -z "$repo_ref" ] && continue + # Skip excluded modules when checking duplicates + if inst_mod_is_excluded "$repo_ref"; then + continue + fi parsed_output=$(inst_parse_module_spec "$repo_ref") IFS=' ' read -r _ owner modname _ _ url dirname <<< "$parsed_output" # dirname defaults to repo name; flat install path uses dirname only if [[ -n "${_seen[$dirname]:-}" ]]; then - echo "Error: duplicate module target directory '$dirname' detected in modules.list:" + print_error "Error: duplicate module target directory '$dirname' detected in modules.list:" echo " - ${_first[$dirname]}" echo " - ${repo_ref}" - echo "Use a custom folder name to disambiguate, e.g.: ${repo_ref}:$dirname-alt" + print_warn "Use a custom folder name to disambiguate, e.g.: ${repo_ref}:$dirname-alt" dup_error=1 else _seen[$dirname]=1 @@ -623,11 +791,16 @@ function inst_module_install { # Second pass: install in flat modules directory (no owner subfolders) while read -r repo_ref branch commit; do [ -z "$repo_ref" ] && continue + # Skip excluded entries during installation + if inst_mod_is_excluded "$repo_ref"; then + print_warn "[$repo_ref] Excluded by MODULES_EXCLUDE_LIST (skipping)." + continue + fi parsed_output=$(inst_parse_module_spec "$repo_ref") IFS=' ' read -r _ owner modname _ _ url dirname <<< "$parsed_output" if [ -d "$J_PATH_MODULES/$dirname" ]; then - echo "[$repo_ref] Already installed (skipping)." + print_skip "[$repo_ref] Already installed (skipping)." continue fi @@ -642,9 +815,9 @@ function inst_module_install { local curCommit curCommit=$(git -C "$J_PATH_MODULES/$dirname" rev-parse HEAD 2>/dev/null || echo "") inst_mod_list_upsert "$repo_ref" "$branch" "$curCommit" - echo "[$repo_ref] Installed." + print_success "[$repo_ref] Installed." else - echo "[$repo_ref] Install failed." + print_error "[$repo_ref] Install failed." exit 1; fi done < <(inst_mod_list_read) @@ -663,7 +836,7 @@ function inst_module_install { # Check if module is already installed (by owner/name matching) existing_repo_ref=$(inst_mod_is_installed "$spec" || true) if [ -n "$existing_repo_ref" ]; then - echo "[$spec] Already installed as [$existing_repo_ref] (skipping)." + print_skip "[$spec] Already installed as [$existing_repo_ref] (skipping)." continue fi @@ -689,14 +862,14 @@ function inst_module_install { fi if [[ "$v" == "none" || "$v" == "not-defined" || "$b" == "none" ]]; then def="$(inst_get_default_branch "$repo_ref")" - echo "Warning: $repo_ref has no compatible acore-module.json; installing from branch '$def' (latest commit)." + print_warn "Warning: $repo_ref has no compatible acore-module.json; installing from branch '$def' (latest commit)." b="$def" fi fi # Use flat directory structure with custom directory name if [ -d "$J_PATH_MODULES/$dirname" ]; then - echo "[$repo_ref] Already installed (skipping)." + print_skip "[$repo_ref] Already installed (skipping)." curCommit=$(git -C "$J_PATH_MODULES/$dirname" rev-parse HEAD 2>/dev/null || echo "") inst_mod_list_upsert "$repo_ref" "$b" "$curCommit" continue @@ -709,14 +882,14 @@ function inst_module_install { if git -C "$J_PATH_MODULES/$dirname" rev-parse --verify "$override_commit" >/dev/null 2>&1; then git -C "$J_PATH_MODULES/$dirname" checkout --quiet "$override_commit" else - echo "[$repo_ref] Warning: provided commit '$override_commit' not found; staying on branch '$b' HEAD." + print_warn "[$repo_ref] provided commit '$override_commit' not found; staying on branch '$b' HEAD." fi fi curCommit=$(git -C "$J_PATH_MODULES/$dirname" rev-parse HEAD 2>/dev/null || echo "") inst_mod_list_upsert "$repo_ref" "$b" "$curCommit" - echo "[$repo_ref] Installed in '$dirname'. Please re-run compiling and db assembly." + print_success "[$repo_ref] Installed in '$dirname'. Please re-run compiling and db assembly." else - echo "[$repo_ref] Install failed or module not found" + print_error "[$repo_ref] Install failed or module not found" exit 1; fi done @@ -748,21 +921,26 @@ function inst_module_update { local line repo_ref branch commit newCommit owner modname url dirname while read -r repo_ref branch commit; do [ -z "$repo_ref" ] && continue + # Skip excluded modules during update --all + if inst_mod_is_excluded "$repo_ref"; then + print_warn "[$repo_ref] Excluded by MODULES_EXCLUDE_LIST (skipping)." + continue + fi parsed_output=$(inst_parse_module_spec "$repo_ref") IFS=' ' read -r _ owner modname _ _ url dirname <<< "$parsed_output" dirname="${dirname:-$modname}" if [ ! -d "$J_PATH_MODULES/$dirname/" ]; then - echo "[$repo_ref] Not installed locally, skipping." + print_skip "[$repo_ref] Not installed locally, skipping." continue fi if Joiner:upd_repo "$url" "$dirname" "$branch" ""; then newCommit=$(git -C "$J_PATH_MODULES/$dirname" rev-parse HEAD 2>/dev/null || echo "") inst_mod_list_upsert "$repo_ref" "$branch" "$newCommit" - echo "[$repo_ref] Updated to latest commit on '$branch'." + print_success "[$repo_ref] Updated to latest commit on '$branch'." else - echo "[$repo_ref] Cannot update" + print_error "[$repo_ref] Cannot update" fi done < <(inst_mod_list_read) else @@ -793,11 +971,11 @@ function inst_module_update { fi if [[ "$v" == "none" || "$v" == "not-defined" || "$b" == "none" ]]; then if branch=$(git -C "$J_PATH_MODULES/$dirname" rev-parse --abbrev-ref HEAD 2>/dev/null); then - echo "Warning: $repo_ref has no compatible acore-module.json; updating current branch '$branch'." + print_warn "Warning: $repo_ref has no compatible acore-module.json; updating current branch '$branch'." b="$branch" else def="$(inst_get_default_branch "$repo_ref")" - echo "Warning: $repo_ref has no compatible acore-module.json and no git branch detected; updating default branch '$def'." + print_warn "Warning: $repo_ref has no compatible acore-module.json and no git branch detected; updating default branch '$def'." b="$def" fi fi @@ -806,12 +984,12 @@ function inst_module_update { if Joiner:upd_repo "$url" "$dirname" "$b" ""; then newCommit=$(git -C "$J_PATH_MODULES/$dirname" rev-parse HEAD 2>/dev/null || echo "") inst_mod_list_upsert "$repo_ref" "$b" "$newCommit" - echo "[$repo_ref] Done, please re-run compiling and db assembly" + print_success "[$repo_ref] Done, please re-run compiling and db assembly" else - echo "[$repo_ref] Cannot update" + print_error "[$repo_ref] Cannot update" fi else - echo "[$repo_ref] Cannot update! Path doesn't exist ($J_PATH_MODULES/$dirname/)" + print_error "[$repo_ref] Cannot update! Path doesn't exist ($J_PATH_MODULES/$dirname/)" fi done fi @@ -840,9 +1018,9 @@ function inst_module_remove { dirname="${dirname:-$modname}" if Joiner:remove "$dirname" ""; then inst_mod_list_remove "$repo_ref" - echo "[$repo_ref] Done, please re-run compiling" + print_success "[$repo_ref] Done, please re-run compiling" else - echo "[$repo_ref] Cannot remove" + print_error "[$repo_ref] Cannot remove" fi done |