Skip to content

Refactor IconTest and remove redundant tests; add ComposeListEndpoint… #112

Refactor IconTest and remove redundant tests; add ComposeListEndpoint…

Refactor IconTest and remove redundant tests; add ComposeListEndpoint… #112

Workflow file for this run

# .github/workflows/tag-release.yml
# Automatic release tagging on merge to main or dev
#
# When code is pushed to main or dev (typically via a merged PR), this workflow:
# 1. Generates a date-based version tag (vYYYY.MM.DD for stable, vYYYY.MM.DD.HHMM for beta)
# 2. Updates the PLG changelog with categorized commit notes
# 3. Pushes the tag, then calls build.yml via workflow_call to build and release
#
# Guards:
# - Skips bot commits ([skip ci], Release v*) to prevent infinite loops
# - Handles same-day tag collisions with a/b/c suffixes (stable only)
# - Beta releases require an open PR from dev → main
name: Tag Release
on:
push:
branches: [main, dev]
paths:
- 'source/compose.manager/**'
- 'source/pkg_build.sh'
- 'versions.env'
workflow_dispatch:
permissions:
contents: write
concurrency:
group: release-${{ github.ref_name }}
cancel-in-progress: false
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'
PLUGIN_NAME: compose.manager
jobs:
gate:
runs-on: ubuntu-latest
if: >-
!contains(github.event.head_commit.message, '[skip ci]')
&& !startsWith(github.event.head_commit.message, 'Release v')
&& github.event.head_commit.author.name != 'github-actions[bot]'
outputs:
should_release: ${{ steps.check.outputs.should_release }}
pr_number: ${{ steps.check.outputs.pr_number }}
steps:
- name: Check for qualifying open PR (dev → main)
id: check
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [[ "$GITHUB_REF_NAME" == "dev" ]]; then
PR_NUMBER=$(gh pr list --repo "${{ github.repository }}" \
--head dev --base main --state open \
--author mstrhakr --label release \
--json number --jq '.[0].number // empty')
if [[ -z "$PR_NUMBER" ]]; then
echo "::notice::No qualifying open PR from dev → main (must be by mstrhakr with 'release' label). Skipping beta release."
echo "should_release=false" >> $GITHUB_OUTPUT
exit 0
fi
echo "Open PR #${PR_NUMBER} found — proceeding with beta release."
echo "pr_number=${PR_NUMBER}" >> $GITHUB_OUTPUT
fi
echo "should_release=true" >> $GITHUB_OUTPUT
tag:
needs: gate
runs-on: ubuntu-latest
if: needs.gate.outputs.should_release == 'true'
outputs:
tag: ${{ steps.tag.outputs.tag }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Determine tag
id: tag
run: |
BRANCH="${GITHUB_REF_NAME}"
export TZ="America/Detroit"
DATE_VERSION=$(date +%Y.%m.%d)
if [[ "$BRANCH" == "dev" ]]; then
# Beta: v2026.03.15.HHMM (America/Detroit) — always unique
TIME_STAMP=$(date +%H%M)
NEW_TAG="v${DATE_VERSION}.${TIME_STAMP}"
echo "is_beta=true" >> $GITHUB_OUTPUT
else
# Stable: v2026.03.15, then v2026.03.15a, v2026.03.15b, ...
BASE_TAG="v${DATE_VERSION}"
# Find existing stable tags for today (keep only vYYYY.MM.DD[a-z] format)
EXISTING=$(git tag -l "${BASE_TAG}*" | grep -P '^v\d{4}\.\d{2}\.\d{2}[a-z]?$' | sort || true)
if [[ -z "$EXISTING" ]]; then
NEW_TAG="$BASE_TAG"
else
LAST_TAG=$(echo "$EXISTING" | tail -1)
if [[ "$LAST_TAG" == "$BASE_TAG" ]]; then
NEW_TAG="${BASE_TAG}a"
elif [[ "$LAST_TAG" =~ ^v[0-9]{4}\.[0-9]{2}\.[0-9]{2}([a-z])$ ]]; then
LAST_SUFFIX="${BASH_REMATCH[1]}"
# Increment suffix letter
NEXT_SUFFIX=$(echo "$LAST_SUFFIX" | tr 'a-y' 'b-z')
if [[ "$NEXT_SUFFIX" == "$LAST_SUFFIX" ]]; then
echo "::error::Too many releases today (exceeded 'z' suffix)"
exit 1
fi
NEW_TAG="${BASE_TAG}${NEXT_SUFFIX}"
else
echo "::error::Unexpected tag format: $LAST_TAG"
exit 1
fi
fi
echo "is_beta=false" >> $GITHUB_OUTPUT
fi
echo "tag=${NEW_TAG}" >> $GITHUB_OUTPUT
echo "version=${NEW_TAG#v}" >> $GITHUB_OUTPUT
echo "Generated tag: ${NEW_TAG}"
- name: Generate changelog
id: changelog
run: |
VERSION="${{ steps.tag.outputs.version }}"
NEW_TAG="${{ steps.tag.outputs.tag }}"
# Find the previous tag for this channel
IS_BETA="${{ steps.tag.outputs.is_beta }}"
if [[ "$IS_BETA" == "true" ]]; then
LAST_TAG=$(git tag -l 'v*' | sort -V | tail -1)
else
LAST_TAG=$(git tag -l 'v*' | grep -P '^v\d{4}\.\d{2}\.\d{2}[a-z]?$' | sort -V | tail -1)
fi
if [[ -n "$LAST_TAG" ]]; then
echo "Changes since $LAST_TAG"
RANGE="${LAST_TAG}..HEAD"
else
echo "No previous tag found — using all commits"
RANGE="HEAD"
fi
# Category mapping for conventional commits
declare -A CATEGORIES=(
[feat]="Features"
[fix]="Bug Fixes"
[perf]="Performance"
[refactor]="Refactoring"
[docs]="Documentation"
[test]="Tests"
[build]="Build"
[ci]="CI/CD"
[chore]="Chores"
)
# Ordered category keys
ORDER=(feat fix perf refactor docs test build ci chore)
# Collect commits by category
declare -A BUCKET
UNCATEGORIZED=""
while IFS= read -r subject; do
# Skip noise
[[ -z "$subject" ]] && continue
[[ "$subject" =~ ^Merge\ ]] && continue
[[ "$subject" =~ ^Release\ v ]] && continue
[[ "$subject" =~ ^\[skip\ ci\] ]] && continue
[[ "$subject" =~ ^v[0-9]{4}\. ]] && continue
# Parse conventional commit: type(scope): message
# Regex must be in a variable — bash [[ =~ ]] chokes on inline )
CONV_RE='^([a-z]+)(\([^)]+\))?:[[:space:]](.+)$'
if [[ "$subject" =~ $CONV_RE ]]; then
TYPE="${BASH_REMATCH[1]}"
SCOPE="${BASH_REMATCH[2]}"
MSG="${BASH_REMATCH[3]}"
LABEL="${CATEGORIES[$TYPE]:-$TYPE}"
if [[ -n "$SCOPE" ]]; then
LINE="- ${LABEL} ${SCOPE}: ${MSG}"
else
LINE="- ${LABEL}: ${MSG}"
fi
BUCKET[$TYPE]+="${LINE}"$'\n'
else
UNCATEGORIZED+="- ${subject}"$'\n'
fi
done < <(git log "$RANGE" --pretty=format:"%s" --no-merges)
# Build changelog text
NOTES="###${VERSION}"$'\n'
HAS_CONTENT=false
for TYPE in "${ORDER[@]}"; do
if [[ -n "${BUCKET[$TYPE]}" ]]; then
NOTES+="${BUCKET[$TYPE]}"
HAS_CONTENT=true
fi
done
if [[ -n "$UNCATEGORIZED" ]]; then
NOTES+="${UNCATEGORIZED}"
HAS_CONTENT=true
fi
if [[ "$HAS_CONTENT" == false ]]; then
NOTES+="- Minor updates and improvements"$'\n'
fi
# Add comparison and PR links
if [[ "$IS_BETA" == "true" ]]; then
PR_NUMBER="${{ needs.gate.outputs.pr_number }}"
if [[ -n "$PR_NUMBER" ]]; then
NOTES+="- [PR #${PR_NUMBER}](https://github.com/${{ github.repository }}/pull/${PR_NUMBER})"$'\n'
fi
LATEST_STABLE=$(git tag -l 'v*' | grep -P '^v\d{4}\.\d{2}\.\d{2}[a-z]?$' | sort -V | tail -1)
if [[ -n "$LATEST_STABLE" ]]; then
NOTES+="- [beta release diff](https://github.com/${{ github.repository }}/compare/${LATEST_STABLE}...${NEW_TAG})"$'\n'
fi
elif [[ -n "$LAST_TAG" ]]; then
NOTES+="- [View all changes](https://github.com/${{ github.repository }}/compare/${LAST_TAG}...${NEW_TAG})"$'\n'
fi
# Save for next step
echo "$NOTES" > /tmp/changelog.txt
echo "Changelog:"
cat /tmp/changelog.txt
- name: Update PLG changelog
run: |
PLG="${{ env.PLUGIN_NAME }}.plg"
CHANGELOG=$(cat /tmp/changelog.txt)
# XML-escape the changelog
ESCAPED=$(echo "$CHANGELOG" | sed 's/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g')
# Replace everything between <CHANGES> and </CHANGES>
python3 - "$PLG" "$ESCAPED" << 'PYEOF'
import sys, re
plg_path, new_notes = sys.argv[1], sys.argv[2]
with open(plg_path, 'r') as f:
content = f.read()
pattern = r'(<CHANGES>\n).*?(\n</CHANGES>)'
replacement = r'\g<1>' + new_notes.rstrip('\n') + r'\g<2>'
content = re.sub(pattern, replacement, content, flags=re.DOTALL)
with open(plg_path, 'w') as f:
f.write(content)
PYEOF
echo "Updated PLG changelog"
- name: Sync pluginURL and README for branch
run: |
BRANCH="${GITHUB_REF_NAME}"
PLG_FILE="${{ env.PLUGIN_NAME }}.plg"
README_FILE="source/compose.manager/README.md"
# Ensure pluginURL entity references the current branch
# Scope sed to the pluginURL line only — avoids corrupting packageURL
CURRENT_URL_BRANCH=$(grep -oP '<!ENTITY pluginURL.*github;/\K[^/]+' "$PLG_FILE" || echo "unknown")
if [[ "$CURRENT_URL_BRANCH" != "$BRANCH" ]]; then
echo "Fixing pluginURL branch: $CURRENT_URL_BRANCH → $BRANCH"
sed -i "/pluginURL/s|/&github;/[^/]*/|/\&github;/${BRANCH}/|" "$PLG_FILE"
fi
# Ensure README title matches branch disposition
if [[ "$BRANCH" == "dev" ]]; then
if ! grep -q '^\*\*Compose Manager Plus (Beta)\*\*$' "$README_FILE"; then
sed -i '1s|^\*\*Compose Manager Plus.*\*\*$|**Compose Manager Plus (Beta)**|' "$README_FILE"
fi
else
if grep -q '^\*\*Compose Manager Plus (Beta)\*\*$' "$README_FILE"; then
sed -i '1s|^\*\*Compose Manager Plus (Beta)\*\*$|**Compose Manager Plus**|' "$README_FILE"
fi
fi
- name: Commit changelog and create tag
run: |
NEW_TAG="${{ steps.tag.outputs.tag }}"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add "${{ env.PLUGIN_NAME }}.plg" "source/compose.manager/README.md"
git diff --cached --quiet && echo "No changelog changes" || \
git commit -m "chore: update changelog for ${NEW_TAG} [skip ci]"
# Rebase on latest remote to avoid non-fast-forward rejection
git pull --rebase origin "${GITHUB_REF_NAME}"
git push origin "${GITHUB_REF_NAME}"
# Create and push the tag
git tag "$NEW_TAG"
git push origin "$NEW_TAG"
echo "::notice::Tagged ${NEW_TAG} — calling build.yml via workflow_call"
build:
needs: tag
uses: ./.github/workflows/build.yml
with:
tag: ${{ needs.tag.outputs.tag }}
permissions:
contents: write