Cleanup Old Releases #14
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # This workflow automatically cleans up old releases to maintain a manageable number of releases per tool. | |
| # | |
| # Purpose: | |
| # - Fetches all releases from the repository | |
| # - Groups releases by tool name (e.g., "HTML Sample Tool", "PCF Builder", "Data Migrator") | |
| # - Keeps only the top 3 most recent releases for each tool | |
| # - Deletes older releases automatically | |
| # | |
| # Release Name Format: | |
| # - Positive matches: "HTML Sample Tool v0.0.1", "HTML Sample Tool v0.0.2" | |
| # - These are grouped as "HTML Sample Tool" and the newest 3 are kept | |
| # - Negative matches (different tools): "PCF Builder v0.0.1", "Data Migrator v0.0.1" | |
| # - Each tool is handled separately | |
| # | |
| # Features: | |
| # - Scheduled: Runs weekly on Sundays at 00:00 UTC | |
| # - Manual trigger: Can be run manually via workflow_dispatch | |
| # - Dry-run mode: Default mode that shows what would be deleted without actually deleting | |
| # - Delete mode: Set dry_run to 'false' to actually delete old releases | |
| # | |
| # Usage: | |
| # 1. By default (scheduled or manual), runs in DRY RUN mode - safe to test | |
| # 2. To actually delete releases, manually trigger with dry_run='false' | |
| # 3. Review the workflow logs to see what will be deleted before running in delete mode | |
| # | |
| name: Cleanup Old Releases | |
| on: | |
| schedule: | |
| # Run weekly on Sundays at 00:00 UTC | |
| - cron: "0 0 * * 0" | |
| workflow_dispatch: | |
| inputs: | |
| dry_run: | |
| description: "Dry run mode (true to only show what would be deleted, false to actually delete)" | |
| required: false | |
| default: "true" | |
| type: choice | |
| options: | |
| - "true" | |
| - "false" | |
| jobs: | |
| cleanup-releases: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "20" | |
| - name: Fetch and cleanup releases | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| DRY_RUN: ${{ github.event.inputs.dry_run || 'true' }} | |
| run: | | |
| set -e | |
| echo "===================================" | |
| echo "Release Cleanup Workflow" | |
| echo "===================================" | |
| echo "Dry Run Mode: $DRY_RUN" | |
| echo "" | |
| # Fetch all releases | |
| echo "📥 Fetching all releases..." | |
| gh release list --limit 1000 --json tagName,name,publishedAt,createdAt > releases.json | |
| RELEASE_COUNT=$(jq 'length' releases.json) | |
| echo "Found $RELEASE_COUNT total releases" | |
| echo "" | |
| if [ "$RELEASE_COUNT" -eq 0 ]; then | |
| echo "✅ No releases found. Nothing to clean up." | |
| exit 0 | |
| fi | |
| # Parse releases and group by tool name | |
| # Release name format: "Tool Name v0.0.1" or "Tool Name v1.2.3" | |
| # We need to extract the tool name (everything before " v") | |
| echo "🔍 Analyzing releases and grouping by tool..." | |
| # Create a list of tools with their releases | |
| jq -r '.[] | "\(.name)|\(.tagName)|\(.publishedAt // .createdAt // "1970-01-01T00:00:00Z")"' releases.json | while IFS='|' read -r name tag published; do | |
| # Use tag as fallback when the release name is empty/null so grouping still works | |
| display_name="$name" | |
| if [ -z "$display_name" ] || [ "$display_name" = "null" ]; then | |
| display_name="$tag" | |
| fi | |
| if [ -z "$display_name" ]; then | |
| display_name="Unknown Release" | |
| fi | |
| # Extract tool name by removing version pattern (v followed by numbers and dots) | |
| # Match patterns like " v0.0.1", " v1.2.3", etc. | |
| tool_name=$(echo "$display_name" | sed -E 's/ v[0-9]+\.[0-9]+\.[0-9]+.*$//') | |
| # If tool_name is empty or same as name, it might not match our pattern | |
| if [ -z "$tool_name" ] || [ "$tool_name" = "$display_name" ]; then | |
| # Try alternative pattern: remove everything after last space if it starts with v | |
| if echo "$display_name" | grep -qE ' v[0-9]'; then | |
| tool_name=$(echo "$display_name" | sed -E 's/ v[0-9].*$//') | |
| else | |
| tool_name="$display_name" | |
| fi | |
| fi | |
| if [ -z "$tool_name" ]; then | |
| tool_name="Unknown Tool" | |
| fi | |
| echo "$tool_name|$display_name|$tag|$published" | |
| done | sort > releases_with_tools.txt | |
| # Get unique tool names | |
| cut -d'|' -f1 releases_with_tools.txt | sort -u > unique_tools.txt | |
| TOOL_COUNT=$(wc -l < unique_tools.txt) | |
| echo "Identified $TOOL_COUNT unique tools" | |
| echo "" | |
| # For each tool, find releases to delete (all but top 3) | |
| echo "📊 Analysis by Tool:" | |
| echo "====================" | |
| # Clear the delete list file | |
| > releases_to_delete.txt | |
| while read -r tool_name; do | |
| echo "" | |
| echo "Tool: $tool_name" | |
| # Get all releases for this tool, sorted by date (newest first) | |
| # Use grep -F for fixed string matching to avoid regex metacharacter issues | |
| grep -F "$tool_name|" releases_with_tools.txt | sort -t'|' -k4 -r > tool_releases.txt | |
| release_count=$(wc -l < tool_releases.txt) | |
| echo " Total releases: $release_count" | |
| if [ "$release_count" -le 3 ]; then | |
| echo " ✅ Keeping all $release_count releases (≤ 3)" | |
| else | |
| keep_count=3 | |
| delete_count=$((release_count - keep_count)) | |
| echo " ⚠️ Will delete $delete_count old releases (keeping newest 3)" | |
| # Show what we're keeping | |
| echo " 📌 Keeping:" | |
| head -n 3 tool_releases.txt | while IFS='|' read -r tn rname rtag rpub; do | |
| echo " - $rname (published: $rpub)" | |
| done | |
| # Show what we're deleting | |
| echo " 🗑️ Marking for deletion:" | |
| tail -n +4 tool_releases.txt | while IFS='|' read -r tn rname rtag rpub; do | |
| echo " - $rname (published: $rpub)" | |
| # Append to file instead of variable to avoid subshell issues | |
| echo "$rtag|$rname" >> releases_to_delete.txt | |
| done | |
| fi | |
| done < unique_tools.txt | |
| # Count releases to delete | |
| if [ -s releases_to_delete.txt ]; then | |
| DELETE_COUNT=$(wc -l < releases_to_delete.txt) | |
| else | |
| DELETE_COUNT=0 | |
| fi | |
| echo "" | |
| echo "===================================" | |
| echo "Summary" | |
| echo "===================================" | |
| echo "Total releases: $RELEASE_COUNT" | |
| echo "Unique tools: $TOOL_COUNT" | |
| echo "Releases to delete: $DELETE_COUNT" | |
| echo "" | |
| if [ "$DELETE_COUNT" -eq 0 ]; then | |
| echo "✅ Nothing to delete. All tools have ≤ 3 releases." | |
| exit 0 | |
| fi | |
| if [ "$DRY_RUN" = "true" ]; then | |
| echo "🔍 DRY RUN MODE - No releases will be deleted" | |
| echo "" | |
| echo "To actually delete these releases, run this workflow again with dry_run=false" | |
| else | |
| echo "⚠️ DELETION MODE - Deleting releases..." | |
| echo "" | |
| # Delete releases | |
| while IFS='|' read -r rtag rname; do | |
| if [ -n "$rtag" ]; then | |
| echo "Deleting: $rname ($rtag)" | |
| if gh release delete "$rtag" --yes --cleanup-tag 2>&1; then | |
| echo " ✅ Deleted successfully" | |
| else | |
| echo " ❌ Failed to delete" | |
| fi | |
| fi | |
| done < releases_to_delete.txt | |
| echo "" | |
| echo "✅ Cleanup complete!" | |
| fi |