Files
skins-template/.gitea/workflows/ci.yml
Arlind 1a1d073bf6 test
2025-06-16 09:55:59 +02:00

915 lines
34 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: CI/CD Pipeline
on:
push:
branches:
- main
paths:
- '.gitea/workflows/*'
- 'Skins/**/*'
workflow_dispatch:
inputs:
force_rebuild:
description: 'Force rebuild all skins'
required: false
default: 'false'
env:
DANSER_PATH: "/app/danser/danser-cli"
DANSER_DIR: "/app/danser"
DANSER_VIDEO_DIR: "/app/danser/videos"
DANSER_SCREENSHOT_DIR: "/app/danser/screenshots"
SKINS_DIR: "${{ github.workspace }}/Skins"
DANSER_SKINS_DIR: "/app/danser/skins"
DEFAULT_SKIN_DIR: "${{ github.workspace }}/src/default-skin"
REPO_SCREENSHOT_DIR: "${{ github.workspace }}/media/gameplay"
REPO_MOD_ICONS_DIR: "${{ github.workspace }}/media/icons"
REPO_RANKING_PANEL_DIR: "${{ github.workspace }}/media/panel"
SETTINGS_JSON_PATH: "/app/danser/settings/default.json"
README_PATH: "${{ github.workspace }}/README.md"
REPLAY_PATH: "${{ github.workspace }}/src/replay.osr"
OSK_PATH: "${{ github.workspace }}/export"
IMAGE_NAME: osc/skins-image
REGISTRY_URL: "https://${{ vars.CONTAINER_REGISTRY }}"
OSU_ID: ${{ vars.OSUID }}
jobs:
generate_everything:
name: Full CI/CD Pipeline
runs-on: ubuntu-latest
container:
image: ${{ vars.CONTAINER_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
options: >-
--gpus all
--privileged
--security-opt seccomp=unconfined
--security-opt apparmor=unconfined
--cap-add=ALL
--env NVIDIA_DRIVER_CAPABILITIES=all
--env NVIDIA_VISIBLE_DEVICES=all
--user 0:0
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
tags: true
token: ${{ secrets.TOKEN }}
- name: Discover all skins
shell: bash
run: |
echo "Discovering all skins in $SKINS_DIR…"
mapfile -t skins < <(
find "$SKINS_DIR" -mindepth 1 -maxdepth 1 -type d \
| sed 's|'"$SKINS_DIR"'/||'
)
{
echo 'ALL_SKINS_DIR<<EOF'
for s in "${skins[@]}"; do
echo "$s"
done
echo 'EOF'
} >> "$GITHUB_ENV"
echo "→ ALL_SKINS_DIR set (newline-delimited list)"
- name: Detect Changed Skin Directories
shell: bash
run: |
echo "[Detect Changed Skin Directories Started]"
readarray -t all_skins <<< "$ALL_SKINS_DIR"
force_rebuild="${{ github.event.inputs.force_rebuild }}"
skins=()
deleted_skins=()
echo "→ Force rebuild flag: $force_rebuild"
if [[ "$force_rebuild" == "true" ]]; then
echo "→ Force rebuild is enabled. Using ALL_SKINS_DIR for full list…"
skins=("${all_skins[@]}")
echo " ✓ Found ${#skins[@]} skin directories (from ALL_SKINS_DIR)"
else
echo "→ Force rebuild is disabled. Finding latest git tag..."
latest_tag=$(git tag --sort=-creatordate | head -n 1 || true)
if [[ -n "$latest_tag" ]]; then
echo "→ Latest tag found: $latest_tag"
echo "→ Finding added/modified skins since $latest_tag…"
mapfile -t skins < <(
git diff --name-only -z --diff-filter=AM "$latest_tag" HEAD \
| while IFS= read -r -d '' file; do
[[ $file == Skins/* ]] && echo "${file#Skins/}" | cut -d/ -f1
done
)
echo " ✓ Found ${#skins[@]} added/modified skins"
echo "→ Finding deleted skins since $latest_tag…"
mapfile -t deleted_skins < <(
git diff --name-only -z --diff-filter=D "$latest_tag" HEAD \
| while IFS= read -r -d '' file; do
[[ $file == Skins/* ]] && echo "${file#Skins/}" | cut -d/ -f1
done
)
if [ "${#deleted_skins[@]}" -gt 0 ]; then
for d in "${deleted_skins[@]}"; do
echo "→ Skin '$d' was deleted"
done
else
echo " ✓ No skins deleted"
fi
else
echo "→ No tag found. Falling back to ALL_SKINS_DIR for full list…"
skins=("${all_skins[@]}")
echo " ✓ Found ${#skins[@]} skin directories (from ALL_SKINS_DIR)"
fi
fi
echo ""
echo "[Cleaning Skin Names]"
uniq_skins=()
for skin in "${skins[@]}"; do
skin="${skin#"${skin%%[![:space:]]*}"}"
skin="${skin%"${skin##*[![:space:]]}"}"
[[ -n "$skin" ]] && uniq_skins+=("$skin")
done
echo " ✓ ${#uniq_skins[@]} valid skin names after cleaning"
echo ""
if [ "${#uniq_skins[@]}" -eq 0 ]; then
echo "→ No added/modified skins detected."
echo "CHANGED_SKINS_FILE=" >> "$GITHUB_ENV"
else
echo "[Writing Changed Skins to File]"
changed_skins_file=$(mktemp)
printf "%s\n" "${uniq_skins[@]}" > "$changed_skins_file"
echo " ✓ Skins written to $changed_skins_file"
echo "CHANGED_SKINS_FILE=$changed_skins_file" >> "$GITHUB_ENV"
fi
echo ""
echo "[Detect Changed Skin Directories Complete — ${#uniq_skins[@]} skins processed, ${#deleted_skins[@]} skins deleted]"
- name: Pull Git LFS objects for changed skins (and core assets)
shell: bash
run: |
if [ -z "${CHANGED_SKINS_FILE:-}" ] || [ ! -s "$CHANGED_SKINS_FILE" ]; then
echo "No skins changed. Skipping git pull lfs."
exit 0
fi
includes="src/**,export/**,media/**"
skin_includes=$(
while IFS= read -r skin; do
esc=$(printf '%s' "$skin" \
| sed -e 's/\[/\\[/g' -e 's/\]/\\]/g')
printf 'Skins/%s/**\n' "$esc"
done < "$CHANGED_SKINS_FILE" \
| paste -sd ','
)
includes="$includes,$skin_includes"
echo "→ Pulling LFS objects for patterns: $includes"
git lfs pull --include="$includes"
- name: Extract Repository path
shell: bash
run: |
echo "Extracting repository path..."
USER_REPOSITORY="${{ github.workspace }}"
USER_REPOSITORY="${USER_REPOSITORY#/workspace/}"
USER_REPOSITORY="${USER_REPOSITORY%/}"
echo "Repository path extracted: $USER_REPOSITORY"
echo "USER_REPOSITORY=$USER_REPOSITORY" >> $GITHUB_ENV
- name: Set XDG_RUNTIME_DIR
shell: bash
run: |
echo "Setting XDG_RUNTIME_DIR..."
mkdir -p /tmp/xdg_runtime_dir
chmod 0700 /tmp/xdg_runtime_dir
echo "XDG_RUNTIME_DIR=/tmp/xdg_runtime_dir" >> "$GITHUB_ENV"
echo "XDG_RUNTIME_DIR set."
- name: Create directories for assets
shell: bash
run: |
echo "Creating base directories for assets..."
mkdir -p "$REPO_SCREENSHOT_DIR" "$REPO_MOD_ICONS_DIR" "$REPO_RANKING_PANEL_DIR" "$OSK_PATH"
# Read the newline-delimited list back into an array
readarray -t skins <<< "$ALL_SKINS_DIR"
for skin in "${skins[@]}"; do
echo " → Creating subdirs for '$skin'…"
mkdir -p \
"$REPO_SCREENSHOT_DIR/$skin" \
"$REPO_MOD_ICONS_DIR/$skin" \
"$REPO_RANKING_PANEL_DIR/$skin" \
"$OSK_PATH/$skin"
done
echo "All asset directories created for ${#skins[@]} skins."
- name: Create New Tag
shell: bash
run: |
echo "Computing new tag..."
latest_tag=$(git describe --tags $(git rev-list --tags --max-count=1) 2>/dev/null || echo "")
if [ -z "$latest_tag" ]; then
new_tag="v1.0.0"
else
IFS='.' read -r major minor patch <<< "${latest_tag#v}"
minor=$((minor + 1))
patch=0
new_tag="v${major}.${minor}.${patch}"
fi
echo "new_tag=$new_tag" >> $GITHUB_ENV
echo "Computed new tag: $new_tag"
- name: Move Skin files to Danser Skins directory
shell: bash
run: |
echo "Moving Skin files to Danser Skins directory..."
mkdir -p "$DANSER_SKINS_DIR"
mv "$SKINS_DIR"/* "$DANSER_SKINS_DIR"
echo "Skin files moved."
- name: Check for Duplicate skin.ini Names
shell: bash
run: |
set -euo pipefail
echo "[Duplicate Skin Name Check Started]"
# Guard: ensure DANSER_SKINS_DIR is set and exists
if [ -z "${DANSER_SKINS_DIR:-}" ] || [ ! -d "${DANSER_SKINS_DIR}" ]; then
echo "DANSER_SKINS_DIR is not set or not a directory. Exiting."
exit 1
fi
sanitize_filename() {
local filename="$1"
echo "$filename" \
| sed -e 's#[\\/:\*\?"<>|]#-#g' -e 's#%#_#g' \
| tr -s ' ' \
| sed 's/^ *//;s/ *$//'
}
declare -A name_counts=()
declare -A name_dirs=()
duplicates=()
shopt -s nullglob
for dir in "${DANSER_SKINS_DIR}"/*; do
[ -d "$dir" ] || continue
dir_base=$(basename "$dir" | tr -d $'\r\n')
name="$dir_base"
ini=$(find "$dir" -maxdepth 1 -iname "skin.ini" | head -n1 || true)
if [ -f "$ini" ]; then
line=$(grep -i '^[[:space:]]*Name:' "$ini" | head -n1 || true)
if [ -n "${line:-}" ]; then
val="${line#*:}"
val=$(echo "$val" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
name=$(sanitize_filename "$val")
fi
fi
name_counts["$name"]=$(( ${name_counts["$name"]:-0} + 1 ))
if [ -z "${name_dirs[$name]:-}" ]; then
name_dirs["$name"]="$dir_base"
else
name_dirs["$name"]+=",${dir_base}"
fi
done
shopt -u nullglob
for nm in "${!name_counts[@]}"; do
if [ "${name_counts[$nm]}" -gt 1 ]; then
duplicates+=("$nm")
fi
done
if [ "${#duplicates[@]}" -gt 0 ]; then
echo "⚠️ Duplicate skin.ini Names found:"
for nm in "${duplicates[@]}"; do
IFS=',' read -r -a dirs <<< "${name_dirs[$nm]}"
echo " • $nm (${name_counts[$nm]} skins):"
for d in "${dirs[@]}"; do
echo " - $d"
done
done
else
echo "✓ No duplicate skin.ini Names detected."
fi
echo "[Duplicate Skin Name Check Completed]"
- name: Generate Danser videos and screenshots
shell: bash
run: |
echo "[Danser Job Started]"
if [ -z "${CHANGED_SKINS_FILE:-}" ] || [ ! -s "$CHANGED_SKINS_FILE" ]; then
echo "No skins changed. Skipping generation."
exit 0
fi
mapfile -t skins < "$CHANGED_SKINS_FILE"
[ "${#skins[@]}" -eq 0 ] && { echo "No skins to process. Exiting."; exit 0; }
SKIN_COUNT=${#skins[@]}
INDEX=1
for skin_path in "${skins[@]}"; do
[ -z "$skin_path" ] && continue
SKIN_DIR="$DANSER_SKINS_DIR/$skin_path"
[ ! -d "$SKIN_DIR" ] && { echo "Skipping missing skin: $skin_path"; continue; }
SKIN_NAME="$skin_path"
OUT_GIF_DIR="$REPO_SCREENSHOT_DIR/$SKIN_NAME"
OUT_PNG_DIR="$REPO_RANKING_PANEL_DIR/$SKIN_NAME"
echo ""
echo "[$INDEX/$SKIN_COUNT] Generating for skin: $SKIN_NAME"
LOGFILE="/tmp/danser_log_$INDEX.txt"
FFMPEG_LOG="/tmp/ffmpeg_log_$INDEX.txt"
echo " → Generating video..."
if ! xvfb-run -a "$DANSER_DIR/danser-cli" \
-replay "$REPLAY_PATH" -record -skip -start=215 -end=230 -noupdatecheck \
-out="$SKIN_NAME" -skin="$SKIN_NAME" >"$LOGFILE" 2>&1; then
echo " ✖ Video failed for $SKIN_NAME"; cat "$LOGFILE"; INDEX=$((INDEX+1)); continue
fi
echo " → Taking screenshot..."
if ! xvfb-run -a "$DANSER_DIR/danser-cli" \
-replay "$REPLAY_PATH" -skip -noupdatecheck -ss 243 \
-out="$SKIN_NAME" -skin="$SKIN_NAME" >>"$LOGFILE" 2>&1; then
echo " ✖ Screenshot failed for $SKIN_NAME"; cat "$LOGFILE"; INDEX=$((INDEX+1)); continue
fi
if [ -f "$DANSER_VIDEO_DIR/$SKIN_NAME.mp4" ]; then
echo " → Converting to GIF..."
if ffmpeg -y -hwaccel cuda -ss 4 -t 10 -i "$DANSER_VIDEO_DIR/$SKIN_NAME.mp4" \
-filter_complex "[0:v] fps=24,scale=720:-1:flags=lanczos,palettegen [p]; [0:v] fps=24,scale=720:-1:flags=lanczos [x]; [x][p] paletteuse" \
-c:v gif "$DANSER_VIDEO_DIR/$SKIN_NAME.gif" >"$FFMPEG_LOG" 2>&1; then
mv "$DANSER_VIDEO_DIR/$SKIN_NAME.gif" "$OUT_GIF_DIR/$SKIN_NAME.gif"
echo " ✓ GIF moved to $OUT_GIF_DIR/"
else
echo " ✖ FFmpeg conversion failed for $SKIN_NAME"; cat "$FFMPEG_LOG"
fi
else
echo " ✖ No MP4 found for $SKIN_NAME"
fi
if [ -f "$DANSER_SCREENSHOT_DIR/$SKIN_NAME.png" ]; then
mv "$DANSER_SCREENSHOT_DIR/$SKIN_NAME.png" "$OUT_PNG_DIR/$SKIN_NAME.png"
echo " ✓ PNG moved to $OUT_PNG_DIR/"
else
echo " ✖ No PNG found for $SKIN_NAME"
fi
INDEX=$((INDEX + 1))
done
echo ""
echo "[Danser Job Finished — processed $SKIN_COUNT skins]"
- name: Rename Generated Assets Based on skin.ini
shell: bash
run: |
echo "[Asset Renaming Job Started]"
if [ -z "${CHANGED_SKINS_FILE:-}" ] || [ ! -s "$CHANGED_SKINS_FILE" ]; then
echo "No skins changed. Skipping asset renaming."
exit 0
fi
mapfile -t skins < "$CHANGED_SKINS_FILE"
[ "${#skins[@]}" -eq 0 ] && { echo "No skins to rename. Exiting."; exit 0; }
SKIN_COUNT=${#skins[@]}
INDEX=1
sanitize_filename() {
echo "$1" \
| sed -e 's#[\\/:\*\?"<>|]#-#g' -e 's#%#_#g' \
| tr -s ' ' \
| sed 's/^ *//;s/ *$//'
}
for skin_path in "${skins[@]}"; do
[ -z "$skin_path" ] && continue
SKIN_DIR_NAME="$skin_path"
SKIN_DIR="$DANSER_SKINS_DIR/$skin_path"
if [ ! -d "$SKIN_DIR" ]; then
echo "Skipping missing skin directory: $SKIN_DIR"
continue
fi
echo "Processing skin $INDEX/$SKIN_COUNT: $SKIN_DIR_NAME"
skin_header="$SKIN_DIR_NAME"
ini_file=$(find "$SKIN_DIR" -maxdepth 1 -iname "skin.ini" | head -n1 || true)
if [ -f "$ini_file" ]; then
name_line=$(grep -i '^[[:space:]]*Name:' "$ini_file" | head -n1 || true)
if [ -n "$name_line" ]; then
val="${name_line#*:}"
val="$(echo "$val" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
sanitized="$(sanitize_filename "$val")"
[ -n "$sanitized" ] && skin_header="$sanitized"
fi
fi
GIF_DIR="$REPO_SCREENSHOT_DIR/$SKIN_DIR_NAME"
PNG_DIR="$REPO_RANKING_PANEL_DIR/$SKIN_DIR_NAME"
if [ -f "$GIF_DIR/$SKIN_DIR_NAME.gif" ] && [ "$SKIN_DIR_NAME" != "$skin_header" ]; then
mv -f "$GIF_DIR/$SKIN_DIR_NAME.gif" \
"$GIF_DIR/$skin_header.gif" || true
echo " ✓ Renamed GIF to $GIF_DIR/$skin_header.gif"
fi
if [ -f "$PNG_DIR/$SKIN_DIR_NAME.png" ] && [ "$SKIN_DIR_NAME" != "$skin_header" ]; then
mv -f "$PNG_DIR/$SKIN_DIR_NAME.png" \
"$PNG_DIR/$skin_header.png" || true
echo " ✓ Renamed PNG to $PNG_DIR/$skin_header.png"
fi
INDEX=$((INDEX + 1))
done
echo ""
echo "[Asset Renaming Complete — processed $SKIN_COUNT skins]"
- name: Generate Mod Icons
shell: bash
run: |
echo "[Mod Icon Generation Job Started]"
if [ -z "${CHANGED_SKINS_FILE:-}" ] || [ ! -s "$CHANGED_SKINS_FILE" ]; then
echo "No skins changed. Skipping mod icon generation."
exit 0
fi
mapfile -t skin_dirs < "$CHANGED_SKINS_FILE"
[ "${#skin_dirs[@]}" -eq 0 ] && { echo "No skins to process. Exiting."; exit 0; }
sanitize_filename() {
echo "$1" \
| sed -e 's#[\\/:\*\?"<>|]#-#g' -e 's#%#_#g' \
| tr -s ' ' \
| sed 's/^ *//;s/ *$//'
}
ICONS_JSON_FILE="${{ github.workspace }}/.gitea/workflows/icons.json"
group1_icons=$(jq -r '.group1 | join(" ")' "$ICONS_JSON_FILE")
group2_icons=$(jq -r '.group2 | join(" ")' "$ICONS_JSON_FILE")
group3_icons=$(jq -r '.group3 | join(" ")' "$ICONS_JSON_FILE")
BLANK_IMAGE="blank.png"
magick -size "160x160" xc:none "$BLANK_IMAGE"
SKIN_COUNT=${#skin_dirs[@]}
INDEX=1
for skin_path in "${skin_dirs[@]}"; do
SKIN_DIR="$DANSER_SKINS_DIR/$skin_path"
[ ! -d "$SKIN_DIR" ] && { echo "Skipping missing skin directory: $SKIN_DIR"; ((INDEX++)); continue; }
skin_header="$skin_path"
ini_file=$(find "$SKIN_DIR" -maxdepth 1 -iname "skin.ini" | head -n1 || true)
if [ -f "$ini_file" ]; then
name_line=$(grep -i '^[[:space:]]*Name:' "$ini_file" | head -n1 || true)
if [ -n "$name_line" ]; then
val="${name_line#*:}"
val="$(echo "$val" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
sanitized="$(sanitize_filename "$val")"
[ -n "$sanitized" ] && skin_header="$sanitized"
fi
fi
echo ""
echo "[$INDEX/$SKIN_COUNT] Skin: $skin_header"
ICON_FOLDER="$SKIN_DIR"
OUTPUT_DIR="$REPO_MOD_ICONS_DIR/$skin_path"
mkdir -p "$OUTPUT_DIR"
OUTPUT="$OUTPUT_DIR/${skin_header}-mod-icons.png"
row_images=()
row_index=1
for group_list in "$group1_icons" "$group2_icons" "$group3_icons"; do
montage_files=()
for icon in $group_list; do
if [ -f "${ICON_FOLDER}/selection-mod-${icon}@2x.png" ]; then
montage_files+=("${ICON_FOLDER}/selection-mod-${icon}@2x.png")
elif [ -f "${DEFAULT_SKIN_DIR}/selection-mod-${icon}@2x.png" ]; then
montage_files+=("${DEFAULT_SKIN_DIR}/selection-mod-${icon}@2x.png")
fi
done
while [ "${#montage_files[@]}" -lt 7 ]; do
montage_files+=("$BLANK_IMAGE")
done
magick montage "${montage_files[@]}" \
-tile "7x1" -geometry "160x160+10+10" -background none \
"row_${row_index}.png"
row_images+=("row_${row_index}.png")
row_index=$((row_index + 1))
done
magick montage "${row_images[@]}" \
-tile "1x${#row_images[@]}" -geometry "+10+10" -background none \
"$OUTPUT"
rm row_*.png
echo " ✓ Mod Icons Generated at $OUTPUT"
INDEX=$((INDEX + 1))
done
rm "$BLANK_IMAGE"
echo ""
echo "[Mod Icon Generation Finished — processed $SKIN_COUNT skins]"
- name: Create OSK Files
shell: bash
run: |
echo "[OSK Creation Job Started]"
if [ -z "${CHANGED_SKINS_FILE:-}" ] || [ ! -s "$CHANGED_SKINS_FILE" ]; then
echo "No skins changed. Skipping OSK creation."
exit 0
fi
mapfile -t skin_dirs < "$CHANGED_SKINS_FILE"
[ "${#skin_dirs[@]}" -eq 0 ] && { echo "No skins to process. Exiting."; exit 0; }
sanitize_filename() {
echo "$1" \
| sed -e 's#[\\/:\*\?"<>|]#-#g' -e 's#%#_#g' \
| tr -s ' ' \
| sed 's/^ *//;s/ *$//'
}
FIXED_TIMESTAMP="2025-01-01 00:00:00"
SKIN_COUNT=${#skin_dirs[@]}
INDEX=1
for skin_path in "${skin_dirs[@]}"; do
SKIN_DIR="$DANSER_SKINS_DIR/$skin_path"
[ ! -d "$SKIN_DIR" ] && { echo "Skipping missing skin directory: $SKIN_DIR"; ((INDEX++)); continue; }
OUTPUT_DIR="$OSK_PATH/$skin_path"
mkdir -p "$OUTPUT_DIR"
skin_header="$skin_path"
ini_file=$(find "$SKIN_DIR" -maxdepth 1 -iname "skin.ini" | head -n1 || true)
if [ -f "$ini_file" ]; then
name_line=$(grep -i '^[[:space:]]*name:' "$ini_file" | head -n1 || true)
if [ -n "$name_line" ]; then
val="${name_line#*:}"
val="$(echo "$val" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
sanitized="$(sanitize_filename "$val")"
[ -n "$sanitized" ] && skin_header="$sanitized"
fi
fi
echo ""
echo "[$INDEX/$SKIN_COUNT] Processing skin: $skin_header"
(cd "$SKIN_DIR" && find . -type f -exec touch -d "$FIXED_TIMESTAMP" {} +)
(cd "$SKIN_DIR" && find . -type f | sort | \
zip -rq -D -X -9 --compression-method deflate \
"$OUTPUT_DIR/${skin_header}.osk" -@)
echo " ✓ OSK file created at $OUTPUT_DIR/${skin_header}.osk"
INDEX=$((INDEX + 1))
done
echo ""
echo "[OSK Creation Job Finished — processed $SKIN_COUNT skins]"
- name: Generate README
shell: bash
run: |
echo "Starting README generation..."
sanitize_filename() {
echo "$1" | \
sed -e 's#[\\/:\*\?"<>|]#-#g' -e 's#%#_#g' | \
tr -s ' ' | \
sed 's/^ *//;s/ *$//'
}
SKINS_JSON_FILE="${{ github.workspace }}/.gitea/workflows/skins.json"
DESC_FILE=$(mktemp)
echo "Step 1: Extracting descriptions from skins.json..."
jq -r '.descriptions | to_entries[] | "\(.key)=\(.value)"' "$SKINS_JSON_FILE" > "$DESC_FILE"
echo "Step 2: Starting to build README..."
echo "---" > "$README_PATH"
echo "gitea: none" >> "$README_PATH"
echo "include_toc: true" >> "$README_PATH"
echo "---" >> "$README_PATH"
echo "" >> "$README_PATH"
echo "# Skins" >> "$README_PATH"
echo "" >> "$README_PATH"
echo "<!--" >> "$README_PATH"
echo "osuid: $OSU_ID" >> "$README_PATH"
echo "-->" >> "$README_PATH"
echo "" >> "$README_PATH"
echo "**Go back to [osc/skins]($REGISTRY_URL/osc/skins)**" >> "$README_PATH"
echo "" >> "$README_PATH"
get_desc() {
grep -F -m1 -- "$1=" "$DESC_FILE" 2>/dev/null | cut -d '=' -f2-
}
ORDER_FILE=$(mktemp)
echo "Step 3: Extracting order from skins.json..."
jq -r '.order[]?' "$SKINS_JSON_FILE" > "$ORDER_FILE"
echo "Step 4: Processing ordered skins..."
while IFS= read -r skin; do
echo " Processing skin (order): $skin"
dir="$DANSER_SKINS_DIR/$skin"
[ ! -d "$dir" ] && { echo " Skipping missing directory: $dir"; continue; }
ini_file=$(find "$dir" -maxdepth 1 -iname "skin.ini" | head -n1)
skin_header="$skin"
if [ -f "$ini_file" ]; then
line=$(grep -i '^[[:space:]]*Name:' "$ini_file" | head -n1 2>/dev/null || true)
[ -n "$line" ] && {
val="${line#*:}"
skin_header=$(sanitize_filename "$val")
}
fi
base_path=$(printf "%s/%s" "$skin" "$skin_header" | sed 's/ /%20/g')
echo "## [$skin_header]($REGISTRY_URL/$USER_REPOSITORY/media/tag/$new_tag/export/${base_path}.osk)" >> "$README_PATH"
echo "" >> "$README_PATH"
desc=$(get_desc "$skin")
[ -n "$desc" ] && { echo "$desc" >> "$README_PATH"; echo "" >> "$README_PATH"; }
if [ -f "$ini_file" ]; then
author_line=$(grep -i '^[[:space:]]*Author:' "$ini_file" | head -n1 2>/dev/null || true)
if [ -n "$author_line" ]; then
author="${author_line#*:}"
author="$(echo "$author" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
[ -n "$author" ] && { echo "**Author:** $author" >> "$README_PATH"; echo "" >> "$README_PATH"; }
fi
fi
echo "![$skin_header Gameplay](media/gameplay/${base_path}.gif)" >> "$README_PATH"
echo "" >> "$README_PATH"
echo "![$skin_header Ranking Panel](media/panel/${base_path}.png)" >> "$README_PATH"
echo "" >> "$README_PATH"
echo "![$skin_header Mods](media/icons/${base_path}-mod-icons.png)" >> "$README_PATH"
echo "" >> "$README_PATH"
done < "$ORDER_FILE"
declare -A ordered
while IFS= read -r skin; do
ordered["$skin"]=1
done < "$ORDER_FILE"
echo "Step 5: Processing extra skins..."
if [ ! -s "$ORDER_FILE" ]; then
echo "→ No \`order\` defined in skins.json; skipping extra-skins loop."
else
for dir in "$DANSER_SKINS_DIR"/*; do
[ -d "$dir" ] || continue
skin=$(basename "$dir")
[[ -n "${ordered[$skin]}" ]] && continue
echo " Writing extra skin: $skin"
ini_file=$(find "$dir" -maxdepth 1 -iname "skin.ini" | head -n1)
skin_header="$skin"
if [ -f "$ini_file" ]; then
line=$(grep -i '^[[:space:]]*Name:' "$ini_file" | head -n1 2>/dev/null || true)
[ -n "$line" ] && {
val="${line#*:}"
skin_header=$(sanitize_filename "$val")
}
fi
base_path=$(printf "%s/%s" "$skin" "$skin_header" | sed 's/ /%20/g')
echo "## [$skin_header]($REGISTRY_URL/$USER_REPOSITORY/media/tag/$new_tag/export/${base_path}.osk)" >> "$README_PATH"
echo "" >> "$README_PATH"
if [ -f "$ini_file" ]; then
author_line=$(grep -i '^[[:space:]]*Author:' "$ini_file" | head -n1 2>/dev/null || true)
if [ -n "$author_line" ]; then
author="${author_line#*:}"
author="$(echo "$author" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
[ -n "$author" ] && { echo "**Author:** $author" >> "$README_PATH"; echo "" >> "$README_PATH"; }
fi
fi
echo "![$skin_header Gameplay](media/gameplay/${base_path}.gif)" >> "$README_PATH"
echo "" >> "$README_PATH"
echo "![$skin_header Ranking Panel](media/panel/${base_path}.png)" >> "$README_PATH"
echo "" >> "$README_PATH"
echo "![$skin_header Mods](media/icons/${base_path}-mod-icons.png)" >> "$README_PATH"
echo "" >> "$README_PATH"
done
fi
echo "Step 7: Writing Build History section..."
echo "# Build History" >> "$README_PATH"
echo "" >> "$README_PATH"
echo "| Version | Date |" >> "$README_PATH"
echo "| ------- | ---- |" >> "$README_PATH"
current_commit_date=$(TZ="Europe/Zurich" date -d "$(git log -1 --format=%cI)" "+%d.%m.%Y %H:%M:%S")
echo "| [\`$new_tag (Current)\`]($REGISTRY_URL/$USER_REPOSITORY/src/tag/$new_tag/README.md) | $current_commit_date |" >> "$README_PATH"
old_tags=$(git tag --sort=-v:refname | grep -v "^$new_tag$" | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' || true)
if [ -n "$old_tags" ]; then
echo "$old_tags" | while read -r tag; do
tag_date=$(git log -1 --format=%ci "$tag")
formatted_date=$(TZ="Europe/Zurich" date -d "$tag_date" "+%d.%m.%Y %H:%M:%S")
echo "| [\`$tag\`]($REGISTRY_URL/$USER_REPOSITORY/src/tag/$tag/README.md) | $formatted_date |" >> "$README_PATH"
done
fi
echo "README generation completed successfully."
- name: Migrate flat assets into per-skin folders
shell: bash
run: |
set -euo pipefail
echo "[Migration of flat assets started]"
# helper to sanitize header
sanitize() {
echo "$1" \
| tr -d '\r\n' \
| sed -e 's#[\\/:\*\?"<>|]#-#g' -e 's#%#_#g' \
| tr -s ' ' \
| sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'
}
# build header→folder map
readarray -t skins <<< "$ALL_SKINS_DIR"
declare -A hdr2fld
for skin in "${skins[@]}"; do
[[ -z "${skin// }" ]] && continue
header=$(sanitize "$skin")
if ini=$(find "$DANSER_SKINS_DIR/$skin" -maxdepth 1 -type f -iname "skin.ini" -print -quit); then
raw=$(grep -i '^[[:space:]]*Name:' "$ini" | head -n1 || true)
raw="${raw#*:}"
header=$(sanitize "$raw")
fi
[[ -n "$header" ]] && hdr2fld["$header"]="$skin"
done
migrate() {
local root=$1 ext=$2
echo " → Migrating *.$ext in $root"
shopt -s nullglob
while IFS= read -r f; do
base="${f##*/}"
base="${base%.$ext}"
[[ "$root" == *icons ]] && base="${base%-mod-icons}"
key=$(sanitize "$base")
# debug: show what key its looking up and what it finds
echo " DEBUG: looking for key='$key' → '${hdr2fld[$key]:-<NONE>}'"
target="${hdr2fld[$key]:-}"
if [[ -n "$target" ]]; then
dst="$root/$target"
mkdir -p "$dst"
echo " • Moving $f → $dst/"
mv "$f" "$dst/"
else
echo " ✖ No mapping for '$base' (key='$key'), leaving $f"
fi
done < <(find "$root" -maxdepth 1 -type f -iname "*.$ext")
# remove any now-empty skin folders
find "$root" -mindepth 1 -maxdepth 1 -type d -empty -exec rmdir {} \; || true
shopt -u nullglob
}
migrate "$REPO_SCREENSHOT_DIR" gif
migrate "$REPO_RANKING_PANEL_DIR" png
migrate "$REPO_MOD_ICONS_DIR" png
migrate "$OSK_PATH" osk
echo "[Migration of flat assets complete]"
- name: Cleanup Extra Files
shell: bash
run: |
set -euo pipefail
echo "[Cleanup Extra Files Started]"
# Remove legacy docs
[ -d src/docs ] && rm -rf src/docs
[ -f how-to-use.md ] && rm -f how-to-use.md
# Read the current list of skins
readarray -t skins <<< "$ALL_SKINS_DIR"
# Helper to sanitize header (for pruning inside)
sanitize() {
echo "$1" \
| tr -d '\r\n' \
| sed -e 's#[\\/:\*\?"<>|]#-#g' -e 's#%#_#g' \
| tr -s ' ' \
| sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'
}
for root in "$REPO_SCREENSHOT_DIR" "$REPO_RANKING_PANEL_DIR" "$REPO_MOD_ICONS_DIR" "$OSK_PATH"; do
for dir in "$root"/*; do
[ -d "$dir" ] || continue
name="$(basename "$dir")"
if ! printf '%s\n' "${skins[@]}" | grep -Fxq -- "$name"; then
echo " → Skin '$name' deleted—removing directory $dir"
rm -rf "$dir"
fi
done
done
for skin in "${skins[@]}"; do
header=$(sanitize "$skin")
ini=$(find "$DANSER_SKINS_DIR/$skin" -maxdepth 1 -type f -iname "skin.ini" -print -quit || true)
if [[ -f "$ini" ]]; then
raw=$(grep -i '^[[:space:]]*Name:' "$ini" | head -n1 || true)
raw="${raw#*:}"
header=$(sanitize "$raw")
fi
expect_gif="$header.gif"
expect_png="$header.png"
expect_icon="$header-mod-icons.png"
expect_osk="$header.osk"
prune_dir() {
root=$1; expected=$2
dir="$root/$skin"
[ ! -d "$dir" ] && return
for f in "$dir"/*; do
[ -e "$f" ] || continue
if [[ "$(basename "$f")" != "$expected" ]]; then
echo " → Removing unexpected file: $f"
rm -f "$f"
fi
done
}
prune_dir "$REPO_SCREENSHOT_DIR" "$expect_gif"
prune_dir "$REPO_RANKING_PANEL_DIR" "$expect_png"
prune_dir "$REPO_MOD_ICONS_DIR" "$expect_icon"
prune_dir "$OSK_PATH" "$expect_osk"
done
echo "[Cleanup Extra Files Complete]"
- name: Configure Git
shell: bash
run: |
git config user.email "arlind@sulej.ch"
git config user.name "ci-bot"
- name: Add and Commit changes
shell: bash
run: |
git config advice.addIgnoredFile false
for p in media/gameplay media/panel media/icons export README.md how-to-use.md src; do
if [ -e "$p" ]; then
git add -A "$p"
fi
done
git commit -m "[ci skip] push back from pipeline" -q || echo "No changes to commit"
- name: Push changes and create tag
shell: bash
run: |
if [ "${GITHUB_REF}" = "refs/heads/main" ]; then
git push origin HEAD:main || echo "No changes to push"
git tag "$new_tag"
git push origin "$new_tag"
else
git push origin HEAD:"${GITHUB_REF_NAME}" || echo "No changes to push"
fi