mirror of
https://github.com/uprightbass360/AzerothCore-RealmMaster.git
synced 2026-01-13 00:58:34 +00:00
537 lines
16 KiB
Bash
Executable File
537 lines
16 KiB
Bash
Executable File
#!/bin/bash
|
|
# Dynamic changelog generator for AzerothCore source repositories and modules
|
|
# Uses existing project configuration to automatically detect and track changes
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_ROOT="$SCRIPT_DIR"
|
|
cd "$PROJECT_ROOT"
|
|
|
|
# Source common library for standardized logging
|
|
if ! source "$SCRIPT_DIR/scripts/bash/lib/common.sh" 2>/dev/null; then
|
|
echo "❌ FATAL: Cannot load $SCRIPT_DIR/scripts/bash/lib/common.sh" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Load environment configuration (available on deployed servers)
|
|
if [ -f ".env" ]; then
|
|
set -a
|
|
source .env
|
|
set +a
|
|
fi
|
|
|
|
# Default configuration
|
|
LOCAL_STORAGE_ROOT="${STORAGE_PATH_LOCAL:-./local-storage}"
|
|
OUTPUT_DIR="${CHANGELOG_OUTPUT_DIR:-./changelogs}"
|
|
DAYS_BACK="${CHANGELOG_DAYS_BACK:-7}"
|
|
FORMAT="${CHANGELOG_FORMAT:-markdown}"
|
|
|
|
# Specialized logging with timestamp for changelog context
|
|
log() { info "[$(date '+%H:%M:%S')] $*"; }
|
|
success() { ok "$*"; }
|
|
# warn() function already provided by lib/common.sh
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
Usage: $0 [options]
|
|
|
|
Generates changelog from all source repositories and modules configured in the project.
|
|
|
|
Options:
|
|
-d, --days DAYS Number of days to look back (default: $DAYS_BACK)
|
|
-o, --output DIR Output directory for file (default: console output)
|
|
-f, --format FORMAT Output format: markdown, json, text, summary (default: $FORMAT)
|
|
--since DATE Get changes since specific date (YYYY-MM-DD)
|
|
--include-modules Include module changes (default: auto-detect)
|
|
--exclude-modules Exclude module changes
|
|
--main-only Only main source repository
|
|
--save Save to file in output directory
|
|
-v, --verbose Verbose output
|
|
-h, --help Show this help
|
|
|
|
Examples:
|
|
$0 # Console output with auto build detection
|
|
$0 --days 14 # Last 2 weeks to console
|
|
$0 --since 2024-11-01 # Since specific date to console
|
|
$0 --save # Save to file in ./changelogs/
|
|
$0 --save -o /tmp # Save to file in /tmp/
|
|
$0 --format json # JSON output to console
|
|
$0 --format summary # Deploy summary (commit counts)
|
|
$0 --main-only # Only core changes
|
|
|
|
The script automatically detects:
|
|
- Source repository variant (standard/playerbots)
|
|
- Enabled modules from project configuration
|
|
- Repository URLs and branches from environment
|
|
- Last build time from Docker image labels
|
|
EOF
|
|
}
|
|
|
|
# Parse arguments
|
|
INCLUDE_MODULES="auto"
|
|
MAIN_ONLY=false
|
|
VERBOSE=false
|
|
SINCE_DATE=""
|
|
SAVE_TO_FILE=false
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
-d|--days)
|
|
DAYS_BACK="$2"; shift 2;;
|
|
-o|--output)
|
|
OUTPUT_DIR="$2"; shift 2;;
|
|
-f|--format)
|
|
FORMAT="$2"; shift 2;;
|
|
--since)
|
|
SINCE_DATE="$2"; shift 2;;
|
|
--include-modules)
|
|
INCLUDE_MODULES=true; shift;;
|
|
--exclude-modules)
|
|
INCLUDE_MODULES=false; shift;;
|
|
--main-only)
|
|
MAIN_ONLY=true; shift;;
|
|
--save)
|
|
SAVE_TO_FILE=true; shift;;
|
|
-v|--verbose)
|
|
VERBOSE=true; shift;;
|
|
-h|--help)
|
|
usage; exit 0;;
|
|
*)
|
|
echo "Unknown option: $1" >&2; exit 1;;
|
|
esac
|
|
done
|
|
|
|
# Get last build time from container metadata
|
|
get_last_build_time() {
|
|
local containers=("ac-worldserver" "ac-authserver")
|
|
local images=("azerothcore-stack:worldserver-playerbots" "azerothcore-stack:authserver-playerbots")
|
|
local latest_date=""
|
|
|
|
# Try to get build timestamp from containers and images
|
|
local sources=()
|
|
for container in "${containers[@]}"; do
|
|
sources+=("container:$container")
|
|
done
|
|
for image in "${images[@]}"; do
|
|
sources+=("image:$image")
|
|
done
|
|
|
|
for source in "${sources[@]}"; do
|
|
local type="${source%%:*}"
|
|
local name="${source#*:}"
|
|
local build_date=""
|
|
|
|
# Try build.source_date first, then build.timestamp as fallback
|
|
build_date=$(docker inspect "$name" --format='{{index .Config.Labels "build.source_date"}}' 2>/dev/null || echo "")
|
|
|
|
if [[ -z "$build_date" || "$build_date" == "unknown" ]]; then
|
|
build_date=$(docker inspect "$name" --format='{{index .Config.Labels "build.timestamp"}}' 2>/dev/null || echo "")
|
|
fi
|
|
|
|
if [[ -n "$build_date" && "$build_date" != "unknown" ]]; then
|
|
# Convert ISO date to YYYY-MM-DD format
|
|
build_date=$(echo "$build_date" | cut -d'T' -f1)
|
|
|
|
# Keep the latest date
|
|
if [[ -z "$latest_date" ]] || [[ "$build_date" > "$latest_date" ]]; then
|
|
latest_date="$build_date"
|
|
fi
|
|
fi
|
|
done
|
|
|
|
echo "$latest_date"
|
|
}
|
|
|
|
# Determine date range
|
|
if [[ -n "$SINCE_DATE" ]]; then
|
|
SINCE_OPTION="--since=$SINCE_DATE"
|
|
DATE_DESC="since $SINCE_DATE"
|
|
else
|
|
# Try to use last build time as default
|
|
LAST_BUILD_DATE=$(get_last_build_time)
|
|
|
|
if [[ -n "$LAST_BUILD_DATE" ]]; then
|
|
SINCE_OPTION="--since=$LAST_BUILD_DATE"
|
|
DATE_DESC="since last build ($LAST_BUILD_DATE)"
|
|
$VERBOSE && log "Using last build date: $LAST_BUILD_DATE"
|
|
else
|
|
SINCE_OPTION="--since=$(date -d "$DAYS_BACK days ago" +%Y-%m-%d)"
|
|
DATE_DESC="last $DAYS_BACK days (no build metadata found)"
|
|
$VERBOSE && warn "No build metadata found in containers, falling back to $DAYS_BACK days"
|
|
fi
|
|
fi
|
|
|
|
# Auto-detect source variant and configuration
|
|
detect_source_config() {
|
|
local variant="core"
|
|
|
|
# Check environment variables for playerbots mode
|
|
if [[ "${MODULE_PLAYERBOTS:-0}" == "1" ]] || [[ "${PLAYERBOT_ENABLED:-0}" == "1" ]] || [[ "${STACK_SOURCE_VARIANT:-}" == "playerbots" ]]; then
|
|
variant="playerbots"
|
|
fi
|
|
|
|
# Also check which source directory actually exists (resolve relative paths)
|
|
local playerbots_path="$LOCAL_STORAGE_ROOT/source/azerothcore-playerbots"
|
|
local standard_path="$LOCAL_STORAGE_ROOT/source/azerothcore"
|
|
|
|
# Convert to absolute paths if needed
|
|
if [[ "$playerbots_path" != /* ]]; then
|
|
if [[ -d "$PROJECT_ROOT/$playerbots_path" ]]; then
|
|
playerbots_path="$(realpath "$PROJECT_ROOT/$playerbots_path")"
|
|
else
|
|
playerbots_path="$PROJECT_ROOT/$playerbots_path"
|
|
fi
|
|
fi
|
|
if [[ "$standard_path" != /* ]]; then
|
|
if [[ -d "$PROJECT_ROOT/$standard_path" ]]; then
|
|
standard_path="$(realpath "$PROJECT_ROOT/$standard_path")"
|
|
else
|
|
standard_path="$PROJECT_ROOT/$standard_path"
|
|
fi
|
|
fi
|
|
|
|
$VERBOSE && log "Checking absolute paths: playerbots=$playerbots_path, standard=$standard_path" >&2
|
|
$VERBOSE && log "Playerbots exists: $([[ -d "$playerbots_path/.git" ]] && echo "yes" || echo "no")" >&2
|
|
$VERBOSE && log "Standard exists: $([[ -d "$standard_path/.git" ]] && echo "yes" || echo "no")" >&2
|
|
|
|
if [[ "$variant" == "core" && -d "$playerbots_path/.git" && ! -d "$standard_path/.git" ]]; then
|
|
variant="playerbots"
|
|
$VERBOSE && log "Switched to playerbots variant" >&2
|
|
fi
|
|
|
|
# Repository URLs from environment or defaults
|
|
local standard_repo="${ACORE_REPO_STANDARD:-https://github.com/azerothcore/azerothcore-wotlk.git}"
|
|
local standard_branch="${ACORE_BRANCH_STANDARD:-master}"
|
|
local playerbots_repo="${ACORE_REPO_PLAYERBOTS:-https://github.com/mod-playerbots/azerothcore-wotlk.git}"
|
|
local playerbots_branch="${ACORE_BRANCH_PLAYERBOTS:-Playerbot}"
|
|
|
|
if [[ "$variant" == "playerbots" ]]; then
|
|
echo "$playerbots_repo|$playerbots_branch|$LOCAL_STORAGE_ROOT/source/azerothcore-playerbots"
|
|
else
|
|
echo "$standard_repo|$standard_branch|$LOCAL_STORAGE_ROOT/source/azerothcore"
|
|
fi
|
|
}
|
|
|
|
# Get enabled modules from project configuration
|
|
get_enabled_modules() {
|
|
local modules_file="$LOCAL_STORAGE_ROOT/modules/.modules-meta/modules-enabled.txt"
|
|
local modules_state="$LOCAL_STORAGE_ROOT/modules/.modules_state"
|
|
|
|
if [[ -f "$modules_file" ]]; then
|
|
cat "$modules_file" 2>/dev/null || true
|
|
elif [[ -f "$modules_state" ]]; then
|
|
# Parse modules state format: MODULE_NAME=1|MODULE_NAME2=0|...
|
|
grep -o 'MODULE_[^=]*=1' "$modules_state" 2>/dev/null | sed 's/MODULE_//; s/=1//' | tr '[:upper:]' '[:lower:]' | sed 's/_/-/g' || true
|
|
fi
|
|
}
|
|
|
|
# Get module repository info using existing module management
|
|
get_module_repos() {
|
|
local python_helper="$PROJECT_ROOT/scripts/python/modules.py"
|
|
|
|
if [[ -x "$python_helper" ]]; then
|
|
# Use existing module helper if available
|
|
python3 "$python_helper" list --format=repo 2>/dev/null || true
|
|
else
|
|
# Fallback: scan module directories for git repos
|
|
find "$LOCAL_STORAGE_ROOT/modules" -name ".git" -type d 2>/dev/null | while read -r git_dir; do
|
|
local module_dir="$(dirname "$git_dir")"
|
|
local module_name="$(basename "$module_dir")"
|
|
local repo_url=""
|
|
|
|
if [[ -f "$git_dir/config" ]]; then
|
|
repo_url=$(grep -A1 '\[remote "origin"\]' "$git_dir/config" 2>/dev/null | grep -E '^\s*url\s*=' | sed 's/.*url\s*=\s*//' | tr -d '\r\n' || true)
|
|
fi
|
|
|
|
if [[ -n "$repo_url" && -n "$module_name" ]]; then
|
|
echo "$repo_url|master|$module_dir"
|
|
fi
|
|
done
|
|
fi
|
|
}
|
|
|
|
# Format changelog entry based on output format
|
|
format_changelog() {
|
|
local repo_name="$1"
|
|
local repo_url="$2"
|
|
local commit_count="$3"
|
|
local commits="$4"
|
|
|
|
case "$FORMAT" in
|
|
json)
|
|
cat <<EOF
|
|
{
|
|
"repository": "$repo_name",
|
|
"url": "$repo_url",
|
|
"commit_count": $commit_count,
|
|
"commits": [
|
|
$(echo "$commits" | sed 's/^/ /; s/$/,/; $s/,$//')
|
|
]
|
|
}
|
|
EOF
|
|
;;
|
|
markdown)
|
|
cat <<EOF
|
|
|
|
## $repo_name ($commit_count commits)
|
|
**Repository:** $repo_url
|
|
|
|
$commits
|
|
EOF
|
|
;;
|
|
text)
|
|
cat <<EOF
|
|
|
|
=== $repo_name ===
|
|
Repository: $repo_url
|
|
Commits: $commit_count
|
|
|
|
$commits
|
|
EOF
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Get commits from a repository
|
|
get_repo_commits() {
|
|
local repo_path="$1"
|
|
local repo_name="$(basename "$repo_path")"
|
|
|
|
if [[ ! -d "$repo_path/.git" ]]; then
|
|
$VERBOSE && warn "No git repository found at $repo_path" >&2
|
|
return
|
|
fi
|
|
|
|
# Handle git ownership issues on deployed servers
|
|
local original_dir="$PWD"
|
|
cd "$repo_path"
|
|
|
|
# Ensure git can access this repository
|
|
git config --global --add safe.directory "$repo_path" 2>/dev/null || true
|
|
|
|
# Get remote URL
|
|
local repo_url
|
|
repo_url=$(git remote get-url origin 2>/dev/null || echo "local")
|
|
|
|
# Get commits in the specified timeframe
|
|
local commits
|
|
commits=$(git log --oneline --no-merges $SINCE_OPTION 2>/dev/null | head -50 || echo "")
|
|
|
|
cd "$original_dir"
|
|
|
|
if [[ -z "$commits" ]]; then
|
|
$VERBOSE && log "No commits found in $repo_name for $DATE_DESC" >&2
|
|
return
|
|
fi
|
|
|
|
local commit_count=$(echo "$commits" | wc -l)
|
|
$VERBOSE && log "Found $commit_count commits in $repo_name" >&2
|
|
|
|
# For summary format, just return the count
|
|
if [[ "$FORMAT" == "summary" ]]; then
|
|
echo "$commit_count"
|
|
return
|
|
fi
|
|
|
|
# Format commits based on output format
|
|
local formatted_commits
|
|
case "$FORMAT" in
|
|
json)
|
|
formatted_commits=$(echo "$commits" | sed 's/^\([^ ]*\) \(.*\)$/ {"hash": "\1", "message": "\2"}/')
|
|
;;
|
|
markdown)
|
|
formatted_commits=$(echo "$commits" | sed 's/^\([^ ]*\) \(.*\)$/- **\1**: \2/')
|
|
;;
|
|
text)
|
|
formatted_commits=$(echo "$commits" | sed 's/^/ /')
|
|
;;
|
|
esac
|
|
|
|
format_changelog "$repo_name" "$repo_url" "$commit_count" "$formatted_commits"
|
|
}
|
|
|
|
# Main execution
|
|
main() {
|
|
$VERBOSE && log "Generating changelog for $DATE_DESC"
|
|
|
|
# Determine output destination
|
|
local output_file=""
|
|
local use_stdout=true
|
|
|
|
if [[ "$SAVE_TO_FILE" == "true" ]]; then
|
|
use_stdout=false
|
|
mkdir -p "$OUTPUT_DIR"
|
|
local timestamp=$(date +"%Y%m%d_%H%M%S")
|
|
output_file="$OUTPUT_DIR/changelog_${timestamp}.${FORMAT}"
|
|
fi
|
|
|
|
# Function to write output (either to file or stdout)
|
|
write_output() {
|
|
if [[ "$use_stdout" == "true" ]]; then
|
|
echo "$1"
|
|
else
|
|
echo "$1" >> "$output_file"
|
|
fi
|
|
}
|
|
|
|
# Start output
|
|
case "$FORMAT" in
|
|
json)
|
|
if [[ "$use_stdout" == "false" ]]; then
|
|
echo "{" > "$output_file"
|
|
else
|
|
echo "{"
|
|
fi
|
|
write_output " \"generated\": \"$(date -Iseconds)\","
|
|
write_output " \"period\": \"$DATE_DESC\","
|
|
write_output " \"repositories\": ["
|
|
;;
|
|
markdown)
|
|
write_output "# AzerothCore Changelog"
|
|
write_output ""
|
|
write_output "**Generated:** $(date)"
|
|
write_output "**Period:** $DATE_DESC"
|
|
write_output ""
|
|
;;
|
|
text)
|
|
write_output "AzerothCore Changelog"
|
|
write_output "Generated: $(date)"
|
|
write_output "Period: $DATE_DESC"
|
|
write_output "$(printf '=%.0s' {1..50})"
|
|
;;
|
|
summary)
|
|
# Summary format will be handled after collecting data
|
|
;;
|
|
esac
|
|
|
|
local first_repo=true
|
|
|
|
# Collect all output in a variable first for console display
|
|
local changelog_content=""
|
|
local main_commits=0
|
|
local module_commits=0
|
|
local total_repos=0
|
|
|
|
# Function to collect output
|
|
collect_output() {
|
|
changelog_content+="$1"$'\n'
|
|
}
|
|
|
|
# Main source repository
|
|
if ! $MAIN_ONLY; then
|
|
$VERBOSE && log "Processing main source repository..."
|
|
local source_config
|
|
source_config="$(detect_source_config)"
|
|
$VERBOSE && log "Source config: $source_config" >&2
|
|
IFS='|' read -r repo_url branch repo_path <<< "$source_config"
|
|
|
|
local repo_output
|
|
repo_output=$(get_repo_commits "$repo_path")
|
|
if [[ -n "$repo_output" ]]; then
|
|
if [[ "$FORMAT" == "summary" ]]; then
|
|
main_commits="$repo_output"
|
|
total_repos=$((total_repos + 1))
|
|
else
|
|
if [[ "$FORMAT" == "json" && ! $first_repo ]]; then
|
|
collect_output ","
|
|
fi
|
|
collect_output "$repo_output"
|
|
first_repo=false
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# Module repositories
|
|
if [[ "$INCLUDE_MODULES" != "false" && ! $MAIN_ONLY ]]; then
|
|
# Auto-detect if modules should be included
|
|
if [[ "$INCLUDE_MODULES" == "auto" ]]; then
|
|
local enabled_modules=$(get_enabled_modules)
|
|
if [[ -n "$enabled_modules" ]]; then
|
|
INCLUDE_MODULES=true
|
|
else
|
|
INCLUDE_MODULES=false
|
|
fi
|
|
fi
|
|
|
|
if [[ "$INCLUDE_MODULES" == "true" ]]; then
|
|
$VERBOSE && log "Processing module repositories..."
|
|
|
|
while IFS='|' read -r repo_url branch repo_path; do
|
|
[[ -z "$repo_path" ]] && continue
|
|
|
|
local repo_output
|
|
repo_output=$(get_repo_commits "$repo_path")
|
|
if [[ -n "$repo_output" ]]; then
|
|
if [[ "$FORMAT" == "summary" ]]; then
|
|
module_commits=$((module_commits + repo_output))
|
|
total_repos=$((total_repos + 1))
|
|
else
|
|
if [[ "$FORMAT" == "json" && ! $first_repo ]]; then
|
|
collect_output ","
|
|
fi
|
|
collect_output "$repo_output"
|
|
first_repo=false
|
|
fi
|
|
fi
|
|
done < <(get_module_repos)
|
|
fi
|
|
fi
|
|
|
|
# Handle different output formats
|
|
case "$FORMAT" in
|
|
summary)
|
|
local total_commits=$((main_commits + module_commits))
|
|
if [[ $total_commits -eq 0 ]]; then
|
|
write_output "No changes since last build"
|
|
else
|
|
write_output "Changes since last build: ${total_commits} commits"
|
|
if [[ $main_commits -gt 0 ]]; then
|
|
write_output " Core: ${main_commits} commits"
|
|
fi
|
|
if [[ $module_commits -gt 0 ]]; then
|
|
write_output " Modules: ${module_commits} commits"
|
|
fi
|
|
write_output " Repositories: ${total_repos}"
|
|
fi
|
|
;;
|
|
*)
|
|
# Output collected content (if any)
|
|
if [[ -n "$changelog_content" ]]; then
|
|
write_output "$changelog_content"
|
|
fi
|
|
|
|
# Close output
|
|
case "$FORMAT" in
|
|
json)
|
|
write_output " ]"
|
|
write_output "}"
|
|
;;
|
|
esac
|
|
;;
|
|
esac
|
|
|
|
# Show completion message
|
|
if [[ "$use_stdout" == "false" ]]; then
|
|
success "Changelog generated: $output_file"
|
|
if $VERBOSE; then
|
|
log "File size: $(du -h "$output_file" | cut -f1)"
|
|
fi
|
|
fi
|
|
|
|
# Show summary if verbose
|
|
if $VERBOSE; then
|
|
local repo_count=0
|
|
case "$FORMAT" in
|
|
json)
|
|
repo_count=$(echo "$changelog_content" | grep -c '"repository":' 2>/dev/null || echo "0")
|
|
;;
|
|
*)
|
|
repo_count=$(echo "$changelog_content" | grep -E '^(## |=== )' | wc -l)
|
|
;;
|
|
esac
|
|
log "Repositories processed: $repo_count"
|
|
fi
|
|
}
|
|
|
|
# Run main function
|
|
main "$@" |