Update proxy.lua #44
Workflow file for this run
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
| name: Build and Release Executable | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| manual_previous_tag: | |
| description: 'Optional: Manually set the previous tag to generate the changelog from.' | |
| required: false | |
| default: '' | |
| dry_run: | |
| description: 'Dry run mode for pruning (preview without deleting)' | |
| required: false | |
| type: boolean | |
| default: false | |
| push: | |
| paths: | |
| - 'mic_python/python/**' | |
| - 'launch_mic.bat' | |
| - '.github/workflows/build.yml' | |
| - 'cliff.toml' | |
| jobs: | |
| build: | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| matrix: | |
| os: [windows-latest, ubuntu-latest, macos-latest] | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@v4 | |
| - name: Set up uv | |
| uses: astral-sh/setup-uv@v4 | |
| with: | |
| enable-cache: true | |
| cache-dependency-glob: "mic_python/python/requirements.txt" | |
| - name: Set up Python with uv | |
| shell: bash | |
| run: | | |
| uv python install 3.12 | |
| uv venv --python 3.12 | |
| - name: Install dependencies | |
| shell: bash | |
| run: | | |
| cd mic_python/python | |
| source ../../.venv/bin/activate || . ../../.venv/Scripts/activate | |
| uv pip install -r requirements.txt | |
| uv pip install pyinstaller | |
| - name: Get PyInstaller cache directory | |
| id: pyinstaller-cache-dir | |
| shell: bash | |
| run: | | |
| if [ "${{ runner.os }}" == "Windows" ]; then | |
| echo "path=$USERPROFILE/AppData/Local/pyinstaller" >> $GITHUB_OUTPUT | |
| elif [ "${{ runner.os }}" == "Linux" ]; then | |
| echo "path=$HOME/.cache/pyinstaller" >> $GITHUB_OUTPUT | |
| elif [ "${{ runner.os }}" == "macOS" ]; then | |
| echo "path=$HOME/Library/Application Support/pyinstaller" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Cache PyInstaller build data | |
| uses: actions/cache@v4 | |
| with: | |
| path: ${{ steps.pyinstaller-cache-dir.outputs.path }} | |
| key: ${{ runner.os }}-pyinstaller-3.12-${{ hashFiles('mic_python/python/requirements.txt') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pyinstaller-3.12- | |
| - name: Build executable | |
| shell: bash | |
| run: | | |
| cd mic_python/python | |
| source ../../.venv/bin/activate || . ../../.venv/Scripts/activate | |
| if [ "${{ runner.os }}" == "Windows" ]; then | |
| ./build.bat | |
| else | |
| # Run PyInstaller directly on Unix systems | |
| # Note: --icon parameter works differently on different platforms but PyInstaller handles it | |
| python -m PyInstaller --onefile --name talker_mic --icon=talker_mic.ico --hidden-import=gemini_proxy --hidden-import=whisper_local --hidden-import=whisper_api main.py | |
| fi | |
| - name: Get short SHA | |
| id: version | |
| shell: bash | |
| run: | | |
| sha=$(git rev-parse --short HEAD) | |
| echo "sha=$sha" >> $GITHUB_OUTPUT | |
| - name: Prepare files for artifact | |
| shell: bash | |
| run: | | |
| stagingDir="staging" | |
| mkdir -p $stagingDir | |
| if [ "${{ runner.os }}" == "Windows" ]; then | |
| cp mic_python/python/dist/talker_mic.exe "$stagingDir/" | |
| else | |
| cp mic_python/python/dist/talker_mic "$stagingDir/" | |
| fi | |
| cp launch_mic.bat "$stagingDir/" | |
| echo "--- Staging directory contents ---" | |
| ls -R $stagingDir | |
| echo "------------------------------------" | |
| - name: Archive build artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: talker-mic-build-${{ runner.os }}-${{ steps.version.outputs.sha }} | |
| path: staging/ | |
| release: | |
| needs: build | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| env: | |
| WHITELISTED_BRANCHES: "main" | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Fetch all tags and history | |
| shell: bash | |
| run: git fetch --prune --tags | |
| - name: Get short SHA | |
| id: get_sha | |
| shell: bash | |
| run: echo "sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT | |
| - name: Generate Build Version | |
| id: version | |
| shell: bash | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| BRANCH_NAME=${{ github.ref_name }} | |
| DATE_STAMP_NEW=$(date +'%Y%m%d') | |
| DATE_STAMP_OLD=$(date +'%Y.%m.%d') | |
| # Find the number of releases already created today for this branch, matching either old or new format. | |
| # We use grep -E for an OR condition and wrap it to prevent failures when no matches are found. | |
| BUILD_COUNT=$(gh release list --repo "${{ github.repository }}" --limit 100 | { grep -E "$BRANCH_NAME/build-($DATE_STAMP_NEW|$DATE_STAMP_OLD)" || true; } | wc -l) | |
| # Increment the build number for the new release | |
| BUILD_NUMBER=$((BUILD_COUNT + 1)) | |
| # Create the new, sortable version string using the new format | |
| VERSION="$DATE_STAMP_NEW-$BUILD_NUMBER-${{ steps.get_sha.outputs.sha }}" | |
| # Define all naming components | |
| echo "release_title=Build ($BRANCH_NAME): $VERSION" >> $GITHUB_OUTPUT | |
| echo "release_tag=$BRANCH_NAME/build-$VERSION" >> $GITHUB_OUTPUT | |
| echo "archive_version_part=$BRANCH_NAME-$VERSION" >> $GITHUB_OUTPUT | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "timestamp=$(date -u +'%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_OUTPUT | |
| - name: Download build artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: release-assets | |
| pattern: talker-mic-build-*-${{ steps.get_sha.outputs.sha }} | |
| - name: Archive release files | |
| id: archive | |
| shell: bash | |
| run: | | |
| ASSET_PATHS="" | |
| for dir in release-assets/talker-mic-build-*; do | |
| if [ -d "$dir" ]; then | |
| os_name=$(basename "$dir" | cut -d'-' -f4) | |
| archive_name="TALKER-Mic-${os_name}-${{ steps.version.outputs.archive_version_part }}.zip" | |
| ( | |
| cd "$dir" | |
| zip -r "../../$archive_name" . | |
| ) | |
| if [ -z "$ASSET_PATHS" ]; then | |
| ASSET_PATHS="$archive_name" | |
| else | |
| ASSET_PATHS="$ASSET_PATHS $archive_name" | |
| fi | |
| fi | |
| done | |
| echo "ASSET_PATHS=$ASSET_PATHS" >> $GITHUB_OUTPUT | |
| - name: Install git-cliff | |
| shell: bash | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| API_RESPONSE=$(curl -s -H "Authorization: token $GITHUB_TOKEN" https://api.github.com/repos/orhun/git-cliff/releases/latest) | |
| LATEST_CLIFF_URL=$(echo "$API_RESPONSE" | jq -r '.assets[] | select(.name | endswith("x86_64-unknown-linux-gnu.tar.gz")) | .browser_download_url') | |
| if [ -z "$LATEST_CLIFF_URL" ]; then | |
| echo "::error::Could not find git-cliff asset URL." | |
| echo "API Response: $API_RESPONSE" | |
| exit 1 | |
| fi | |
| curl -L "$LATEST_CLIFF_URL" | tar xz | |
| sudo mv git-cliff-*/git-cliff /usr/local/bin/ | |
| - name: Prepare git-cliff config | |
| shell: bash | |
| run: | | |
| # Inject the GitHub repo URL into your template | |
| sed -i "s|{{ repository_url }}|https://github.com/${GITHUB_REPOSITORY}|g" .github/cliff.toml | |
| echo "✅ cliff.toml:" | |
| head -20 .github/cliff.toml | |
| - name: Generate Changelog | |
| id: changelog | |
| shell: bash | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| BRANCH_NAME=${{ github.ref_name }} | |
| if [ -n "${{ github.event.inputs.manual_previous_tag }}" ]; then | |
| echo "Manual tag provided: ${{ github.event.inputs.manual_previous_tag }}" | |
| LAST_TAG="${{ github.event.inputs.manual_previous_tag }}" | |
| else | |
| echo "No manual tag, searching for latest tag on branch '$BRANCH_NAME'..." | |
| # Prioritize finding the latest tag with the new format (e.g., build-20250707-1-...). | |
| echo "Attempting to find latest tag with new format..." | |
| LAST_TAG=$(git describe --tags --abbrev=0 --match="$BRANCH_NAME/build-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-*" 2>/dev/null || true) | |
| # If no new format tag is found, fall back to the old, more generic pattern. | |
| if [ -z "$LAST_TAG" ]; then | |
| echo "No new format tag found. Falling back to search for any older build tag..." | |
| LAST_TAG=$(git describe --tags --abbrev=0 --match="$BRANCH_NAME/build-*" 2>/dev/null || echo "") | |
| fi | |
| fi | |
| echo "✅ Using tag: $LAST_TAG" | |
| if [ -n "$LAST_TAG" ]; then | |
| # Standard run: A previous tag was found. | |
| echo "🔍 Generating changelog for range: $LAST_TAG..HEAD" | |
| git-cliff \ | |
| --config .github/cliff.toml \ | |
| --strip all \ | |
| --output changelog.md \ | |
| "$LAST_TAG..HEAD" | |
| else | |
| # First run: No previous tag found. | |
| echo "⚠️ No previous build tag found. Generating initial release changelog." | |
| echo "## Initial Release" > changelog.md | |
| echo "" >> changelog.md | |
| echo "This is the first automated build release using this format. Future releases will contain a detailed list of changes." >> changelog.md | |
| fi | |
| # This part of the script remains to handle the output | |
| if [ -s changelog.md ]; then | |
| echo "✅ Changelog generated successfully" | |
| CHANGELOG_B64=$(base64 -w 0 changelog.md) | |
| echo "changelog_b64=$CHANGELOG_B64" >> $GITHUB_OUTPUT | |
| echo "has_changelog=true" >> $GITHUB_OUTPUT | |
| echo "previous_tag=$LAST_TAG" >> $GITHUB_OUTPUT | |
| else | |
| # This is now a true error condition | |
| echo "❌ Critical error: Changelog is empty after generation." | |
| echo "has_changelog=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Debug artifact contents | |
| shell: bash | |
| run: | | |
| echo "🔍 Debugging artifact contents..." | |
| echo "Current directory:" | |
| pwd | |
| echo "" | |
| echo "Release assets directory contents:" | |
| ls -la release-assets/ || echo "release-assets directory not found" | |
| echo "" | |
| echo "All files in current directory:" | |
| find . -name "*.exe" -o -name "*.bat" -o -name ".env*" | head -20 | |
| echo "" | |
| echo "Directory structure:" | |
| find release-assets -type f 2>/dev/null || echo "No files found in release-assets" | |
| - name: Generate Build Metadata | |
| id: metadata | |
| shell: bash | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| # Find executable files and get their sizes | |
| WINDOWS_EXE=$(find release-assets -name "talker_mic.exe" -type f | head -1) | |
| if [ -n "$WINDOWS_EXE" ]; then | |
| WIN_SIZE=$(du -sh "$WINDOWS_EXE" | cut -f1) | |
| else | |
| WIN_SIZE="Unknown" | |
| fi | |
| echo "win_build_size=$WIN_SIZE" >> $GITHUB_OUTPUT | |
| LINUX_EXE=$(find release-assets -path "*/talker-mic-build-Linux-*/talker_mic" -type f | head -1) | |
| if [ -n "$LINUX_EXE" ]; then | |
| LINUX_SIZE=$(du -sh "$LINUX_EXE" | cut -f1) | |
| else | |
| LINUX_SIZE="Unknown" | |
| fi | |
| echo "linux_build_size=$LINUX_SIZE" >> $GITHUB_OUTPUT | |
| MACOS_EXE=$(find release-assets -path "*/talker-mic-build-macOS-*/talker_mic" -type f | head -1) | |
| if [ -n "$MACOS_EXE" ]; then | |
| MACOS_SIZE=$(du -sh "$MACOS_EXE" | cut -f1) | |
| else | |
| MACOS_SIZE="Unknown" | |
| fi | |
| echo "macos_build_size=$MACOS_SIZE" >> $GITHUB_OUTPUT | |
| COMMIT_COUNT=$(git rev-list --count HEAD) | |
| # Generate rich contributor list | |
| if [ -n "${{ steps.changelog.outputs.previous_tag }}" ]; then | |
| echo "✅ Found previous tag, getting contributors since ${{ steps.changelog.outputs.previous_tag }}" | |
| CONTRIBUTOR_LOG=$(git log ${{ steps.changelog.outputs.previous_tag }}..HEAD --format='%ae' | sort -u) | |
| else | |
| echo "⚠️ No previous tag found, getting author of the last commit." | |
| CONTRIBUTOR_LOG=$(git log -1 --format='%ae') | |
| fi | |
| CONTRIBUTORS_LIST="" | |
| while read -r email; do | |
| # Find user by email | |
| USER_INFO=$(gh api "search/users?q=$email+in:email" --jq '.items[0]') | |
| if [ -n "$USER_INFO" ]; then | |
| USERNAME=$(echo "$USER_INFO" | jq -r '.login') | |
| AVATAR_URL=$(echo "$USER_INFO" | jq -r '.avatar_url') | |
| CONTRIBUTORS_LIST="$CONTRIBUTORS_LIST [](https://github.com/$USERNAME) " | |
| fi | |
| done <<< "$CONTRIBUTOR_LOG" | |
| echo "commit_count=$COMMIT_COUNT" >> $GITHUB_OUTPUT | |
| echo "contributors_list=$CONTRIBUTORS_LIST" >> $GITHUB_OUTPUT | |
| echo "📊 Build metadata:" | |
| echo " - Size (Windows): $WIN_SIZE" | |
| echo " - Size (Linux): $LINUX_SIZE" | |
| echo " - Size (macOS): $MACOS_SIZE" | |
| echo " - Commits: $COMMIT_COUNT" | |
| echo " - Contributors: $CONTRIBUTORS_LIST" | |
| - name: Create Release | |
| shell: bash | |
| run: | | |
| # Prepare changelog content | |
| if [ "${{ steps.changelog.outputs.has_changelog }}" == "true" ]; then | |
| echo "${{ steps.changelog.outputs.changelog_b64 }}" | base64 -d > decoded_changelog.md | |
| CHANGELOG_CONTENT=$(cat decoded_changelog.md) | |
| else | |
| CHANGELOG_CONTENT="No significant changes detected in this release." | |
| fi | |
| # Prepare the full release notes in a temporary file | |
| if [ -n "${{ steps.changelog.outputs.previous_tag }}" ]; then | |
| CHANGELOG_URL="**Full Changelog**: https://github.com/${{ github.repository }}/compare/${{ steps.changelog.outputs.previous_tag }}...${{ steps.version.outputs.release_tag }}" | |
| else | |
| CHANGELOG_URL="" | |
| fi | |
| # Generate file descriptions | |
| FILE_TABLE="| File | Description | | |
| |------|-------------| | |
| | \`talker_mic.exe\` | Main executable for **Windows** | | |
| | \`talker_mic\` | Main executable for **Linux** and **macOS** | | |
| | \`launch_mic.bat\` | Setup script - use it to launch |" | |
| # List archives | |
| WINDOWS_ARCHIVE=$(echo "${{ steps.archive.outputs.ASSET_PATHS }}" | tr ' ' '\n' | grep 'Windows' || echo "N/A") | |
| LINUX_ARCHIVE=$(echo "${{ steps.archive.outputs.ASSET_PATHS }}" | tr ' ' '\n' | grep 'Linux' || echo "N/A") | |
| MACOS_ARCHIVE=$(echo "${{ steps.archive.outputs.ASSET_PATHS }}" | tr ' ' '\n' | grep 'macOS' || echo "N/A") | |
| ARCHIVE_LIST="- **Windows**: \`$WINDOWS_ARCHIVE\` | |
| - **Linux**: \`$LINUX_ARCHIVE\` | |
| - **macOS**: \`$MACOS_ARCHIVE\`" | |
| cat > releasenotes.md <<-EOF | |
| ## Build Information | |
| | Field | Value | | |
| |-------|-------| | |
| | 📦 **Version** | \`${{ steps.version.outputs.version }}\` | | |
| | 💾 **Binary Size** | Win: \`${{ steps.metadata.outputs.win_build_size }}\`, Linux: \`${{ steps.metadata.outputs.linux_build_size }}\`, macOS: \`${{ steps.metadata.outputs.macos_build_size }}\` | | |
| | 🔗 **Commit** | [\`${{ steps.get_sha.outputs.sha }}\`](https://github.com/${{ github.repository }}/commit/${{ github.sha }}) | | |
| | 📅 **Build Date** | \`${{ steps.version.outputs.timestamp }}\` | | |
| | ⚡ **Trigger** | \`${{ github.event_name }}\` | | |
| ## 📋 What's Changed | |
| $CHANGELOG_CONTENT | |
| ### 📁 Included Files | |
| Each OS-specific archive contains the following files: | |
| $FILE_TABLE | |
| ### 📦 Archives | |
| $ARCHIVE_LIST | |
| ## 🔗 Useful Links | |
| - 📖 [Documentation](https://github.com/${{ github.repository }}/wiki) | |
| - 🐛 [Report Issues](https://github.com/${{ github.repository }}/issues) | |
| - 💬 [Discussions](https://github.com/${{ github.repository }}/discussions) | |
| - 🌟 [Star this repo](https://github.com/${{ github.repository }}) if you find it useful! | |
| --- | |
| > **Note**: This is an automated build release. | |
| $CHANGELOG_URL | |
| EOF | |
| # Set release flags and notes based on the branch | |
| CURRENT_BRANCH="${{ github.ref_name }}" | |
| PRERELEASE_FLAG="" | |
| LATEST_FLAG="--latest" | |
| EXPERIMENTAL_NOTE="" | |
| # Check if the current branch is in the comma-separated whitelist | |
| if ! [[ ",${{ env.WHITELISTED_BRANCHES }}," == *",$CURRENT_BRANCH,"* ]]; then | |
| PRERELEASE_FLAG="--prerelease" | |
| LATEST_FLAG="" # Do not mark non-whitelisted branches as 'latest' | |
| EXPERIMENTAL_NOTE=$(cat <<-EOF | |
| > [!WARNING] | |
| > | ⚠️ **EXPERIMENTAL BUILD** ⚠️ | | |
| > |:---------------------------:| | |
| > This release is from the [\`$CURRENT_BRANCH\`](https://github.com/${{ github.repository }}/tree/$CURRENT_BRANCH) branch and is **highly unstable**. It contains features that are under active development, may be feature-incomplete, contain bugs, or have features that will be removed in the future. | |
| > | |
| > **Do not use in production environments.** | |
| > | |
| > --- | |
| > | |
| > **Found an issue?** Please [report it here](https://github.com/${{ github.repository }}/issues/new/choose) and include the build version (\`${{ steps.version.outputs.version }}\`) in your report. | |
| EOF | |
| ) | |
| fi | |
| # Prepend the experimental note if it exists | |
| if [ -n "$EXPERIMENTAL_NOTE" ]; then | |
| echo "$EXPERIMENTAL_NOTE" > releasenotes_temp.md | |
| echo "" >> releasenotes_temp.md | |
| cat releasenotes.md >> releasenotes_temp.md | |
| mv releasenotes_temp.md releasenotes.md | |
| fi | |
| # Create the release using the notes file | |
| gh release create ${{ steps.version.outputs.release_tag }} \ | |
| --target ${{ github.sha }} \ | |
| --title "${{ steps.version.outputs.release_title }}" \ | |
| --notes-file releasenotes.md \ | |
| $LATEST_FLAG \ | |
| $PRERELEASE_FLAG \ | |
| ${{ steps.archive.outputs.ASSET_PATHS }} | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Prune Old Releases | |
| if: always() # Run even if release creation failed (optional, but safer to run only on success usually. Let's stick to default behavior which is success) | |
| shell: bash | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PRUNE_ENABLED: false | |
| PROTECTED_BRANCHES: "main,master,production,prod,staging,develop" | |
| RETENTION_DAYS_FULL: 2 | |
| RETENTION_KEEP_ONE_DAILY_OLDER: true | |
| RETENTION_MAX_COUNT: 10 | |
| DRY_RUN: ${{ github.event.inputs.dry_run }} | |
| CURRENT_TAG: ${{ steps.version.outputs.release_tag }} | |
| run: | | |
| # 1. Check if enabled | |
| if [ "$PRUNE_ENABLED" != "true" ]; then | |
| echo "ℹ️ Pruning is disabled." | |
| exit 0 | |
| fi | |
| CURRENT_BRANCH="${{ github.ref_name }}" | |
| # 2. Check Protected Branches | |
| IFS=',' read -ra PROTECTED <<< "$PROTECTED_BRANCHES" | |
| for branch in "${PROTECTED[@]}"; do | |
| # Trim whitespace | |
| branch=$(echo "$branch" | xargs) | |
| if [ "$CURRENT_BRANCH" == "$branch" ]; then | |
| echo "🛡️ Branch '$CURRENT_BRANCH' is protected. Skipping pruning." | |
| exit 0 | |
| fi | |
| done | |
| echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" | |
| echo "✂️ Smart Release Pruning" | |
| echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" | |
| echo "Configuration:" | |
| echo " • Retention Window: $RETENTION_DAYS_FULL days (Full retention)" | |
| echo " • Keep Daily Snapshot: $RETENTION_KEEP_ONE_DAILY_OLDER" | |
| echo " • Max Total Releases: $RETENTION_MAX_COUNT" | |
| echo " • Dry Run: $DRY_RUN" | |
| echo "" | |
| # Calculate Cutoff Date (YYYY-MM-DD) | |
| # We want to keep releases from Today, Yesterday, ... up to RETENTION_DAYS_FULL days ago. | |
| # So if RETENTION_DAYS_FULL is 2, we keep Today (0), 1 day ago, 2 days ago. | |
| # Anything strictly OLDER than (Current - 2 days) is candidate for pruning. | |
| CUTOFF_DATE=$(date -d "$RETENTION_DAYS_FULL days ago" +%Y-%m-%d) | |
| echo "📅 Cutoff Date: $CUTOFF_DATE (Releases older than this are subject to daily thinning)" | |
| echo "" | |
| # Fetch releases | |
| # We need tagName and createdAt. | |
| # Filter by branch prefix to be safe, though we are on the branch. | |
| # Note: gh release list lists releases for the repository. We need to filter by tag pattern. | |
| # Tag pattern: $BRANCH_NAME/build-* | |
| echo "🔍 Fetching releases for branch '$CURRENT_BRANCH'..." | |
| # Get JSON data | |
| RELEASES_JSON=$(gh release list --repo "${{ github.repository }}" --limit 1000 --json tagName,createdAt,isDraft,isPrerelease) | |
| # Process in a loop to handle logic | |
| # We will build a list of "TO_DELETE" and "KEPT" | |
| # We need to sort releases by date descending (newest first) to handle the "Max Count" logic correctly. | |
| # gh release list usually returns newest first, but let's be sure. | |
| # We'll use jq to filter and sort, then process line by line | |
| # Filter: tagName starts with "$CURRENT_BRANCH/" | |
| FILTERED_RELEASES=$(echo "$RELEASES_JSON" | jq -c --arg branch "$CURRENT_BRANCH/" --arg current_tag "$CURRENT_TAG" ' | |
| map(select(.tagName | startswith($branch))) | | |
| map(select(.tagName != $current_tag)) | | |
| sort_by(.createdAt) | reverse | |
| ') | |
| COUNT=$(echo "$FILTERED_RELEASES" | jq 'length') | |
| echo "📦 Found $COUNT historical releases (excluding current build)." | |
| if [ "$COUNT" -eq 0 ]; then | |
| echo "✅ No old releases to prune." | |
| exit 0 | |
| fi | |
| # Arrays to track status | |
| declare -a TO_DELETE | |
| declare -a KEPT_RELEASES | |
| # Associative array to track "seen days" for daily snapshot logic | |
| declare -A SEEN_DAYS | |
| # Iterate through releases (Newest to Oldest) | |
| while read -r release; do | |
| TAG=$(echo "$release" | jq -r '.tagName') | |
| CREATED_AT=$(echo "$release" | jq -r '.createdAt') | |
| # Convert ISO8601 to YYYY-MM-DD | |
| RELEASE_DATE=$(date -d "$CREATED_AT" +%Y-%m-%d) | |
| # Logic Check | |
| KEEP=false | |
| REASON="" | |
| # Check 1: Is it within the Full Retention Window? | |
| # We compare strings: If RELEASE_DATE >= CUTOFF_DATE | |
| if [[ "$RELEASE_DATE" > "$CUTOFF_DATE" ]] || [[ "$RELEASE_DATE" == "$CUTOFF_DATE" ]]; then | |
| KEEP=true | |
| REASON="Within retention window ($RETENTION_DAYS_FULL days)" | |
| else | |
| # Check 2: Daily Snapshot | |
| if [ "$RETENTION_KEEP_ONE_DAILY_OLDER" == "true" ]; then | |
| if [ -z "${SEEN_DAYS[$RELEASE_DATE]}" ]; then | |
| KEEP=true | |
| REASON="Daily snapshot for $RELEASE_DATE" | |
| SEEN_DAYS[$RELEASE_DATE]="seen" | |
| else | |
| KEEP=false | |
| REASON="Redundant build for $RELEASE_DATE" | |
| fi | |
| else | |
| KEEP=false | |
| REASON="Older than window and snapshots disabled" | |
| fi | |
| fi | |
| if [ "$KEEP" == "true" ]; then | |
| KEPT_RELEASES+=("$TAG") | |
| echo " ✅ KEEP: $TAG ($RELEASE_DATE) - $REASON" | |
| else | |
| TO_DELETE+=("$TAG") | |
| echo " ❌ PRUNE: $TAG ($RELEASE_DATE) - $REASON" | |
| fi | |
| done < <(echo "$FILTERED_RELEASES" | jq -c '.[]') | |
| echo "" | |
| echo "📊 Phase 1 Result: ${#KEPT_RELEASES[@]} kept, ${#TO_DELETE[@]} marked for pruning." | |
| # Phase 2: Max Count Cap | |
| # KEPT_RELEASES is sorted Newest -> Oldest | |
| if [ "${#KEPT_RELEASES[@]}" -gt "$RETENTION_MAX_COUNT" ]; then | |
| echo "⚠️ Total kept releases (${#KEPT_RELEASES[@]}) exceeds limit ($RETENTION_MAX_COUNT). Trimming oldest..." | |
| # The first MAX_COUNT are safe. The rest must go. | |
| # Bash array slicing: ${array[@]:start:length} | |
| # New kept list is just the first N | |
| FINAL_KEPT=("${KEPT_RELEASES[@]:0:$RETENTION_MAX_COUNT}") | |
| # The overflow are added to delete list | |
| OVERFLOW=("${KEPT_RELEASES[@]:$RETENTION_MAX_COUNT}") | |
| for tag in "${OVERFLOW[@]}"; do | |
| TO_DELETE+=("$tag") | |
| echo " ❌ PRUNE (Overflow): $tag" | |
| done | |
| KEPT_RELEASES=("${FINAL_KEPT[@]}") | |
| fi | |
| echo "" | |
| echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" | |
| echo "🗑️ Executing Deletions (${#TO_DELETE[@]} items)" | |
| echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" | |
| if [ "${#TO_DELETE[@]}" -eq 0 ]; then | |
| echo "✅ Nothing to delete." | |
| exit 0 | |
| fi | |
| for tag in "${TO_DELETE[@]}"; do | |
| if [ "$DRY_RUN" == "true" ]; then | |
| echo " [DRY RUN] Would delete: $tag" | |
| else | |
| echo " Deleting: $tag" | |
| gh release delete "$tag" --repo "${{ github.repository }}" --cleanup-tag --yes || echo " ⚠️ Failed to delete $tag" | |
| fi | |
| done | |
| echo "" | |
| echo "✅ Pruning complete." |