feat(config): add support for excluding modules during installation and updates (#22793)

This commit is contained in:
Yehonal
2025-09-06 11:22:22 +02:00
committed by GitHub
parent 725b475dd4
commit d3a6c09b31
4 changed files with 785 additions and 76 deletions

View File

@@ -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