Release Process
Release early, release often. This is a mantra from free software communities. It's accurate. You want to do your design work in the open, and get critical feedback as early as possible.
1. Stages
- alpha1
- Alpha is for preview purposes only.
- it is intended to be a technology preview aimed at early adopters, testers, translators
- Do not use alpha releases on production systems!
- alpha2
- beta1
- Beta releases are appropriate for use with new development. Within beta releases, the features and compatibility should remain consistent. However beta releases may contain numerous and major unaddressed bugs.
- beta2
- is available for testers, extension developers, and other people who are curious to follow the development of the next release
- rc1
- Release Candidates are believed stable, having passed all internal testing, and with all known fatal runtime bugs fixed. However this release has not been in widespread use long enough to know for sure that all bugs have been identified.
- wait to declare something RC until code stabilizes and critical bugs are fixed
- feature freeze -> testing and debugging ->
- This release is ready for widespread testing, and we’re encouraging everyone to download and test the beta releases. Please run the release through your usual routine, and let us know about any bugs or other issues that you find. Remember that this is a beta release, and is not suitable for use on production systems.
- For translators, this is the first call to update the translations to the current set of strings, which should not change much until final.
- Release Candidates are believed stable, having passed all internal testing, and with all known fatal runtime bugs fixed. However this release has not been in widespread use long enough to know for sure that all bugs have been identified.
- rc2
- final
- also know as Production releases, are stable, having successfully passed through all earlier release stages and believed to be reliable, free of serious bugs, and suitable for use in production systems
- All users are advised to upgrade.
- m1
- Maintenance release
2. Issue tracker
- update issues in bugtracker
- set release date
3. Change Log
Feed Title: Release notes from wackowiki
6.0.38
Minor feature and bug fix release for 6.0.x series (PHP 7.3 - 8.0)
- adds icon for action to WikiEdit toolbar
_t()uses now English as fallback for missing message sets- allows now hyphen-minus in file name and ignores duplicate files
- e.g.
wackowiki-6.0.38.zip
- e.g.
- re-check for anonymous user before storing cache
soft_login()may setsess->user_profile
- adds function
number_format()to localize formatting- uses
intl NumberFormatter
- uses
- adds
admin_replaceaction- allows administrators to do a global string find-and-replace on all wiki pages
- changes format for
backup.logto JSON (see upgrade page for more details)- Remove the backups you've created prior to WackoWiki 6.0.38 from the
file/backup/folder or replace thebackup.logwith a JSON formatted version.
- Remove the backups you've created prior to WackoWiki 6.0.38 from the
- disables
show_permalinkby default - removed deprecated
Pragma: no-cacheandExpires:header - fixes
If-Modified-Sinceconditional requests issues - fixes invalid
ETag - fixes various
Cache-Controlheader issues - adds option to set the
SameSiteattribute - removes redundant
setlocale()call - set HTTP status
410for deleted pages - fix uploading is rejected when
upload_max_sizeis0 - add copy to clipboard button for page tag and file syntax
- allow Admins to change passwords for other users
- uses
:focus-withinpseudo selector for dropdown - adds CSS class
btn-smandbtn-md - miscellaneous minor fixes
- update libs
- SimplePie 1.8.1
- PHPMailer 6.10.0
- freeCap 1.4.6
Full Changelog: 6.0.37...6.0.38
4. Release Notes
- Upgrade from previous versions
- Component compatibility
- Configuration
- Highlights
- Call for Localization
- If you can help with languages other than English and German, it would be appreciated.
- Testing
- Most Annoying Bugs
- While many bugs have been eliminated since the last beta, we still have a few remaining (and new) bugs that testers should be aware of:
- Most Annoying Bugs
- Themes
- Hacks / Patches
5. Build packages
- tarball
- zip
- name the folder within the package according the release
- wacko.r6.0.38
- wacko.r6.2.1
- wacko.r7.0.1
Set back error reporting level in constants.php
const PHP_ERROR_REPORTING = 0;
Set back error SQL mode to server in config.php
'sql_mode' => '0',
Workflow:
- On your local machine (or CI), run:
composer install --no-dev --optimize-autoloader
- Create a clean distribution archive that includes everything users need.
- Upload the resulting .zip / .tar.gz as an asset to a new GitHub Release.
5.1. Create the Distribution Package
Place the script in your project root folder and run it. Ensure you've installed Composer.
Basic usage (default):
./build-release.sh --version=6.3.0 - pulls HEAD of master branch
Build from tag:
./build-release.sh --version=6.3.0 --source=6.3.0 - pulls version with 6.3.0 tag
Build from commit hash:
./build-release.sh --version=6.3.1-dev --source=b0061efba26b793a044f0af4bd8611ce92244760 - pulls version with the provided hash
#!/bin/bash
# ================================================
# WackoWiki Release Builder
# ================================================
# Location: community/devel/build-release.sh
# Usage: Copy to repository root and execute
# ================================================
set -euo pipefail
# ================== CONFIG ==================
VERSION=""
SOURCE="HEAD"
BRANCH=""
MINIMAL=true
ENABLE_LOG=true
AUTO_ACCEPT=false
# Colors (detect BEFORE any output redirection)
if [ -t 1 ] && command -v tput > /dev/null 2>&1; then
RED="$(tput setaf 1 2>/dev/null || echo '')"
GREEN="$(tput setaf 2 2>/dev/null || echo '')"
YELLOW="$(tput setaf 3 2>/dev/null || echo '')"
BLUE="$(tput setaf 4 2>/dev/null || echo '')"
CYAN="$(tput setaf 6 2>/dev/null || echo '')"
BOLD="$(tput bold 2>/dev/null || echo '')"
NC="$(tput sgr0 2>/dev/null || echo '')"
else
RED='' GREEN='' YELLOW='' BLUE='' CYAN='' BOLD='' NC=''
fi
# Global variables
LOG_FILE=""
REPO_ROOT=""
# ================== LOGGING FUNCTIONS ==================
log_info() {
echo -e "${BLUE}ℹ${NC} $*"
}
log_success() {
echo -e "${GREEN}✓${NC} $*"
}
log_warn() {
echo -e "${YELLOW}⚠${NC} $*" >&2
}
log_error() {
echo -e "${RED}✗${NC} $*" >&2
}
log_step() {
echo -e "${CYAN}→${NC} $*"
}
# Function to strip ANSI escape codes (for clean log file)
strip_ansi() {
sed -r 's/\x1b\[[0-9;]*([A-Za-z]|\(B)//g; s/\x1b[()][AB012]//g'
}
# ================== SCRIPT LOCATION CHECK ==================
# Find the actual repository root
find_repo_root() {
local dir
dir="$(pwd)"
local max_depth=10
local depth=0
while [ "$depth" -lt "$max_depth" ]; do
if [ -f "$dir/composer.json" ] && [ -d "$dir/.git" ]; then
# Verify it's a WackoWiki repository
if grep -q '"name".*[wW]acko[Ww]iki' "$dir/composer.json" 2>/dev/null; then
echo "$dir"
return 0
fi
fi
# Check if we've reached filesystem root
if [ "$dir" = "/" ] || [ -z "$dir" ]; then
break
fi
dir="$(dirname "$dir")"
((depth++))
done
return 1
}
# ================== GIT STATUS CHECK ==================
check_git_status() {
local target_dir="$1"
# Validate input
if [ -z "$target_dir" ]; then
log_warn "No directory specified for git status check"
return 0
fi
if [ ! -d "$target_dir" ]; then
log_warn "Directory '$target_dir' does not exist, skipping git status check"
return 0
fi
if ! cd "$target_dir"; then
log_warn "Cannot change to directory '$target_dir', skipping git status check"
return 0
fi
# Check if git repository
if ! git rev-parse --git-dir > /dev/null 2>&1; then
log_warn "Not a git repository, skipping sync check"
return 0
fi
# Check for remote
local remote_url
remote_url=$(git remote get-url origin 2>/dev/null || echo "")
if [ -z "$remote_url" ]; then
log_warn "No remote configured, skipping sync check"
return 0
fi
# Identify host (GitHub or Codeberg)
local remote_host="origin"
if echo "$remote_url" | grep -q "github.com"; then
remote_host="GitHub"
elif echo "$remote_url" | grep -q "codeberg.org"; then
remote_host="Codeberg"
fi
log_info "Checking sync with $remote_host..."
# Fetch latest from remote
log_step "Fetching latest from remote..."
if ! git fetch origin 2>&1; then
log_warn "Failed to fetch from remote, continuing anyway..."
return 0
fi
# Get current HEAD commit and remote HEAD commit
local local_head remote_head
local_head=$(git rev-parse HEAD)
remote_head=$(git rev-parse FETCH_HEAD 2>/dev/null || echo "$local_head")
if [ "$local_head" != "$remote_head" ]; then
log_warn "Your local repository is NOT up-to-date with $remote_host"
log_warn "Local HEAD: ${local_head:0:8}"
log_warn "Remote HEAD: ${remote_head:0:8}"
echo ""
if [ "$AUTO_ACCEPT" = false ]; then
read -p "Do you want to pull the latest changes before creating the release? [y/N] " -n 1 -r
echo ""
if [[ $REPLY =~ ^[Yy]$ ]]; then
log_step "Pulling latest changes..."
if git pull --ff-only; then
log_success "Pull completed"
else
log_error "Pull failed. Please resolve conflicts manually."
exit 1
fi
else
log_warn "Proceeding without pulling latest changes..."
fi
else
log_warn "Auto-accept enabled, proceeding without pulling..."
fi
else
log_success "Repository is up-to-date with $remote_host"
fi
echo ""
}
# ================== INTERACTIVE INPUT ==================
prompt_version() {
echo ""
echo "=============================================="
echo -e "${BOLD}WackoWiki Release Builder${NC}"
echo "=============================================="
echo ""
echo "No VERSION specified."
echo ""
echo "You can specify a version using:"
echo " • Interactive input (this prompt)"
echo " • Command line: ${CYAN}./build-release.sh --version=6.2.2${NC}"
echo ""
# Get current branch/tag info for suggestions
local current_branch latest_tag
current_branch=$(git symbolic-ref --short HEAD 2>/dev/null || echo "")
latest_tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
if [ -n "$latest_tag" ]; then
echo -e "Latest tag: ${GREEN}$latest_tag${NC}"
fi
if [ -n "$current_branch" ]; then
echo -e "Current branch: ${YELLOW}$current_branch${NC}"
fi
echo ""
# Special case: if on master/main and SOURCE is HEAD
if [ "$SOURCE" = "HEAD" ] && { [ "$current_branch" = "master" ] || [ "$current_branch" = "main" ]; } && [ -z "$BRANCH" ]; then
echo "Since you're on ${BOLD}$current_branch${NC} (HEAD), you have two options:"
echo ""
echo " 1) Build a MASTER snapshot (unstable, for testing)"
echo " → Will be named: ${CYAN}wackowiki-MASTER.tar.gz${NC}"
echo ""
echo " 2) Specify a version number (for stable releases)"
echo " → Will be named: ${CYAN}wackowiki-X.Y.Z.tar.gz${NC}"
echo ""
read -p "Build MASTER snapshot or enter version? [M/v]: " -n 1 -r
echo ""
if [[ $REPLY =~ ^[Mm]$ ]] || [ -z "$REPLY" ]; then
VERSION="MASTER"
log_info "Building MASTER snapshot..."
else
prompt_version_number
fi
else
prompt_version_number
fi
}
prompt_version_number() {
echo ""
read -p "Enter version number (e.g., 6.2.2): " VERSION
VERSION=$(echo "$VERSION" | tr -d '[:space:]')
if [ -z "$VERSION" ]; then
log_error "Version cannot be empty"
prompt_version_number
fi
# Validate version format (basic check)
if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+(\.[0-9]+)?$ ]]; then
log_warn "Version '$VERSION' doesn't look like a standard semver (X.Y or X.Y.Z)"
read -p "Use this version anyway? [y/N]: " -n 1 -r
echo ""
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
prompt_version_number
fi
fi
}
# ================== SOURCE/BRANCH RESOLUTION ==================
resolve_source() {
# If BRANCH specified, use that; otherwise use SOURCE
if [ -n "$BRANCH" ]; then
# Verify branch exists
if ! git rev-parse --verify "$BRANCH" > /dev/null 2>&1; then
log_error "Branch '$BRANCH' does not exist"
log_info "Available branches:"
git branch --format=' %(refname:short)'
exit 1
fi
echo "$BRANCH"
elif [ "$SOURCE" = "HEAD" ]; then
# Use current HEAD
git rev-parse HEAD
else
# SOURCE could be a tag or commit hash
if git rev-parse --verify "$SOURCE" > /dev/null 2>&1; then
echo "$SOURCE"
else
log_error "Source '$SOURCE' not found (not a valid tag, branch, or commit)"
exit 1
fi
fi
}
# ================== PARSE ARGUMENTS ==================
parse_arguments() {
while [[ $# -gt 0 ]]; do
case $1 in
--version=*)
VERSION="${1#*=}"
;;
--source=*)
SOURCE="${1#*=}"
;;
--branch=*)
BRANCH="${1#*=}"
;;
--minimal)
MINIMAL=true
;;
--full)
MINIMAL=false
;;
--no-log)
ENABLE_LOG=false
;;
-y|--yes|--auto-accept)
AUTO_ACCEPT=true
;;
--help|-h)
show_help
exit 0
;;
*)
echo "Unknown option: $1"
echo "Use --help for usage information"
exit 1
;;
esac
shift
done
}
show_help() {
cat << 'EOF'
===============================================
WackoWiki Release Builder
===============================================
Usage: ./build-release.sh [OPTIONS]
OPTIONS:
--version=X.Y.Z Version number for the release
(required unless building MASTER snapshot)
--source=REF Git reference to build from
Can be: HEAD, tag name, commit hash
Default: HEAD (current commit)
--branch=NAME Build from specific branch
(alternative to --source)
--minimal Build minimal package (src/ + vendor/)
This is the default
--full Build full package including docs
(src/ + vendor/ + README, LICENSE, etc.)
--no-log Disable logging to build.log
-y, --yes Auto-accept all prompts (for CI/CD)
--help, -h Show this help message
EXAMPLES:
# Interactive mode (will prompt for version)
./build-release.sh
# Build specific version
./build-release.sh --version=6.2.2
# Build master snapshot
./build-release.sh --version=MASTER
# Build from specific tag
./build-release.sh --source=v6.2.2
# Build from specific branch
./build-release.sh --branch=6.3 --version=6.3.0
# Build full package
./build-release.sh --version=6.2.2 --full
# Non-interactive/CI mode
./build-release.sh --version=6.2.2 --yes
NOTES:
- Script must be executed from repository root
- composer.json must be present in root directory
- Git remote should be configured (GitHub or Codeberg)
- Script will check if local repo is up-to-date with remote
For more information, see: community/devel/build-release.sh
===============================================
EOF
}
# ================== MAIN ==================
main() {
# Parse command line arguments first
parse_arguments "$@"
# Find repository root
log_step "Locating WackoWiki repository..."
local found_repo
found_repo=$(find_repo_root)
if [ -z "$found_repo" ]; then
log_error "This does not appear to be a WackoWiki repository."
echo ""
log_info "Please run this script from the WackoWiki repository root."
log_info "The root directory must contain:"
echo " • composer.json (with \"name\" containing \"wackowiki\")"
echo " • .git directory"
echo ""
log_info "Expected location: ~/path/to/wackowiki/"
echo ""
log_info "You are currently in: $(pwd)"
echo ""
log_info "Tip: If you copied build-release.sh to community/devel/, "
log_info " copy it to the repository root and run it from there."
exit 1
fi
# Validate the found path
if [ ! -d "$found_repo" ]; then
log_error "Found repository path is invalid: $found_repo"
exit 1
fi
# Change to repository root
if ! cd "$found_repo"; then
log_error "Cannot change to repository directory: $found_repo"
exit 1
fi
REPO_ROOT="$(pwd)"
log_success "WackoWiki repository confirmed: $REPO_ROOT"
echo ""
# Check git sync status
check_git_status "$REPO_ROOT"
# Prompt for version if not provided
if [ -z "$VERSION" ]; then
prompt_version
fi
# Resolve actual source to build from
local resolved_source
resolved_source=$(resolve_source)
log_info "Building from source: $resolved_source"
# Determine release name and directories
local RELEASE_NAME="wackowiki-$VERSION"
local BUILD_DIR="$REPO_ROOT/build"
local TEMP_DIR="$BUILD_DIR/tmp"
local FINAL_DIR="$BUILD_DIR/$RELEASE_NAME"
# Create directories BEFORE setting up logging
log_step "Preparing build directories..."
rm -rf "$BUILD_DIR"
mkdir -p "$TEMP_DIR" "$FINAL_DIR"
# Setup logging with absolute path (must be after mkdir)
LOG_FILE="$BUILD_DIR/build.log"
if [ "$ENABLE_LOG" = true ]; then
# Create the log file first, then setup redirection
# Strip ANSI codes when writing to log, but keep colors on terminal
exec > >(tee >(strip_ansi >> "$LOG_FILE")) 2>&1
fi
echo "=== Building WackoWiki $VERSION from $resolved_source ==="
echo ""
echo "=================================================="
echo -e "${BOLD}Building WackoWiki $VERSION${NC}"
echo "=================================================="
echo "Source : $resolved_source"
echo "Mode : $([ "$MINIMAL" = true ] && echo "Minimal (src/ + vendor/)" || echo "Full (includes docs)")"
echo "=================================================="
echo ""
# 1. Export clean source from Git
log_step "Exporting source from Git..."
# Use --worktree-attributes to include files that match .gitignore patterns
git archive --format=tar --worktree-attributes "$resolved_source" | tar -x -C "$TEMP_DIR"
log_success "Source exported"
# 2. Prepare distribution structure
log_step "Preparing distribution structure..."
# Copy src/ to FINAL_DIR - this ensures proper structure: FINAL_DIR/src/...
cp -a "$TEMP_DIR/src/" "$FINAL_DIR/"
log_success "Files copied to $FINAL_DIR"
if [ "$MINIMAL" = true ]; then
log_info "Minimal mode: src/ + vendor/ only"
else
log_info "Full mode: src/ + vendor/ + documentation files"
for file in "composer.json" "composer.lock"; do
if [ -f "$TEMP_DIR/$file" ]; then
cp "$TEMP_DIR/$file" "$FINAL_DIR/"
log_success "Copied $file"
fi
done
for file in "README.md" "LICENSE" "INSTALL.md" "CHANGELOG.md"; do
if [ -f "$TEMP_DIR/$file" ]; then
cp "$TEMP_DIR/$file" "$FINAL_DIR/"
log_success "Copied $file"
fi
done
fi
# 3. Install Composer dependencies
log_step "Installing Composer dependencies..."
cd "$FINAL_DIR" || exit 1
# Restore old script's relative path handling for composer files
if [ ! -f "composer.json" ]; then
log_warn "composer.json not found in archive, copying from project root..."
cp -v "$REPO_ROOT/composer.json" . 2>/dev/null || {
log_error "Failed to find composer.json"
exit 1
}
cp -v "$REPO_ROOT/composer.lock" . 2>/dev/null || true
fi
# Also copy composer.lock if it exists but not in archive
if [ -f "composer.json" ] && [ ! -f "composer.lock" ]; then
if [ -f "$REPO_ROOT/composer.lock" ]; then
cp -v "$REPO_ROOT/composer.lock" . 2>/dev/null || true
fi
fi
# Detect composer command
local COMPOSER_CMD=""
if command -v composer >/dev/null 2>&1; then
COMPOSER_CMD="composer"
elif [ -f "$REPO_ROOT/composer.phar" ]; then
COMPOSER_CMD="php $REPO_ROOT/composer.phar"
elif [ -f "$(dirname "$0")/composer.phar" ]; then
COMPOSER_CMD="php $(dirname "$0")/composer.phar"
else
log_warn "Composer not found."
log_info "Install composer with: curl -sS https://getcomposer.org/installer | php"
log_info "Or: apt install composer (Debian/Ubuntu)"
log_info "Or: yum install composer (RHEL/CentOS)"
fi
local COMPOSER_PACKAGES="(Composer not available)"
if [ -n "$COMPOSER_CMD" ] && [ -f "composer.json" ]; then
echo ""
# Check for required PHP extensions
local missing_exts=""
for ext in "mbstring" "json" "phar"; do
if ! php -m 2>/dev/null | grep -qi "^$ext$"; then
missing_exts="$missing_exts $ext"
fi
done
if [ -n "$missing_exts" ]; then
log_warn "Missing PHP extensions:$missing_exts"
fi
# Run composer install - vendor will be created at src/vendor per composer.json config
if $COMPOSER_CMD install --no-dev --optimize-autoloader --no-interaction --prefer-dist --verbose 2>&1; then
log_success "Composer install completed"
# Get vendor size (src/vendor as per composer.json)
local vendor_size
vendor_size=$(du -sh src/vendor/ 2>/dev/null | cut -f1 || echo 'N/A')
log_info "Vendor size: $vendor_size"
# Capture package list
echo ""
log_step "Capturing Composer package list..."
COMPOSER_PACKAGES=$($COMPOSER_CMD show 2>/dev/null | awk '{print $1, $2}' | grep -v -i "deprecated" || echo "(List unavailable)")
if [ -z "$COMPOSER_PACKAGES" ] || [ "$COMPOSER_PACKAGES" = "(List unavailable)" ]; then
if [ -d "src/vendor" ]; then
COMPOSER_PACKAGES="(Packages installed but list unavailable)"
else
COMPOSER_PACKAGES="(No packages installed)"
fi
fi
else
log_warn "Composer install had issues, continuing anyway..."
COMPOSER_PACKAGES="(Composer install failed)"
fi
# Cleanup composer files in minimal mode
if [ "$MINIMAL" = true ]; then
rm -f composer.json composer.lock
log_info "Minimal mode: Removed composer.json and composer.lock"
fi
elif [ ! -f "composer.json" ]; then
log_error "composer.json is missing!"
exit 1
fi
cd "$BUILD_DIR" || exit 1
# 4. Create archives
echo ""
log_step "Creating release archives..."
# Create zip
if command -v zip >/dev/null 2>&1; then
zip -r "${RELEASE_NAME}.zip" "$RELEASE_NAME"
log_success "Created ${RELEASE_NAME}.zip"
else
log_warn "zip command not found, skipping .zip creation"
fi
# Create tar.gz
tar -czf "${RELEASE_NAME}.tar.gz" "$RELEASE_NAME"
log_success "Created ${RELEASE_NAME}.tar.gz"
# 5. Final cleanup
log_step "Cleaning up temporary files..."
rm -rf "$TEMP_DIR"
log_success "Temporary folder removed"
# ================== SUMMARY ==================
echo ""
echo "=================================================="
echo -e "${GREEN}${BOLD}✅ Release build completed successfully!${NC}"
echo "=================================================="
echo ""
echo "Version : $VERSION"
echo "Source : $resolved_source"
echo "Mode : $([ "$MINIMAL" = true ] && echo "Minimal" || echo "Full")"
echo ""
echo "Composer packages:"
echo "$COMPOSER_PACKAGES" | while read -r line; do
echo " $line"
done
echo ""
echo "Files created:"
if [ -f "${RELEASE_NAME}.zip" ]; then
ls -lh "${RELEASE_NAME}.zip"
fi
ls -lh "${RELEASE_NAME}.tar.gz"
echo ""
echo "=================================================="
if [ "$ENABLE_LOG" = true ]; then
echo "Log file: $LOG_FILE"
fi
echo ""
log_info "Release files are in: $(pwd)/"
echo ""
}
# Run main with all arguments
main "$@"
6. Repository
- tag the release
- branch final release
7. Update Files
- Latest development version
- Latest released testing version
- Latest released stable version
7.1. Update file references
wackowiki-6.2.1.zip (2.9 MiB)
Development version
master.tar.gz (see Repository)
Beta version
wackowiki-6.2.2-beta5.zip
8. Release notices and announcements
- SF.net file release system's e-mail notices
- Announcement -mailing list
- SF.net Project news
- Draft the new release on GitHub
- Web pages
- Feed