Feat/refactoring-module-menu (#22733)

This commit is contained in:
Yehonal 2025-08-30 23:44:07 +02:00 committed by GitHub
parent 5c31e3b411
commit 5a79a4edce
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 1398 additions and 190 deletions

View File

@ -42,7 +42,12 @@ jobs:
- name: Install requirements
run: |
sudo apt install -y bats
sudo apt-get update
# Install bats-core >= 1.5.0 to support bats_require_minimum_version
sudo apt-get install -y git curl
git clone --depth 1 https://github.com/bats-core/bats-core.git /tmp/bats-core
sudo /tmp/bats-core/install.sh /usr/local
bats --version
./acore.sh install-deps
- name: Run bash script tests for ${{ matrix.test-module }}
@ -50,7 +55,7 @@ jobs:
TERM: xterm-256color
run: |
cd apps/test-framework
./run-tests.sh --tap
./run-tests.sh --tap --all
build-and-test:
name: Build and Integration Test

View File

@ -1,7 +1,9 @@
#!/usr/bin/env bats
# Require minimum BATS version to avoid warnings
bats_require_minimum_version 1.5.0
# Require minimum BATS version when supported (older distro packages lack this)
if type -t bats_require_minimum_version >/dev/null 2>&1; then
bats_require_minimum_version 1.5.0
fi
# AzerothCore Compiler Scripts Test Suite
# Tests the functionality of the compiler scripts using the unified test framework

View File

@ -118,142 +118,28 @@ function inst_allInOne() {
inst_download_client_data
}
function inst_getVersionBranch() {
local res="master"
local v="not-defined"
local MODULE_MAJOR=0
local MODULE_MINOR=0
local MODULE_PATCH=0
local MODULE_SPECIAL=0;
local ACV_MAJOR=0
local ACV_MINOR=0
local ACV_PATCH=0
local ACV_SPECIAL=0;
local curldata=$(curl -f --silent -H 'Cache-Control: no-cache' "$1" || echo "{}")
local parsed=$(echo "$curldata" | "$AC_PATH_DEPS/jsonpath/JSONPath.sh" -b '$.compatibility.*.[version,branch]')
############################################################
# Module helpers and dispatcher #
############################################################
semverParseInto "$ACORE_VERSION" ACV_MAJOR ACV_MINOR ACV_PATCH ACV_SPECIAL
if [[ ! -z "$parsed" ]]; then
readarray -t vers < <(echo "$parsed")
local idx
res="none"
# since we've the pair version,branch alternated in not associative and one-dimensional
# array, we've to simulate the association with length/2 trick
for idx in `seq 0 $((${#vers[*]}/2-1))`; do
semverParseInto "${vers[idx*2]}" MODULE_MAJOR MODULE_MINOR MODULE_PATCH MODULE_SPECIAL
if [[ $MODULE_MAJOR -eq $ACV_MAJOR && $MODULE_MINOR -le $ACV_MINOR ]]; then
res="${vers[idx*2+1]}"
v="${vers[idx*2]}"
fi
done
# Returns the default branch name of a GitHub repo in the azerothcore org.
# If the API call fails, defaults to "master".
function inst_get_default_branch() {
local repo="$1"
local def
def=$(curl --silent "https://api.github.com/repos/azerothcore/${repo}" \
| "$AC_PATH_DEPS/jsonpath/JSONPath.sh" -b '$.default_branch')
if [ -z "$def" ]; then
def="master"
fi
echo "$v" "$res"
}
function inst_module_search {
local res="$1"
local idx=0;
if [ -z "$1" ]; then
echo "Type what to search or leave blank for full list"
read -p "Insert name: " res
fi
local search="+$res"
echo "Searching $res..."
echo "";
readarray -t MODS < <(curl --silent "https://api.github.com/search/repositories?q=org%3Aazerothcore${search}+fork%3Atrue+topic%3Acore-module+sort%3Astars&type=" \
| "$AC_PATH_DEPS/jsonpath/JSONPath.sh" -b '$.items.*.name')
while (( ${#MODS[@]} > idx )); do
mod="${MODS[idx++]}"
read v b < <(inst_getVersionBranch "https://raw.githubusercontent.com/azerothcore/$mod/master/acore-module.json")
if [[ "$b" != "none" ]]; then
echo "-> $mod (tested with AC version: $v)"
else
echo "-> $mod (no revision available for AC v$AC_VERSION, it could not work!)"
fi
done
echo "";
echo "";
}
function inst_module_install {
local res
if [ -z "$1" ]; then
echo "Type the name of the module to install"
read -p "Insert name: " res
else
res="$1"
fi
read v b < <(inst_getVersionBranch "https://raw.githubusercontent.com/azerothcore/$res/master/acore-module.json")
if [[ "$b" != "none" ]]; then
Joiner:add_repo "https://github.com/azerothcore/$res" "$res" "$b" && echo "Done, please re-run compiling and db assembly. Read instruction on module repository for more information"
else
echo "Cannot install $res module: it doesn't exists or no version compatible with AC v$ACORE_VERSION are available"
fi
echo "";
echo "";
}
function inst_module_update {
local res;
local _tmp;
local branch;
local p;
if [ -z "$1" ]; then
echo "Type the name of the module to update"
read -p "Insert name: " res
else
res="$1"
fi
_tmp=$PWD
if [ -d "$J_PATH_MODULES/$res/" ]; then
read v b < <(inst_getVersionBranch "https://raw.githubusercontent.com/azerothcore/$res/master/acore-module.json")
cd "$J_PATH_MODULES/$res/"
# use current branch if something wrong with json
if [[ "$v" == "none" || "$v" == "not-defined" ]]; then
b=`git rev-parse --abbrev-ref HEAD`
fi
Joiner:upd_repo "https://github.com/azerothcore/$res" "$res" "$b" && echo "Done, please re-run compiling and db assembly" || echo "Cannot update"
cd $_tmp
else
echo "Cannot update! Path doesn't exist"
fi;
echo "";
echo "";
}
function inst_module_remove {
if [ -z "$1" ]; then
echo "Type the name of the module to remove"
read -p "Insert name: " res
else
res="$1"
fi
Joiner:remove "$res" && echo "Done, please re-run compiling" || echo "Cannot remove"
echo "";
echo "";
echo "$def"
}
# =============================================================================
# Module Management System
# =============================================================================
# Load the module manager functions from the dedicated modules-manager directory
source "$AC_PATH_INSTALLER/includes/modules-manager/modules.sh"
function inst_simple_restarter {
echo "Running $1 ..."
@ -292,4 +178,4 @@ function inst_download_client_data {
&& echo "unzip downloaded file in $path..." && unzip -q -o "$zipPath" -d "$path/" \
&& echo "Remove downloaded file" && rm "$zipPath" \
&& echo "INSTALLED_VERSION=$VERSION" > "$dataVersionFile"
}
}

View File

@ -0,0 +1,188 @@
# AzerothCore Module Manager
This directory contains the module management system for AzerothCore, providing advanced functionality for installing, updating, and managing server modules.
## 🚀 Features
- **Advanced Syntax**: Support for `repo[:dirname][@branch[:commit]]` format
- **Cross-Format Recognition**: Intelligent matching across URLs, SSH, and simple names
- **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
## 📁 File Structure
```
modules-manager/
├── modules.sh # Core module management functions
└── README.md # This documentation file
```
## 🔧 Module Specification Syntax
The module manager supports flexible syntax for specifying modules:
### New Syntax Format
```bash
repo[:dirname][@branch[:commit]]
```
### Examples
| Specification | Description |
|---------------|-------------|
| `mod-transmog` | Simple module name, uses default branch and directory |
| `mod-transmog:my-custom-dir` | Custom directory name |
| `mod-transmog@develop` | Specific branch |
| `mod-transmog:custom@develop:abc123` | Custom directory, branch, and commit |
| `https://github.com/owner/repo.git@main` | Full URL with branch |
| `git@github.com:owner/repo.git:custom-dir` | SSH URL with custom directory |
## 🎯 Usage Examples
### Installing Modules
```bash
# Simple module installation
./acore.sh module install mod-transmog
# Install with custom directory name
./acore.sh module install mod-transmog:my-transmog-dir
# Install specific branch
./acore.sh module install mod-transmog@develop
# Install with full specification
./acore.sh module install mod-transmog:custom-dir@develop:abc123
# Install from URL
./acore.sh module install https://github.com/azerothcore/mod-transmog.git@main
# Install multiple modules
./acore.sh module install mod-transmog mod-eluna:custom-eluna
# Install all modules from list
./acore.sh module install --all
```
### Updating Modules
```bash
# Update specific module
./acore.sh module update mod-transmog
# Update all modules
./acore.sh module update --all
# Update with branch specification
./acore.sh module update mod-transmog@develop
```
### Removing Modules
```bash
# Remove by simple name (cross-format recognition)
./acore.sh module remove mod-transmog
# Remove by URL (recognizes same module)
./acore.sh module remove https://github.com/azerothcore/mod-transmog.git
# Remove multiple modules
./acore.sh module remove mod-transmog mod-eluna
```
### Searching Modules
```bash
# Search for modules
./acore.sh module search transmog
# Search with multiple terms
./acore.sh module search auction house
# Show all available modules
./acore.sh module search
```
## 🔍 Cross-Format Recognition
The system intelligently recognizes the same module across different specification formats:
```bash
# These all refer to the same module:
mod-transmog
azerothcore/mod-transmog
https://github.com/azerothcore/mod-transmog.git
git@github.com:azerothcore/mod-transmog.git
```
This allows:
- Installing with one format and removing with another
- Preventing duplicates regardless of specification format
- Consistent module tracking across different input methods
## 🛡️ Conflict Prevention
The system prevents common conflicts:
### Directory 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
```
## 🔄 Integration
### Including in Scripts
```bash
# Source the module functions
source "$AC_PATH_INSTALLER/includes/modules-manager/modules.sh"
# Use module functions
inst_module_install "mod-transmog:custom-dir@develop"
```
### Testing
The module system is tested through the main installer test suite:
```bash
./apps/installer/test/test_module_commands.bats
```
## 📋 Module List Format
Modules are tracked in `conf/modules.list` with the format:
```
# Comments start with #
repo_reference branch commit
# Examples:
azerothcore/mod-transmog master abc123def456
https://github.com/custom/mod-custom.git develop def456abc789
mod-eluna:custom-eluna-dir main 789abc123def
```
## 🔧 Configuration
### Environment Variables
- `MODULES_LIST_FILE`: Override default modules list path
- `J_PATH_MODULES`: Modules installation directory
- `AC_PATH_ROOT`: AzerothCore root path
### Default Paths
- Modules list: `$AC_PATH_ROOT/conf/modules.list`
## 🤝 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

View File

@ -0,0 +1,735 @@
#!/usr/bin/env bash
# =============================================================================
# AzerothCore Module Manager Functions
# =============================================================================
# This file contains all functions related to module management in AzerothCore.
# It provides capabilities for installing, updating, removing, and searching
# modules with support for advanced syntax and intelligent cross-format matching.
#
# Main Features:
# - Advanced syntax: repo[:dirname][@branch[:commit]]
# - Legacy compatibility: repo:branch:commit
# - Cross-format module recognition (URLs, SSH, simple names)
# - Custom directory naming to prevent conflicts
# - Intelligent duplicate prevention
#
# Usage:
# source "path/to/modules.sh"
# inst_module_install "mod-transmog:my-custom-dir@develop:abc123"
#
# =============================================================================
# Dispatcher for the unified `module` command.
# Usage: ./acore.sh module <search|install|update|remove> [args...]
function inst_module() {
# Normalize arguments into an array
local tokens=()
read -r -a tokens <<< "$*"
local cmd="${tokens[0]}"
local args=("${tokens[@]:1}")
case "$cmd" in
""|"help"|"-h"|"--help")
echo "Usage:"
echo " ./acore.sh module search [terms...]"
echo " ./acore.sh module install [--all | modules...]"
echo " modules can be specified as: name[:branch[:commit]]"
echo " ./acore.sh module update [modules...]"
echo " ./acore.sh module remove [modules...]"
;;
"search"|"s")
inst_module_search "${args[@]}"
;;
"install"|"i")
inst_module_install "${args[@]}"
;;
"update"|"u")
inst_module_update "${args[@]}"
;;
"remove"|"r")
inst_module_remove "${args[@]}"
;;
*)
echo "Unknown subcommand: $cmd"
echo "Try: ./acore.sh module help"
;;
esac
}
# =============================================================================
# Module Specification Parsing
# =============================================================================
# Parse a module spec with advanced syntax:
# - New syntax: repo[:dirname][@branch[:commit]]
#
# Examples:
# "mod-transmog" -> uses default branch, directory name = mod-transmog
# "mod-transmog:custom-dir" -> uses default branch, directory name = custom-dir
# "mod-transmog@develop" -> uses develop branch, directory name = mod-transmog
# "mod-transmog:custom-dir@develop:abc123" -> custom directory, develop branch, specific commit
#
# Output: "repo_ref owner name branch commit url dirname"
function inst_parse_module_spec() {
local spec="$1"
local dirname="" branch="" commit="" repo_part=""
# Parse the new syntax: repo[:dirname][@branch[:commit]]
# First, extract custom directory name if present (format: repo:dirname@branch)
local repo_with_branch="$spec"
if [[ "$spec" =~ ^([^@:]+):([^@:]+)(@.*)?$ ]]; then
repo_with_branch="${BASH_REMATCH[1]}${BASH_REMATCH[3]}"
dirname="${BASH_REMATCH[2]}"
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]:-}"
else
repo_part="$repo_with_branch"
fi
# Normalize repo reference and extract owner/name.
local repo_ref owner name url owner_repo
repo_ref="$repo_part"
# 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$//')"
else
owner_repo="$repo_ref"
if [[ "$owner_repo" == *"/"* ]]; then
owner="$(echo "$owner_repo" | cut -d'/' -f1)"
name="$(echo "$owner_repo" | cut -d'/' -f2)"
else
owner="azerothcore"
name="$owner_repo"
repo_ref="$owner/$name"
fi
fi
# Build URL only if repo_ref is not already a URL
if [[ "$repo_ref" =~ :// ]] || [[ "$repo_ref" =~ ^git@ ]]; then
url="$repo_ref"
else
url="https://github.com/${repo_ref}"
fi
# Use custom dirname if provided, otherwise default to module name
if [ -z "$dirname" ]; then
dirname="$name"
fi
echo "$repo_ref" "$owner" "$name" "${branch:--}" "${commit:--}" "$url" "$dirname"
}
# =============================================================================
# Cross-Format Module Recognition
# =============================================================================
# Extract owner/name from any repository reference for intelligent matching.
# This enables recognizing the same module regardless of specification format.
#
# Supported formats:
# - GitHub HTTPS: https://github.com/owner/name.git
# - GitHub SSH: git@github.com:owner/name.git
# - GitLab HTTPS: https://gitlab.com/owner/name.git
# - Owner/name: owner/name
# - Simple name: mod-name (assumes azerothcore namespace)
#
# Returns: "owner/name" format for consistent comparison
function inst_extract_owner_name {
local repo_ref="$1"
# For URLs, don't remove dirname suffix since : is part of the URL
local base_ref="$repo_ref"
if [[ ! "$repo_ref" =~ :// ]] && [[ ! "$repo_ref" =~ ^git@ ]]; then
# Only remove dirname suffix for non-URL formats
base_ref="${repo_ref%%:*}"
fi
if [[ "$base_ref" =~ ^https?://github\.com/([^/]+)/([^/]+)(\.git)?(/.*)?$ ]]; then
# HTTPS URL format - check this first before owner/name pattern
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
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
local name="${BASH_REMATCH[2]}"
name="${name%.git}" # Remove .git suffix if present
echo "${BASH_REMATCH[1]}/$name"
elif [[ "$base_ref" =~ ^[^/]+/[^/]+$ ]]; then
# Format: owner/name (check after URL patterns)
echo "$base_ref"
elif [[ "$base_ref" =~ ^(mod-|module-)?([a-zA-Z0-9-]+)$ ]]; then
# Simple module name, assume azerothcore namespace
local modname="${BASH_REMATCH[2]}"
if [[ "$base_ref" == mod-* ]]; then
modname="$base_ref"
else
modname="mod-$modname"
fi
echo "azerothcore/$modname"
else
# Unknown format, return as-is
echo "$base_ref"
fi
}
# =============================================================================
# Module List Management
# =============================================================================
# Returns path to modules list file (configurable via MODULES_LIST_FILE).
function inst_modules_list_path() {
local path="${MODULES_LIST_FILE:-"$AC_PATH_ROOT/conf/modules.list"}"
echo "$path"
}
# Ensure the modules list file exists and its directory is created.
function inst_mod_list_ensure() {
local file
file="$(inst_modules_list_path)"
mkdir -p "$(dirname "$file")"
[ -f "$file" ] || touch "$file"
}
# Read modules list into stdout as triplets: "name branch commit"
# Skips comments (# ...) and blank lines.
function inst_mod_list_read() {
local file
file="$(inst_modules_list_path)"
[ -f "$file" ] || return 0
# shellcheck disable=SC2013
while IFS= read -r line; do
[[ -z "$line" || "$line" =~ ^[[:space:]]*# ]] && continue
echo "$line"
done < "$file"
}
# 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() {
local repo_ref="$1"; shift
local branch="$1"; shift
local commit="$1"; shift
local target_owner_name
target_owner_name=$(inst_extract_owner_name "$repo_ref")
inst_mod_list_ensure
local file tmp tmp_uns tmp_sorted
file="$(inst_modules_list_path)"
tmp="${file}.tmp"
tmp_uns="${file}.unsorted"
tmp_sorted="${file}.sorted"
# Build a list without existing duplicates
: > "$tmp_uns"
while read -r existing_ref existing_branch existing_commit; do
[[ -z "$existing_ref" ]] && continue
local existing_owner_name
existing_owner_name=$(inst_extract_owner_name "$existing_ref")
if [[ "$existing_owner_name" != "$target_owner_name" ]]; then
echo "$existing_ref $existing_branch $existing_commit" >> "$tmp_uns"
fi
done < <(inst_mod_list_read)
# Add/replace the new entry (preserving original repo_ref format)
echo "$repo_ref $branch $commit" >> "$tmp_uns"
# Create key-prefixed lines to sort by normalized owner/name
: > "$tmp"
while read -r r b c; do
[[ -z "$r" ]] && continue
local k
k=$(inst_extract_owner_name "$r")
printf "%s\t%s %s %s\n" "$k" "$r" "$b" "$c" >> "$tmp"
done < "$tmp_uns"
# Stable sort by key and strip the key
LC_ALL=C sort -t $'\t' -k1,1 -s "$tmp" | cut -f2- > "$tmp_sorted" && mv "$tmp_sorted" "$file"
rm -f "$tmp" "$tmp_uns" "$tmp_sorted" 2>/dev/null || true
}
# Remove an entry from the list by matching owner/name.
# This allows removing modules regardless of how they were specified (URL vs owner/name)
function inst_mod_list_remove() {
local repo_ref="$1"
local target_owner_name
target_owner_name=$(inst_extract_owner_name "$repo_ref")
local file
file="$(inst_modules_list_path)"
[ -f "$file" ] || return 0
local tmp_uns="${file}.unsorted"
local tmp="${file}.tmp"
local tmp_sorted="${file}.sorted"
# Keep only lines where owner/name doesn't match
: > "$tmp_uns"
while read -r existing_ref existing_branch existing_commit; do
[[ -z "$existing_ref" ]] && continue
local existing_owner_name
existing_owner_name=$(inst_extract_owner_name "$existing_ref")
if [[ "$existing_owner_name" != "$target_owner_name" ]]; then
echo "$existing_ref $existing_branch $existing_commit" >> "$tmp_uns"
fi
done < <(inst_mod_list_read)
# Key-prefix and sort for deterministic alphabetical order
: > "$tmp"
while read -r r b c; do
[[ -z "$r" ]] && continue
local k
k=$(inst_extract_owner_name "$r")
printf "%s\t%s %s %s\n" "$k" "$r" "$b" "$c" >> "$tmp"
done < "$tmp_uns"
LC_ALL=C sort -t $'\t' -k1,1 -s "$tmp" | cut -f2- > "$tmp_sorted" && mv "$tmp_sorted" "$file"
rm -f "$tmp" "$tmp_uns" "$tmp_sorted" 2>/dev/null || true
}
# Check if a module is already installed by comparing owner/name
# Returns the existing repo_ref if found, empty if not found
function inst_mod_is_installed() {
local spec="$1"
local target_owner_name
target_owner_name=$(inst_extract_owner_name "$spec")
# Use a different approach: read into a variable first, then process
local modules_content
modules_content=$(inst_mod_list_read)
# Process each line
while IFS= read -r line; do
[[ -z "$line" ]] && continue
read -r repo_ref branch commit <<< "$line"
local existing_owner_name
existing_owner_name=$(inst_extract_owner_name "$repo_ref")
if [[ "$existing_owner_name" == "$target_owner_name" ]]; then
echo "$repo_ref" # Return the existing entry
return 0
fi
done <<< "$modules_content"
return 1
}
# =============================================================================
# Conflict Detection and Validation
# =============================================================================
# Check for module directory conflicts with helpful error messages
function inst_check_module_conflict {
local dirname="$1"
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
fi
return 0
}
# =============================================================================
# Module Operations
# =============================================================================
# Get version and branch information from acore-module.json
function inst_getVersionBranch() {
local res="master"
local v="not-defined"
local MODULE_MAJOR=0
local MODULE_MINOR=0
local MODULE_PATCH=0
local MODULE_SPECIAL=0;
local ACV_MAJOR=0
local ACV_MINOR=0
local ACV_PATCH=0
local ACV_SPECIAL=0;
local curldata=$(curl -f --silent -H 'Cache-Control: no-cache' "$1" || echo "{}")
local parsed=$(echo "$curldata" | "$AC_PATH_DEPS/jsonpath/JSONPath.sh" -b '$.compatibility.*.[version,branch]')
semverParseInto "$ACORE_VERSION" ACV_MAJOR ACV_MINOR ACV_PATCH ACV_SPECIAL
if [[ ! -z "$parsed" ]]; then
readarray -t vers < <(echo "$parsed")
local idx
res="none"
# since we've the pair version,branch alternated in not associative and one-dimensional
# array, we've to simulate the association with length/2 trick
for idx in `seq 0 $((${#vers[*]}/2-1))`; do
semverParseInto "${vers[idx*2]}" MODULE_MAJOR MODULE_MINOR MODULE_PATCH MODULE_SPECIAL
if [[ $MODULE_MAJOR -eq $ACV_MAJOR && $MODULE_MINOR -le $ACV_MINOR ]]; then
res="${vers[idx*2+1]}"
v="${vers[idx*2]}"
fi
done
fi
echo "$v" "$res"
}
# Search for modules in the AzerothCore repository
function inst_module_search {
# Accept 0..N search terms; if none provided, prompt the user.
local terms=("$@")
if [ ${#terms[@]} -eq 0 ]; then
echo "Type what to search (blank for full list)"
read -p "Insert name(s): " _line
if [ -n "$_line" ]; then
read -r -a terms <<< "$_line"
fi
fi
local CATALOG_URL="https://www.azerothcore.org/data/catalogue.json"
echo "Searching ${terms[*]}..."
echo ""
# Build candidate list from catalogue (full_name = owner/repo)
local MODS=()
if command -v jq >/dev/null 2>&1; then
mapfile -t MODS < <(curl --silent -L "$CATALOG_URL" \
| jq -r '
[ .. | objects
| select(.full_name and .topics)
| select(.topics | index("azerothcore-module"))
]
| unique_by(.full_name)
| sort_by(.stargazers_count // 0) | reverse
| .[].full_name
')
else
# Fallback without jq: best-effort extraction of owner/repo
mapfile -t MODS < <(curl --silent -L "$CATALOG_URL" \
| grep -oE '\"full_name\"\\s*:\\s*\"[^\"/[:space:]]+/[^\"[:space:]]+\"' \
| sed -E 's/.*\"full_name\"\\s*:\\s*\"([^\"]+)\".*/\\1/' \
| sort -u)
fi
# Local AND filter on user terms (case-insensitive) against full_name
if (( ${#terms[@]} > 0 )); then
local filtered=()
local item
for item in "${MODS[@]}"; do
local keep=1
local lower="${item,,}"
local t
for t in "${terms[@]}"; do
[ -z "$t" ] && continue
if [[ "$lower" != *"${t,,}"* ]]; then
keep=0; break
fi
done
(( keep )) && filtered+=("$item")
done
MODS=("${filtered[@]}")
fi
if (( ${#MODS[@]} == 0 )); then
echo "No results."
echo ""
return 0
fi
local idx=0
while (( ${#MODS[@]} > idx )); do
local mod_full="${MODS[idx++]}" # owner/repo
local mod="${mod_full##*/}" # repo name only for display
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)"
else
echo "-> $mod (NOTE: The module latest tested AC revision is Unknown)"
fi
done
echo ""
echo ""
}
# Install one or more modules with advanced syntax support
function inst_module_install {
# Support multiple modules and the --all flag; prompt if none specified.
local args=("$@")
local use_all=false
if [ ${#args[@]} -gt 0 ] && { [ "${args[0]}" = "--all" ] || [ "${args[0]}" = "-a" ]; }; then
use_all=true
shift || true
fi
local modules=("$@")
echo "Installing modules: ${modules[*]}"
if $use_all; then
# Install all modules from the list (respecting recorded branch and commit).
inst_mod_list_ensure
local line repo_ref branch commit url owner modname dirname
# First pass: detect duplicate target directories (flat structure)
declare -A _seen _first
local dup_error=0
while read -r repo_ref branch commit; do
[ -z "$repo_ref" ] && continue
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:"
echo " - ${_first[$dirname]}"
echo " - ${repo_ref}"
echo "Use a custom folder name to disambiguate, e.g.: ${repo_ref}:$dirname-alt"
dup_error=1
else
_seen[$dirname]=1
_first[$dirname]="$repo_ref"
fi
done < <(inst_mod_list_read)
if [[ "$dup_error" -ne 0 ]]; then
return 1
fi
# Second pass: install in flat modules directory (no owner subfolders)
while read -r repo_ref branch commit; do
[ -z "$repo_ref" ] && continue
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)."
continue
fi
if Joiner:add_repo "$url" "$dirname" "$branch" ""; then
# Checkout the recorded commit if present
if [ -n "$commit" ]; then
git -C "$J_PATH_MODULES/$dirname" fetch --all --quiet || true
if git -C "$J_PATH_MODULES/$dirname" rev-parse --verify "$commit" >/dev/null 2>&1; then
git -C "$J_PATH_MODULES/$dirname" checkout --quiet "$commit"
fi
fi
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."
else
echo "[$repo_ref] Install failed."
exit 1;
fi
done < <(inst_mod_list_read)
else
# Install specified modules; prompt if none specified.
if [ ${#modules[@]} -eq 0 ]; then
echo "Type the name(s) of the module(s) to install"
read -p "Insert name(s): " _line
read -r -a modules <<< "$_line"
fi
local spec name override_branch override_commit v b def curCommit existing_repo_ref dirname
for spec in "${modules[@]}"; do
[ -z "$spec" ] && continue
# 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)."
continue
fi
parsed_output=$(inst_parse_module_spec "$spec")
IFS=' ' read -r repo_ref owner modname override_branch override_commit url dirname <<< "$parsed_output"
[ -z "$repo_ref" ] && continue
# Check for directory conflicts with custom directory names
if ! inst_check_module_conflict "$dirname" "$repo_ref"; then
continue
fi
# override_branch takes precedence; otherwise consult acore-module.json on azerothcore unless repo_ref contains owner or URL
if [ -n "$override_branch" ] && [ "$override_branch" != "-" ]; then
b="$override_branch"
else
# For GitHub repositories, use raw.githubusercontent.com to check acore-module.json
if [[ "$url" =~ github.com ]] || [[ "$repo_ref" =~ ^[^/]+/[^/]+$ ]]; then
read v b < <(inst_getVersionBranch "https://raw.githubusercontent.com/${owner}/${modname}/master/acore-module.json")
else
# Unknown host: try the repository URL as-is (may fail)
read v b < <(inst_getVersionBranch "${url}/master/acore-module.json")
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)."
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)."
curCommit=$(git -C "$J_PATH_MODULES/$dirname" rev-parse HEAD 2>/dev/null || echo "")
inst_mod_list_upsert "$repo_ref" "$b" "$curCommit"
continue
fi
if Joiner:add_repo "$url" "$dirname" "$b" ""; then
# If a commit was provided, try to checkout it
if [ -n "$override_commit" ] && [ "$override_commit" != "-" ]; then
git -C "$J_PATH_MODULES/$dirname" fetch --all --quiet || true
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."
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."
else
echo "[$repo_ref] Install failed or module not found"
exit 1;
fi
done
fi
echo ""
echo ""
}
# Update one or more modules
function inst_module_update {
# Support multiple modules and the --all flag; prompt if none specified.
local args=("$@")
local use_all=false
if [ ${#args[@]} -gt 0 ] && { [ "${args[0]}" = "--all" ] || [ "${args[0]}" = "-a" ]; }; then
use_all=true
shift || true
fi
local _tmp=$PWD
if $use_all; then
local line repo_ref branch commit newCommit owner modname url dirname
while read -r repo_ref branch commit; do
[ -z "$repo_ref" ] && continue
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."
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'."
else
echo "[$repo_ref] Cannot update"
fi
done < <(inst_mod_list_read)
else
local modules=("$@")
if [ ${#modules[@]} -eq 0 ]; then
echo "Type the name(s) of the module(s) to update"
read -p "Insert name(s): " _line
read -r -a modules <<< "$_line"
fi
local spec repo_ref override_branch override_commit owner modname url dirname v b branch def newCommit
for spec in "${modules[@]}"; do
[ -z "$spec" ] && continue
parsed_output=$(inst_parse_module_spec "$spec")
IFS=' ' read -r repo_ref owner modname override_branch override_commit url dirname <<< "$parsed_output"
dirname="${dirname:-$modname}"
if [ -d "$J_PATH_MODULES/$dirname/" ]; then
# determine preferred branch if not provided
if [ -n "$override_branch" ] && [ "$override_branch" != "-" ]; then
b="$override_branch"
else
# try reading acore-module.json for this repo
if [[ "$url" =~ github.com ]]; then
read v b < <(inst_getVersionBranch "https://raw.githubusercontent.com/${owner}/${modname}/master/acore-module.json")
else
read v b < <(inst_getVersionBranch "${url}/master/acore-module.json")
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'."
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'."
b="$def"
fi
fi
fi
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"
else
echo "[$repo_ref] Cannot update"
fi
else
echo "[$repo_ref] Cannot update! Path doesn't exist ($J_PATH_MODULES/$dirname/)"
fi
done
fi
echo ""
echo ""
}
# Remove one or more modules
function inst_module_remove {
# Support multiple modules; prompt if none specified.
local modules=("$@")
if [ ${#modules[@]} -eq 0 ]; then
echo "Type the name(s) of the module(s) to remove"
read -p "Insert name(s): " _line
read -r -a modules <<< "$_line"
fi
local spec repo_ref owner modname url override_branch override_commit dirname
for spec in "${modules[@]}"; do
[ -z "$spec" ] && continue
parsed_output=$(inst_parse_module_spec "$spec")
IFS=' ' read -r repo_ref owner modname override_branch override_commit url dirname <<< "$parsed_output"
[ -z "$repo_ref" ] && continue
dirname="${dirname:-$modname}"
if Joiner:remove "$dirname" ""; then
inst_mod_list_remove "$repo_ref"
echo "[$repo_ref] Done, please re-run compiling"
else
echo "[$repo_ref] Cannot remove"
fi
done
echo ""
echo ""
}

View File

@ -6,22 +6,22 @@ source "$CURRENT_PATH/includes/includes.sh"
PS3='[Please enter your choice]: '
options=(
"init (i): First Installation" # 1
"install-deps (d): Configure OS dep" # 2
"pull (u): Update Repository" # 3
"reset (r): Reset & Clean Repository" # 4
"compiler (c): Run compiler tool" # 5
"module-search (ms): Module Search by keyword" # 6
"module-install (mi): Module Install by name" # 7
"module-update (mu): Module Update by name" # 8
"module-remove: (mr): Module Remove by name" # 9
"client-data: (gd): download client data from github repository (beta)" # 10
"run-worldserver (rw): execute a simple restarter for worldserver" # 11
"run-authserver (ra): execute a simple restarter for authserver" # 12
"docker (dr): Run docker tools" # 13
"version (v): Show AzerothCore version" # 14
"service-manager (sm): Run service manager to run authserver and worldserver in background" # 15
"quit: Exit from this menu" # 16
"init (i): First Installation"
"install-deps (d): Configure OS dep"
"pull (u): Update Repository"
"reset (r): Reset & Clean Repository"
"compiler (c): Run compiler tool"
"module (m): Module manager (search/install/update/remove)"
"module-install (mi): Module Install by name [DEPRECATED]"
"module-update (mu): Module Update by name [DEPRECATED]"
"module-remove: (mr): Module Remove by name [DEPRECATED]"
"client-data: (gd): download client data from github repository (beta)"
"run-worldserver (rw): execute a simple restarter for worldserver"
"run-authserver (ra): execute a simple restarter for authserver"
"docker (dr): Run docker tools"
"version (v): Show AzerothCore version"
"service-manager (sm): Run service manager to run authserver and worldserver in background"
"quit (q): Exit from this menu"
)
function _switch() {
@ -29,56 +29,64 @@ function _switch() {
_opt="$2"
case $_reply in
""|"i"|"init"|"1")
""|"i"|"init")
inst_allInOne
;;
""|"d"|"install-deps"|"2")
""|"d"|"install-deps")
inst_configureOS
;;
""|"u"|"pull"|"3")
""|"u"|"pull")
inst_updateRepo
;;
""|"r"|"reset"|"4")
""|"r"|"reset")
inst_resetRepo
;;
""|"c"|"compiler"|"5")
""|"c"|"compiler")
bash "$AC_PATH_APPS/compiler/compiler.sh" $_opt
;;
""|"ms"|"module-search"|"6")
inst_module_search "$_opt"
""|"m"|"module")
# Unified module command: supports subcommands search|install|update|remove
inst_module "${@:2}"
;;
""|"mi"|"module-install"|"7")
inst_module_install "$_opt"
""|"ms"|"module-search")
echo "[DEPRECATED] Use: ./acore.sh module search <terms...>"
inst_module_search "${@:2}"
;;
""|"mu"|"module-update"|"8")
inst_module_update "$_opt"
""|"mi"|"module-install")
echo "[DEPRECATED] Use: ./acore.sh module install <modules...>"
inst_module_install "${@:2}"
;;
""|"mr"|"module-remove"|"9")
inst_module_remove "$_opt"
""|"mu"|"module-update")
echo "[DEPRECATED] Use: ./acore.sh module update <modules...>"
inst_module_update "${@:2}"
;;
""|"gd"|"client-data"|"10")
""|"mr"|"module-remove")
echo "[DEPRECATED] Use: ./acore.sh module remove <modules...>"
inst_module_remove "${@:2}"
;;
""|"gd"|"client-data")
inst_download_client_data
;;
""|"rw"|"run-worldserver"|"11")
""|"rw"|"run-worldserver")
inst_simple_restarter worldserver
;;
""|"ra"|"run-authserver"|"12")
""|"ra"|"run-authserver")
inst_simple_restarter authserver
;;
""|"dr"|"docker"|"13")
""|"dr"|"docker")
DOCKER=1 bash "$AC_PATH_ROOT/apps/docker/docker-cmd.sh" "${@:2}"
exit
;;
""|"v"|"version"|"14")
""|"v"|"version")
# denoRunFile "$AC_PATH_APPS/installer/main.ts" "version"
printf "AzerothCore Rev. %s\n" "$ACORE_VERSION"
exit
;;
""|"sm"|"service-manager"|"15")
""|"sm"|"service-manager")
bash "$AC_PATH_APPS/startup-scripts/src/service-manager.sh" "${@:2}"
exit
;;
""|"quit"|"16")
""|"q"|"quit")
echo "Goodbye!"
exit
;;
@ -102,4 +110,5 @@ do
_switch $REPLY
break
done
echo "opt: $opt"
done

View File

@ -0,0 +1,14 @@
# BATS Test Configuration
# Set test timeout (in seconds)
export BATS_TEST_TIMEOUT=30
# Enable verbose output for debugging
export BATS_VERBOSE_RUN=1
# Test output format
export BATS_FORMATTER=pretty
# Enable colored output
export BATS_NO_PARALLELIZE_ACROSS_FILES=1
export BATS_NO_PARALLELIZE_WITHIN_FILE=1

View File

@ -0,0 +1,354 @@
#!/usr/bin/env bats
# Tests for installer module commands (search/install/update/remove)
# Focused on installer:module install behavior using a mocked joiner
load '../../test-framework/bats_libs/acore-support'
load '../../test-framework/bats_libs/acore-assert'
setup() {
acore_test_setup
# Point to the installer src directory (not needed in this test)
# Set installer/paths environment for the test
export AC_PATH_APPS="$TEST_DIR/apps"
export AC_PATH_ROOT="$TEST_DIR"
export AC_PATH_DEPS="$TEST_DIR/deps"
export AC_PATH_MODULES="$TEST_DIR/modules"
export MODULES_LIST_FILE="$TEST_DIR/conf/modules.list"
# Create stubbed deps: joiner.sh (sourced by includes) and semver
mkdir -p "$TEST_DIR/deps/acore/joiner"
cat > "$TEST_DIR/deps/acore/joiner/joiner.sh" << 'EOF'
#!/usr/bin/env bash
# Stub joiner functions used by installer
Joiner:add_repo() {
# arguments: url name branch basedir
echo "ADD $@" > "$TEST_DIR/joiner_called.txt"
return 0
}
Joiner:upd_repo() {
echo "UPD $@" > "$TEST_DIR/joiner_called.txt"
return 0
}
Joiner:remove() {
echo "REM $@" > "$TEST_DIR/joiner_called.txt"
return 0
}
EOF
chmod +x "$TEST_DIR/deps/acore/joiner/joiner.sh"
mkdir -p "$TEST_DIR/deps/semver_bash"
# Minimal semver stub
cat > "$TEST_DIR/deps/semver_bash/semver.sh" << 'EOF'
#!/usr/bin/env bash
# semver stub
semver::satisfies() { return 0; }
EOF
chmod +x "$TEST_DIR/deps/semver_bash/semver.sh"
# Provide a minimal compiler includes file expected by installer
mkdir -p "$TEST_DIR/apps/compiler/includes"
touch "$TEST_DIR/apps/compiler/includes/includes.sh"
# Provide minimal bash_shared includes to satisfy installer include
mkdir -p "$TEST_DIR/apps/bash_shared"
cat > "$TEST_DIR/apps/bash_shared/includes.sh" << 'EOF'
#!/usr/bin/env bash
# minimal stub
EOF
# Copy the real installer app into the test apps dir
mkdir -p "$TEST_DIR/apps"
cp -r "$(cd "$AC_TEST_ROOT/apps/installer" && pwd)" "$TEST_DIR/apps/installer"
}
teardown() {
acore_test_teardown
}
@test "module install should call joiner and record entry in modules list" {
cd "$TEST_DIR"
# Source installer includes and call the install function directly to avoid menu interaction
run bash -c "source '$TEST_DIR/apps/installer/includes/includes.sh' && inst_module_install example-module@main:abcd1234"
# Check that joiner was called
[ -f "$TEST_DIR/joiner_called.txt" ]
grep -q "ADD" "$TEST_DIR/joiner_called.txt"
# Check modules list was created and contains the repo_ref and branch
[ -f "$TEST_DIR/conf/modules.list" ]
grep -q "azerothcore/example-module main" "$TEST_DIR/conf/modules.list"
}
@test "module install with owner/name format should work" {
cd "$TEST_DIR"
# Test with owner/name format
run bash -c "source '$TEST_DIR/apps/installer/includes/includes.sh' && inst_module_install myorg/mymodule"
# Check that joiner was called with correct URL
[ -f "$TEST_DIR/joiner_called.txt" ]
grep -q "ADD https://github.com/myorg/mymodule mymodule" "$TEST_DIR/joiner_called.txt"
# Check modules list contains the entry
[ -f "$TEST_DIR/conf/modules.list" ]
grep -q "myorg/mymodule" "$TEST_DIR/conf/modules.list"
}
@test "module remove should call joiner remove and update modules list" {
cd "$TEST_DIR"
# First install a module
bash -c "source '$TEST_DIR/apps/installer/includes/includes.sh' && inst_module_install test-module"
# Then remove it
run bash -c "source '$TEST_DIR/apps/installer/includes/includes.sh' && inst_module_remove test-module"
# Check that joiner remove was called
[ -f "$TEST_DIR/joiner_called.txt" ]
# With flat structure, basedir is empty; ensure name is present
grep -q "REM test-module" "$TEST_DIR/joiner_called.txt"
# Check modules list no longer contains the entry
[ -f "$TEST_DIR/conf/modules.list" ]
! grep -q "azerothcore/test-module" "$TEST_DIR/conf/modules.list"
}
# Tests for intelligent module management (duplicate prevention and cross-format removal)
@test "inst_extract_owner_name should extract owner/name from various formats" {
cd "$TEST_DIR"
source "$TEST_DIR/apps/installer/includes/includes.sh"
# Test simple name
run inst_extract_owner_name "mod-transmog"
[ "$output" = "azerothcore/mod-transmog" ]
# Test owner/name format
run inst_extract_owner_name "azerothcore/mod-transmog"
[ "$output" = "azerothcore/mod-transmog" ]
# Test HTTPS URL
run inst_extract_owner_name "https://github.com/azerothcore/mod-transmog.git"
[ "$output" = "azerothcore/mod-transmog" ]
# Test SSH URL
run inst_extract_owner_name "git@github.com:azerothcore/mod-transmog.git"
[ "$output" = "azerothcore/mod-transmog" ]
# Test GitLab URL
run inst_extract_owner_name "https://gitlab.com/myorg/mymodule.git"
[ "$output" = "myorg/mymodule" ]
}
@test "duplicate module entries should be prevented across different formats" {
cd "$TEST_DIR"
source "$TEST_DIR/apps/installer/includes/includes.sh"
# Add module via simple name
inst_mod_list_upsert "mod-transmog" "master" "abc123"
# Verify it's in the list
grep -q "mod-transmog master abc123" "$TEST_DIR/conf/modules.list"
# Add same module via owner/name format - should replace, not duplicate
inst_mod_list_upsert "azerothcore/mod-transmog" "dev" "def456"
# Should only have one entry (the new one)
[ "$(grep -c "azerothcore/mod-transmog" "$TEST_DIR/conf/modules.list")" -eq 1 ]
grep -q "azerothcore/mod-transmog dev def456" "$TEST_DIR/conf/modules.list"
! grep -q "mod-transmog master abc123" "$TEST_DIR/conf/modules.list"
}
@test "module installed via URL should be recognized when checking with different formats" {
cd "$TEST_DIR"
source "$TEST_DIR/apps/installer/includes/includes.sh"
# Install via HTTPS URL
inst_mod_list_upsert "https://github.com/azerothcore/mod-transmog.git" "master" "abc123"
# Should be detected as installed using simple name
run inst_mod_is_installed "mod-transmog"
[ "$status" -eq 0 ]
# Should be detected as installed using owner/name
run inst_mod_is_installed "azerothcore/mod-transmog"
[ "$status" -eq 0 ]
# Should be detected as installed using SSH URL
run inst_mod_is_installed "git@github.com:azerothcore/mod-transmog.git"
[ "$status" -eq 0 ]
# Non-existent module should not be detected
run inst_mod_is_installed "mod-nonexistent"
[ "$status" -ne 0 ]
}
@test "cross-format module removal should work" {
cd "$TEST_DIR"
source "$TEST_DIR/apps/installer/includes/includes.sh"
# Install via SSH URL
inst_mod_list_upsert "git@github.com:azerothcore/mod-transmog.git" "master" "abc123"
# Verify it's installed
grep -q "git@github.com:azerothcore/mod-transmog.git" "$TEST_DIR/conf/modules.list"
# Remove using simple name
inst_mod_list_remove "mod-transmog"
# Should be completely removed
! grep -q "azerothcore/mod-transmog" "$TEST_DIR/conf/modules.list"
! grep -q "git@github.com:azerothcore/mod-transmog.git" "$TEST_DIR/conf/modules.list"
}
@test "module installation should prevent duplicates when already installed" {
cd "$TEST_DIR"
source "$TEST_DIR/apps/installer/includes/includes.sh"
# Install via simple name first
inst_mod_list_upsert "mod-worldchat" "master" "abc123"
# Try to install same module via URL - should detect it's already installed
run inst_mod_is_installed "https://github.com/azerothcore/mod-worldchat.git"
[ "$status" -eq 0 ]
# Add via URL should replace the existing entry
inst_mod_list_upsert "https://github.com/azerothcore/mod-worldchat.git" "dev" "def456"
# Should only have one entry
[ "$(grep -c "azerothcore/mod-worldchat" "$TEST_DIR/conf/modules.list")" -eq 1 ]
grep -q "https://github.com/azerothcore/mod-worldchat.git dev def456" "$TEST_DIR/conf/modules.list"
}
@test "module update --all uses flat structure (no branch subfolders)" {
cd "$TEST_DIR"
source "$TEST_DIR/apps/installer/includes/includes.sh"
# Prepare modules.list with one entry and a matching local directory
mkdir -p "$TEST_DIR/conf"
echo "azerothcore/mod-transmog master abc123" > "$TEST_DIR/conf/modules.list"
mkdir -p "$TEST_DIR/modules/mod-transmog"
# Run update all
run bash -c "source '$TEST_DIR/apps/installer/includes/includes.sh' && inst_module_update --all"
# Verify Joiner:upd_repo received flat structure args (no basedir)
[ -f "$TEST_DIR/joiner_called.txt" ]
grep -q "UPD https://github.com/azerothcore/mod-transmog mod-transmog master" "$TEST_DIR/joiner_called.txt"
}
@test "module update specific uses flat structure with override branch" {
cd "$TEST_DIR"
source "$TEST_DIR/apps/installer/includes/includes.sh"
# Create local directory so update proceeds
mkdir -p "$TEST_DIR/modules/mymodule"
# Run update specifying owner/name and branch
run bash -c "source '$TEST_DIR/apps/installer/includes/includes.sh' && inst_module_update myorg/mymodule@dev"
# Should call joiner with name 'mymodule' and branch 'dev' (no basedir)
[ -f "$TEST_DIR/joiner_called.txt" ]
grep -q "UPD https://github.com/myorg/mymodule mymodule dev" "$TEST_DIR/joiner_called.txt"
}
@test "custom directory names should work with new syntax" {
cd "$TEST_DIR"
source "$TEST_DIR/apps/installer/includes/includes.sh"
# Test parsing with custom directory name
run inst_parse_module_spec "mod-transmog:my-custom-dir@develop:abc123"
[ "$status" -eq 0 ]
# Should output: repo_ref owner name branch commit url dirname
IFS=' ' read -r repo_ref owner name branch commit url dirname <<< "$output"
[ "$repo_ref" = "azerothcore/mod-transmog" ]
[ "$owner" = "azerothcore" ]
[ "$name" = "mod-transmog" ]
[ "$branch" = "develop" ]
[ "$commit" = "abc123" ]
[ "$url" = "https://github.com/azerothcore/mod-transmog" ]
[ "$dirname" = "my-custom-dir" ]
}
@test "directory conflict detection should work" {
cd "$TEST_DIR"
source "$TEST_DIR/apps/installer/includes/includes.sh"
# Create a fake existing directory
mkdir -p "$TEST_DIR/modules/existing-dir"
# Should detect conflict
run inst_check_module_conflict "existing-dir" "mod-test"
[ "$status" -eq 1 ]
[[ "$output" =~ "Directory 'existing-dir' already exists" ]]
[[ "$output" =~ "Use a different directory name: mod-test:my-custom-name" ]]
# Should not detect conflict for non-existing directory
run inst_check_module_conflict "non-existing-dir" "mod-test"
[ "$status" -eq 0 ]
}
@test "module update should work with custom directories" {
cd "$TEST_DIR"
source "$TEST_DIR/apps/installer/includes/includes.sh"
# First add module with custom directory to list
inst_mod_list_upsert "azerothcore/mod-transmog:custom-dir" "master" "abc123"
# Create fake module directory structure
mkdir -p "$TEST_DIR/modules/custom-dir/.git"
echo "ref: refs/heads/master" > "$TEST_DIR/modules/custom-dir/.git/HEAD"
# Mock git commands in the fake module directory
cat > "$TEST_DIR/modules/custom-dir/.git/config" << 'EOF'
[core]
repositoryformatversion = 0
filemode = true
bare = false
[remote "origin"]
url = https://github.com/azerothcore/mod-transmog
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master
EOF
# Test update with custom directory should work
# Note: This would require more complex mocking for full integration test
# For now, just test the parsing recognizes the custom directory
run inst_parse_module_spec "azerothcore/mod-transmog:custom-dir"
[ "$status" -eq 0 ]
IFS=' ' read -r repo_ref owner name branch commit url dirname <<< "$output"
[ "$dirname" = "custom-dir" ]
}
@test "URL formats should be properly normalized" {
cd "$TEST_DIR"
source "$TEST_DIR/apps/installer/includes/includes.sh"
# Test various URL formats produce same owner/name
run inst_extract_owner_name "https://github.com/azerothcore/mod-transmog"
local url_format="$output"
run inst_extract_owner_name "https://github.com/azerothcore/mod-transmog.git"
local url_git_format="$output"
run inst_extract_owner_name "git@github.com:azerothcore/mod-transmog.git"
local ssh_format="$output"
run inst_extract_owner_name "azerothcore/mod-transmog"
local owner_name_format="$output"
run inst_extract_owner_name "mod-transmog"
local simple_format="$output"
# All should normalize to the same owner/name
[ "$url_format" = "azerothcore/mod-transmog" ]
[ "$url_git_format" = "azerothcore/mod-transmog" ]
[ "$ssh_format" = "azerothcore/mod-transmog" ]
[ "$owner_name_format" = "azerothcore/mod-transmog" ]
[ "$simple_format" = "azerothcore/mod-transmog" ]
}

13
conf/dist/config.sh vendored
View File

@ -149,4 +149,17 @@ export CPUPROFILESIGNAL=${CPUPROFILESIGNAL:-12}
# Other values for HEAPCHECK: minimal, normal (equivalent to "1"), strict, draconian
#export HEAPCHECK=${HEAPCHECK:-normal}
##############################################
#
# MODULES LIST FILE (for installer `module` commands)
#
# Path to the file where the installer records installed modules
# with their branch and commit. You can override this path by
# setting the MODULES_LIST_FILE inside your config.sh or as an environment variable.
# By default it points inside the repository conf folder.
# Format of each line:
# <module-name> <branch> <commit>
# Lines starting with '#' and empty lines are ignored.
export MODULES_LIST_FILE=${MODULES_LIST_FILE:-"$AC_PATH_ROOT/conf/modules.list"}

View File

@ -94,11 +94,11 @@ function Joiner:add_repo() (
basedir="${4:-""}"
[[ -z $url ]] && hasReq=false || hasReq=true
Joiner:_help $hasReq "$1" "Syntax: joiner.sh add-repo [-d] [-e] url name branch [basedir]"
Joiner:_help "$hasReq" "$1" "Syntax: joiner.sh add-repo [-d] [-e] url name branch [basedir]"
# retrieving info from url if not set
if [[ -z $name ]]; then
basename=$(basename $url)
basename=$(basename "$url")
name=${basename%%.*}
if [[ -z "$basedir" ]]; then
@ -115,10 +115,12 @@ function Joiner:add_repo() (
if [ -e "$path/.git/" ]; then
# if exists , update
git --git-dir="$path/.git/" rev-parse && git --git-dir="$path/.git/" pull origin $branch | grep 'Already up-to-date.' && changed="no" || true
echo "Updating $name on branch $branch..."
git --git-dir="$path/.git/" --work-tree="$path" rev-parse && git --git-dir="$path/.git/" --work-tree="$path" pull origin "$branch" | grep 'Already up-to-date.' && changed="no" || true
else
# otherwise clone
git clone $url -c advice.detachedHead=0 -b $branch "$path"
echo "Cloning $name on branch $branch..."
git clone "$url" -c advice.detachedHead=0 -b "$branch" "$path"
fi
if [ "$?" -ne "0" ]; then
@ -140,16 +142,16 @@ function Joiner:add_git_submodule() (
basedir=${4:-""}
[[ -z $url ]] && hasReq=false || hasReq=true
Joiner:_help $hasReq "$1" "Syntax: joiner.sh add-git-submodule [-d] [-e] url name branch [basedir]"
Joiner:_help "$hasReq" "$1" "Syntax: joiner.sh add-git-submodule [-d] [-e] url name branch [basedir]"
# retrieving info from url if not set
if [[ -z $name ]]; then
basename=$(basename $url)
basename=$(basename "$url")
name=${basename%%.*}
if [[ -z $basedir ]]; then
dir=$(dirname $url)
basedir=$(basename $dir)
dir=$(dirname "$url")
basedir=$(basename "$dir")
fi
name="${name,,}" #to lowercase
@ -158,17 +160,17 @@ function Joiner:add_git_submodule() (
path="$J_PATH_MODULES/$basedir/$name"
valid_path=`Joiner:_searchFirstValiPath "$path"`
rel_path=${path#$valid_path}
rel_path=${path#"$valid_path"}
rel_path=${rel_path#/}
if [ -e $path/ ]; then
if [ -e "$path/" ]; then
# if exists , update
(cd "$path" && git pull origin $branch)
(cd "$valid_path" && git submodule update -f --init $rel_path)
(cd "$path" && git pull origin "$branch")
(cd "$valid_path" && git submodule update -f --init "$rel_path")
else
# otherwise add
(cd "$valid_path" && git submodule add -f -b $branch $url $rel_path)
(cd "$valid_path" && git submodule update -f --init $rel_path)
(cd "$valid_path" && git submodule add -f -b "$branch" "$url" "$rel_path")
(cd "$valid_path" && git submodule update -f --init "$rel_path")
fi
if [ "$?" -ne "0" ]; then
@ -324,7 +326,7 @@ function Joiner:self_update() {
if [ ! -z "$J_VER_REQ" ]; then
# if J_VER_REQ is defined then update only if tag is different
_cur_branch=`git --git-dir="$J_PATH/.git/" --work-tree="$J_PATH/" rev-parse --abbrev-ref HEAD`
_cur_ver=`git --git-dir="$J_PATH/.git/" --work-tree="$J_PATH/" name-rev --tags --name-only $_cur_branch`
_cur_ver=`git --git-dir="$J_PATH/.git/" --work-tree="$J_PATH/" name-rev --tags --name-only "$_cur_branch"`
if [ "$_cur_ver" != "$J_VER_REQ" ]; then
git --git-dir="$J_PATH/.git/" --work-tree="$J_PATH/" rev-parse && git --git-dir="$J_PATH/.git/" fetch --tags origin "$_cur_branch" --quiet
git --git-dir="$J_PATH/.git/" --work-tree="$J_PATH/" checkout "tags/$J_VER_REQ" -b "$_cur_branch"
@ -416,8 +418,8 @@ function Joiner:menu() {
while true
do
# run option directly if specified in argument
[ ! -z $1 ] && _switch $@
[ ! -z $1 ] && exit 0
[ ! -z "$1" ] && _switch $@
[ ! -z "$1" ] && exit 0
echo ""
echo "==== JOINER MENU ===="