Ver Fonte

Refactor cache save and restore actions for improved functionality and clarity

- Updated `cache-save` action to require single cache path and key inputs, removing support for multiple paths/keys.
- Introduced new `working_dir` and `debug` inputs for enhanced flexibility and logging.
- Simplified the release creation and upload process, ensuring better error handling and user feedback.
- Added support for file compression levels and improved archive handling.
- Created a backup of the original `cache-save` action for reference.

- Added new `cache-restore` action to facilitate restoring caches from GitHub Releases.
- Implemented support for multiple cache paths/keys and fallback restore keys.
- Enhanced asset downloading with retries and improved error handling.
- Provided detailed logging and summaries for cache restoration processes.
- Created a backup of the original `cache-restore` action for reference.
TheWildJames há 3 meses atrás
pai
commit
35ad0abb5b

+ 59 - 166
.github/actions/cache-restore/action.yml

@@ -1,94 +1,45 @@
 name: 'Restore Cache'
-description: 'Restores cache from GitHub Releases. Supports both single and multiple cache paths/keys/buckets.'
 
 inputs:
   cache_path:
-    description: 'Single path where cache should be restored (use cache_paths for multiple)'
-    required: false
-    type: string
-  cache_paths:
-    description: 'Multiline list of paths where caches should be restored (one per line)'
-    required: false
+    description: 'Path where cache should be restored'
+    required: true
     type: string
-    default: ""
   cache_key:
-    description: 'Primary key to restore cache (use cache_keys for multiple)'
-    required: false
-    type: string
-  cache_keys:
-    description: 'Multiline list of primary keys to restore caches (one per line, must match cache_paths)'
-    required: false
+    description: 'Primary key to restore cache'
+    required: true
     type: string
-    default: ""
   restore_keys:
     description: 'Multiline list of keys (fallback prefixes)'
     required: false
     type: string
     default: ""
   cache_bucket:
-    description: 'Single tag name for the release (use cache_buckets for multiple)'
-    required: false
+    description: 'The tag name for the release (e.g., general-cache)'
+    required: true
     default: 'general-cache'
-  cache_buckets:
-    description: 'Multiline list of tag names for releases (one per line, must match cache_paths)'
-    required: false
-    type: string
-    default: ""
-  compression_level:
-    description: 'Compression level (0-9). 0 = No compression, 1 = Fast, 19 = Best.'
+  debug:
+    description: 'Enable Logs'
     required: false
-    type: number
-    default: 6
-
-outputs:
-  cache-hit:
-    description: 'Returns "true" if at least one cache was found and restored, "false" otherwise'
-    value: ${{ steps.restore.outputs.cache-hit }}
-  cache-key:
-    description: 'The key that matched (primary or fallback). Multiple entries joined by comma.'
-    value: ${{ steps.restore.outputs.cache-key }}
-  cache-size:
-    description: 'Size of restored cache in human-readable format. Multiple entries joined by comma.'
-    value: ${{ steps.restore.outputs.cache-size }}
+    type: boolean
+    default: false
 
 runs:
   using: 'composite'
   steps:
     - name: Detect and Download Cache
-      id: restore
       shell: bash
-      working-directory: ${{ github.workspace }}
       env:
         TARGET_REPO: "${{ github.repository }}"
       run: |
-        set -euo pipefail
-
-        # Validate and parse inputs
-        PATHS_INPUT="${{ inputs.cache_paths }}"
-        KEYS_INPUT="${{ inputs.cache_keys }}"
-        BUCKETS_INPUT="${{ inputs.cache_buckets }}"
-        
-        # If multiple inputs are empty, fall back to single inputs
-        if [ -z "$PATHS_INPUT" ]; then
-          PATHS_INPUT="${{ inputs.cache_path }}"
-        fi
-        if [ -z "$KEYS_INPUT" ]; then
-          KEYS_INPUT="${{ inputs.cache_key }}"
-        fi
-        if [ -z "$BUCKETS_INPUT" ]; then
-          BUCKETS_INPUT="${{ inputs.cache_bucket }}"
-        fi
+        # Detect and Download Cache
+        if [ "${{ inputs.debug }}" = "true" ]; then set -x; fi
         
-        # Split inputs into arrays
-        IFS=$'\n' read -rd '' -a PATHS_ARRAY <<<"$PATHS_INPUT" || true
-        IFS=$'\n' read -rd '' -a KEYS_ARRAY <<<"$KEYS_INPUT" || true
-        IFS=$'\n' read -rd '' -a BUCKETS_ARRAY <<<"$BUCKETS_INPUT" || true
+        cd "$GITHUB_WORKSPACE"
         
-        # Validate array lengths match
-        if [[ ${#PATHS_ARRAY[@]} -ne ${#KEYS_ARRAY[@]} ]] || [[ ${#PATHS_ARRAY[@]} -ne ${#BUCKETS_ARRAY[@]} ]]; then
-          echo "::error::Mismatch in array lengths: paths=${#PATHS_ARRAY[@]}, keys=${#KEYS_ARRAY[@]}, buckets=${#BUCKETS_ARRAY[@]}"
-          exit 1
-        fi
+        TAG_NAME="${{ inputs.cache_bucket }}"
+        BASE_URL="https://github.com/${TARGET_REPO}/releases/download/$TAG_NAME"
+        ASSETS_URL="https://github.com/${TARGET_REPO}/releases/expanded_assets/$TAG_NAME"
         
         ARIA2_OPTS=(
           "-x16" "-s16" "-k1M" "-j5" "--file-allocation=none"
@@ -97,53 +48,36 @@ runs:
           "--header=Connection: keep-alive"
         )
         
-        ARIA2_OPTS+=("--quiet" "--summary-interval=0" "--console-log-level=error")
+        if [ "${{ inputs.debug }}" != "true" ]; then
+          ARIA2_OPTS+=("--quiet" "--summary-interval=0" "--console-log-level=error")
+        fi
         
-        # Collect results
-        OVERALL_HIT=false
-        MATCHED_KEYS=()
-        CACHE_SIZES=()
+        echo "::group:: Fetching Asset List"
+        HTTP_RESPONSE=$(curl -sL -A "Mozilla/5.0" -w "%{http_code}" "$ASSETS_URL" -o assets.html || echo "404")
         
-        # Process each cache entry
-        for ((idx=0; idx<${#PATHS_ARRAY[@]}; idx++)); do
-          CACHE_PATH="${PATHS_ARRAY[$idx]}"
-          CACHE_KEY="${KEYS_ARRAY[$idx]}"
-          CACHE_BUCKET="${BUCKETS_ARRAY[$idx]}"
-          
-          [ -z "$CACHE_PATH" ] && continue
-          
-          TAG_NAME="$CACHE_BUCKET"
-          BASE_URL="https://github.com/${TARGET_REPO}/releases/download/$TAG_NAME"
-          ASSETS_URL="https://github.com/${TARGET_REPO}/releases/expanded_assets/$TAG_NAME"
-          
-          echo "::group:: [$((idx+1))/${#PATHS_ARRAY[@]}] Cache: $CACHE_BUCKET - Fetching Asset List"
-          HTTP_RESPONSE=$(curl -sL -A "Mozilla/5.0" -w "%{http_code}" "$ASSETS_URL" -o assets.html || echo "404")
-          
-          if [ "$HTTP_RESPONSE" -eq 200 ]; then
-            ALL_ASSETS=$(grep -oP "download/$TAG_NAME/\K[^\"' ]+" assets.html | sort -u || echo "")
-          else
-            echo "Status $HTTP_RESPONSE: Failed to fetch assets from scraper endpoint."
-            ALL_ASSETS=""
-          fi
-          rm -f assets.html
-          
-          if [ -z "$ALL_ASSETS" ]; then
-            echo "No assets found in bucket '$TAG_NAME'. Skipping."
-            echo "::endgroup::"
-            continue
-          fi
-          
+        if [ "$HTTP_RESPONSE" -eq 200 ]; then
+          ALL_ASSETS=$(grep -oP "download/$TAG_NAME/\K[^\"' ]+" assets.html | sort -u || echo "")
+        else
+          echo "Status $HTTP_RESPONSE: Failed to fetch assets from scraper endpoint."
+          ALL_ASSETS=""
+        fi
+        rm -f assets.html
+        
+        if [ -z "$ALL_ASSETS" ]; then
+          echo "No assets found in bucket '$TAG_NAME'. Skipping search."
+          echo "::endgroup::"
+        else
           echo "Successfully fetched asset list. Has $(echo "$ALL_ASSETS" | wc -l) assets."
           echo "::endgroup::"
           
-          SEARCH_LIST=$(printf "%s\n%s" "$CACHE_KEY" "${{ inputs.restore_keys }}" | sed '/^$/d')
+          SEARCH_LIST=$(printf "%s\n%s" "${{ inputs.cache_key }}" "${{ inputs.restore_keys }}" | sed '/^$/d')
           
           FOUND=false
           while read -r KEY; do
             [ -z "$KEY" ] && continue
-            echo "::group:: [$((idx+1))/${#PATHS_ARRAY[@]}] Searching Prefix: $KEY"
+            echo "::group:: Searching Prefix: $KEY"
             
-            MATCH=$(echo "$ALL_ASSETS" | grep -E "^cache-$KEY.*\.(tzst|tar)(\.part[a-z]{2})?$" | sort -r | head -n 1 || true)
+            MATCH=$(echo "$ALL_ASSETS" | grep "^cache-$KEY" | sort -r | head -n 1 || true)
             
             if [ -z "$MATCH" ]; then
                 echo "Not found."
@@ -152,7 +86,7 @@ runs:
             fi
             
             BASE_FILENAME=$(echo "$MATCH" | sed -E 's/\.(tzst|tar)(\.part[a-z]{2})?$//')
-            EXT=$(echo "$MATCH" | grep -oP '\.(tzst|tar)' | head -n 1)
+            EXT=$(echo "$MATCH" | grep -oP '\\.(tzst|tar)' | head -n 1)
             
             [[ "$MATCH" == *".part"* ]] && IS_SPLIT=true || IS_SPLIT=false
             [ "$EXT" = ".tzst" ] && COMPRESSED=true || COMPRESSED=false
@@ -161,7 +95,7 @@ runs:
             echo "✅ Hit! Restoring $BASE_FILENAME$EXT"
             echo "::endgroup::"
             
-            echo "::group:: [$((idx+1))/${#PATHS_ARRAY[@]}] Downloading Cache"
+            echo "::group:: Downloading Cache"
             
             download_asset() {
               local filename="$1"
@@ -171,28 +105,11 @@ runs:
               
               while [ $attempt -le $max_retries ]; do
                 echo "  Attempt $attempt: Downloading $filename..."
-                # Prefer gh release download (reliable and atomic) when available
-                rm -f "$filename"
-                if command -v gh >/dev/null 2>&1; then
-                  if timeout 10m gh release download "$TAG_NAME" --repo "$TARGET_REPO" --pattern "$filename" -D . >/dev/null 2>&1; then
-                    # gh writes the file with the same name into the current directory
-                    if [ -s "$filename" ]; then
-                      return 0
-                    fi
-                  fi
-                fi
-
-                # Fallback to aria2c
+                
                 if timeout 10m aria2c "${ARIA2_OPTS[@]}" --retry-wait=10 --max-tries=10 -o "$filename" "$url"; then
-                  # ensure file is non-empty
-                  if [ -s "$filename" ]; then
-                    return 0
-                  else
-                    echo "  ⚠️ Downloaded file is empty. Retrying..."
-                    rm -f "$filename"
-                  fi
+                  return 0
                 fi
-
+                
                 echo "  ⚠️ Download failed or timed out. Retrying in 5s..."
                 sleep 5
                 attempt=$((attempt + 1))
@@ -218,66 +135,42 @@ runs:
             fi
             echo "::endgroup::"
             
-            echo "::group:: [$((idx+1))/${#PATHS_ARRAY[@]}] Extracting Cache"
-            mkdir -p "$CACHE_PATH"
+            echo "::group:: Extracting Cache"
+            mkdir -p "${{ inputs.cache_path }}"
             FINAL_ARCHIVE="$BASE_FILENAME$EXT"
-            EXTRACT_DIR="$(dirname "$CACHE_PATH")"
+            EXTRACT_DIR="$(dirname "${{ inputs.cache_path }}")"
+            
+            extract_cache() {
+              local cmd="$1"
+              if [ "${{ inputs.debug }}" = "true" ]; then
+                sh -c "$cmd"
+              else
+                sh -c "$cmd" > /dev/null 2>&1
+              fi
+            }
             
             if [ "$IS_SPLIT" = "true" ]; then
               PARTS=$(ls "$FINAL_ARCHIVE.part"* | sort)
-              if [ "$COMPRESSED" = "true" ]; then
-                cat $PARTS | tar -I "zstd -d -T0" -xf - -C "$EXTRACT_DIR"
+              if [ "$COMPRESSED" = ""true" ]; then
+                extract_cache "cat $PARTS | tar -I 'zstd -d -T0' -xvf - -C '$EXTRACT_DIR'"
               else
-                cat $PARTS | tar -xf - -C "$EXTRACT_DIR"
+                extract_cache "cat $PARTS | tar -xvf - -C '$EXTRACT_DIR'"
               fi
               rm -f $PARTS
             else
               if [ "$COMPRESSED" = "true" ]; then
-                tar -I "zstd -d -T0" -xf "$FINAL_ARCHIVE" -C "$EXTRACT_DIR"
+                extract_cache "tar -I 'zstd -d -T0' -xvf '$FINAL_ARCHIVE' -C '$EXTRACT_DIR'"
               else
-                tar -xf "$FINAL_ARCHIVE" -C "$EXTRACT_DIR"
+                extract_cache "tar -xvf '$FINAL_ARCHIVE' -C '$EXTRACT_DIR'"
               fi
               rm -f "$FINAL_ARCHIVE"
             fi
             echo "✅ Restore complete."
             echo "::endgroup::"
-            
-            # Track results for this entry
-            OVERALL_HIT=true
-            MATCHED_KEYS+=("$KEY")
-            CACHE_SIZE=$(du -sh "$CACHE_PATH" 2>/dev/null | awk '{print $1}' || echo "unknown")
-            CACHE_SIZES+=("$CACHE_SIZE")
-            # Provide a concise summary of restored path (top-level + one level deep)
-            echo "--- Cache restore summary for: $CACHE_PATH ---"
-            if [ -e "$CACHE_PATH" ]; then
-              du -sh "$CACHE_PATH" || true
-              du -h --max-depth=1 "$CACHE_PATH" 2>/dev/null | sort -hr | head -n 10 || true
-              echo "Showing one-level-deep details for top entries (up to 5 each):"
-              while read -r _entry; do
-                entry_path=$(echo "$_entry" | awk '{print $2}')
-                [ -z "$entry_path" ] && continue
-                if [ "$entry_path" = "$CACHE_PATH" ]; then
-                  continue
-                fi
-                echo "-> $entry_path :"
-                du -h --max-depth=1 "$entry_path" 2>/dev/null | sort -hr | head -n 5 || true
-              done < <(du -h --max-depth=1 "$CACHE_PATH" 2>/dev/null | sort -hr | head -n 10)
-            fi
             break
           done <<< "$SEARCH_LIST"
           
           if [ "$FOUND" = "false" ]; then
-              echo "⚠️ No cache matches found for bucket '$CACHE_BUCKET'. Proceeding with fresh run."
-              MATCHED_KEYS+=("none")
-              CACHE_SIZES+=("0B")
+              echo "⚠️ No cache matches found. Proceeding with fresh run."
           fi
-        done
-        
-        # Output results
-        if [ "$OVERALL_HIT" = "true" ]; then
-          echo "cache-hit=true" >> $GITHUB_OUTPUT
-        else
-          echo "cache-hit=false" >> $GITHUB_OUTPUT
         fi
-        echo "cache-key=$(IFS=,; echo "${MATCHED_KEYS[*]}")" >> $GITHUB_OUTPUT
-        echo "cache-size=$(IFS=,; echo "${CACHE_SIZES[*]}")" >> $GITHUB_OUTPUT

+ 283 - 0
.github/actions/cache-restore/action.yml.bak

@@ -0,0 +1,283 @@
+name: 'Restore Cache'
+description: 'Restores cache from GitHub Releases. Supports both single and multiple cache paths/keys/buckets.'
+
+inputs:
+  cache_path:
+    description: 'Single path where cache should be restored (use cache_paths for multiple)'
+    required: false
+    type: string
+  cache_paths:
+    description: 'Multiline list of paths where caches should be restored (one per line)'
+    required: false
+    type: string
+    default: ""
+  cache_key:
+    description: 'Primary key to restore cache (use cache_keys for multiple)'
+    required: false
+    type: string
+  cache_keys:
+    description: 'Multiline list of primary keys to restore caches (one per line, must match cache_paths)'
+    required: false
+    type: string
+    default: ""
+  restore_keys:
+    description: 'Multiline list of keys (fallback prefixes)'
+    required: false
+    type: string
+    default: ""
+  cache_bucket:
+    description: 'Single tag name for the release (use cache_buckets for multiple)'
+    required: false
+    default: 'general-cache'
+  cache_buckets:
+    description: 'Multiline list of tag names for releases (one per line, must match cache_paths)'
+    required: false
+    type: string
+    default: ""
+  compression_level:
+    description: 'Compression level (0-9). 0 = No compression, 1 = Fast, 19 = Best.'
+    required: false
+    type: number
+    default: 6
+
+outputs:
+  cache-hit:
+    description: 'Returns "true" if at least one cache was found and restored, "false" otherwise'
+    value: ${{ steps.restore.outputs.cache-hit }}
+  cache-key:
+    description: 'The key that matched (primary or fallback). Multiple entries joined by comma.'
+    value: ${{ steps.restore.outputs.cache-key }}
+  cache-size:
+    description: 'Size of restored cache in human-readable format. Multiple entries joined by comma.'
+    value: ${{ steps.restore.outputs.cache-size }}
+
+runs:
+  using: 'composite'
+  steps:
+    - name: Detect and Download Cache
+      id: restore
+      shell: bash
+      working-directory: ${{ github.workspace }}
+      env:
+        TARGET_REPO: "${{ github.repository }}"
+      run: |
+        set -euo pipefail
+
+        # Validate and parse inputs
+        PATHS_INPUT="${{ inputs.cache_paths }}"
+        KEYS_INPUT="${{ inputs.cache_keys }}"
+        BUCKETS_INPUT="${{ inputs.cache_buckets }}"
+        
+        # If multiple inputs are empty, fall back to single inputs
+        if [ -z "$PATHS_INPUT" ]; then
+          PATHS_INPUT="${{ inputs.cache_path }}"
+        fi
+        if [ -z "$KEYS_INPUT" ]; then
+          KEYS_INPUT="${{ inputs.cache_key }}"
+        fi
+        if [ -z "$BUCKETS_INPUT" ]; then
+          BUCKETS_INPUT="${{ inputs.cache_bucket }}"
+        fi
+        
+        # Split inputs into arrays
+        IFS=$'\n' read -rd '' -a PATHS_ARRAY <<<"$PATHS_INPUT" || true
+        IFS=$'\n' read -rd '' -a KEYS_ARRAY <<<"$KEYS_INPUT" || true
+        IFS=$'\n' read -rd '' -a BUCKETS_ARRAY <<<"$BUCKETS_INPUT" || true
+        
+        # Validate array lengths match
+        if [[ ${#PATHS_ARRAY[@]} -ne ${#KEYS_ARRAY[@]} ]] || [[ ${#PATHS_ARRAY[@]} -ne ${#BUCKETS_ARRAY[@]} ]]; then
+          echo "::error::Mismatch in array lengths: paths=${#PATHS_ARRAY[@]}, keys=${#KEYS_ARRAY[@]}, buckets=${#BUCKETS_ARRAY[@]}"
+          exit 1
+        fi
+        
+        ARIA2_OPTS=(
+          "-x16" "-s16" "-k1M" "-j5" "--file-allocation=none"
+          "--header=User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
+          "--header=Accept: */*"
+          "--header=Connection: keep-alive"
+        )
+        
+        ARIA2_OPTS+=("--quiet" "--summary-interval=0" "--console-log-level=error")
+        
+        # Collect results
+        OVERALL_HIT=false
+        MATCHED_KEYS=()
+        CACHE_SIZES=()
+        
+        # Process each cache entry
+        for ((idx=0; idx<${#PATHS_ARRAY[@]}; idx++)); do
+          CACHE_PATH="${PATHS_ARRAY[$idx]}"
+          CACHE_KEY="${KEYS_ARRAY[$idx]}"
+          CACHE_BUCKET="${BUCKETS_ARRAY[$idx]}"
+          
+          [ -z "$CACHE_PATH" ] && continue
+          
+          TAG_NAME="$CACHE_BUCKET"
+          BASE_URL="https://github.com/${TARGET_REPO}/releases/download/$TAG_NAME"
+          ASSETS_URL="https://github.com/${TARGET_REPO}/releases/expanded_assets/$TAG_NAME"
+          
+          echo "::group:: [$((idx+1))/${#PATHS_ARRAY[@]}] Cache: $CACHE_BUCKET - Fetching Asset List"
+          HTTP_RESPONSE=$(curl -sL -A "Mozilla/5.0" -w "%{http_code}" "$ASSETS_URL" -o assets.html || echo "404")
+          
+          if [ "$HTTP_RESPONSE" -eq 200 ]; then
+            ALL_ASSETS=$(grep -oP "download/$TAG_NAME/\K[^\"' ]+" assets.html | sort -u || echo "")
+          else
+            echo "Status $HTTP_RESPONSE: Failed to fetch assets from scraper endpoint."
+            ALL_ASSETS=""
+          fi
+          rm -f assets.html
+          
+          if [ -z "$ALL_ASSETS" ]; then
+            echo "No assets found in bucket '$TAG_NAME'. Skipping."
+            echo "::endgroup::"
+            continue
+          fi
+          
+          echo "Successfully fetched asset list. Has $(echo "$ALL_ASSETS" | wc -l) assets."
+          echo "::endgroup::"
+          
+          SEARCH_LIST=$(printf "%s\n%s" "$CACHE_KEY" "${{ inputs.restore_keys }}" | sed '/^$/d')
+          
+          FOUND=false
+          while read -r KEY; do
+            [ -z "$KEY" ] && continue
+            echo "::group:: [$((idx+1))/${#PATHS_ARRAY[@]}] Searching Prefix: $KEY"
+            
+            MATCH=$(echo "$ALL_ASSETS" | grep -E "^cache-$KEY.*\.(tzst|tar)(\.part[a-z]{2})?$" | sort -r | head -n 1 || true)
+            
+            if [ -z "$MATCH" ]; then
+                echo "Not found."
+                echo "::endgroup::"
+                continue
+            fi
+            
+            BASE_FILENAME=$(echo "$MATCH" | sed -E 's/\.(tzst|tar)(\.part[a-z]{2})?$//')
+            EXT=$(echo "$MATCH" | grep -oP '\\.(tzst|tar)' | head -n 1)
+            
+            [[ "$MATCH" == *".part"* ]] && IS_SPLIT=true || IS_SPLIT=false
+            [ "$EXT" = ".tzst" ] && COMPRESSED=true || COMPRESSED=false
+            FOUND=true
+            
+            echo "✅ Hit! Restoring $BASE_FILENAME$EXT"
+            echo "::endgroup::"
+            
+            echo "::group:: [$((idx+1))/${#PATHS_ARRAY[@]}] Downloading Cache"
+            
+            download_asset() {
+              local filename="$1"
+              local url="$2"
+              local max_retries=3
+              local attempt=1
+              
+              while [ $attempt -le $max_retries ]; do
+                echo "  Attempt $attempt: Downloading $filename..."
+                # Prefer gh release download (reliable and atomic) when available
+                rm -f "$filename"
+                if command -v gh >/dev/null 2>&1; then
+                  if timeout 10m gh release download "$TAG_NAME" --repo "$TARGET_REPO" --pattern "$filename" -D . >/dev/null 2>&1; then
+                    # gh writes the file with the same name into the current directory
+                    if [ -s "$filename" ]; then
+                      return 0
+                    fi
+                  fi
+                fi
+
+                # Fallback to aria2c
+                if timeout 10m aria2c "${ARIA2_OPTS[@]}" --retry-wait=10 --max-tries=10 -o "$filename" "$url"; then
+                  # ensure file is non-empty
+                  if [ -s "$filename" ]; then
+                    return 0
+                  else
+                    echo "  ⚠️ Downloaded file is empty. Retrying..."
+                    rm -f "$filename"
+                  fi
+                fi
+
+                echo "  ⚠️ Download failed or timed out. Retrying in 5s..."
+                sleep 5
+                attempt=$((attempt + 1))
+              done
+              return 1
+            }
+            
+            if [ "$IS_SPLIT" = "true" ]; then
+              for part in {a..z}{a..z}; do
+                PART_NAME="$BASE_FILENAME$EXT.part$part"
+                if echo "$ALL_ASSETS" | grep -q "^$PART_NAME$"; then
+                  if ! download_asset "$PART_NAME" "$BASE_URL/$PART_NAME"; then
+                    echo "::error::Failed to download $PART_NAME after multiple retries."
+                    exit 1
+                  fi
+                else break; fi
+              done
+            else
+              if ! download_asset "$BASE_FILENAME$EXT" "$BASE_URL/$BASE_FILENAME$EXT"; then
+                echo "::error::Failed to download $BASE_FILENAME$EXT after multiple retries."
+                exit 1
+              fi
+            fi
+            echo "::endgroup::"
+            
+            echo "::group:: [$((idx+1))/${#PATHS_ARRAY[@]}] Extracting Cache"
+            mkdir -p "$CACHE_PATH"
+            FINAL_ARCHIVE="$BASE_FILENAME$EXT"
+            EXTRACT_DIR="$(dirname "$CACHE_PATH")"
+            
+            if [ "$IS_SPLIT" = "true" ]; then
+              PARTS=$(ls "$FINAL_ARCHIVE.part"* | sort)
+              if [ "$COMPRESSED" = "true" ]; then
+                cat $PARTS | tar -I "zstd -d -T0" -xf - -C "$EXTRACT_DIR"
+              else
+                cat $PARTS | tar -xf - -C "$EXTRACT_DIR"
+              fi
+              rm -f $PARTS
+            else
+              if [ "$COMPRESSED" = "true" ]; then
+                tar -I "zstd -d -T0" -xf "$FINAL_ARCHIVE" -C "$EXTRACT_DIR"
+              else
+                tar -xf "$FINAL_ARCHIVE" -C "$EXTRACT_DIR"
+              fi
+              rm -f "$FINAL_ARCHIVE"
+            fi
+            echo "✅ Restore complete."
+            echo "::endgroup::"
+            
+            # Track results for this entry
+            OVERALL_HIT=true
+            MATCHED_KEYS+=("$KEY")
+            CACHE_SIZE=$(du -sh "$CACHE_PATH" 2>/dev/null | awk '{print $1}' || echo "unknown")
+            CACHE_SIZES+=("$CACHE_SIZE")
+            # Provide a concise summary of restored path (top-level + one level deep)
+            echo "--- Cache restore summary for: $CACHE_PATH ---"
+            if [ -e "$CACHE_PATH" ]; then
+              du -sh "$CACHE_PATH" || true
+              du -h --max-depth=1 "$CACHE_PATH" 2>/dev/null | sort -hr | head -n 10 || true
+              echo "Showing one-level-deep details for top entries (up to 5 each):"
+              while read -r _entry; do
+                entry_path=$(echo "$_entry" | awk '{print $2}')
+                [ -z "$entry_path" ] && continue
+                if [ "$entry_path" = "$CACHE_PATH" ]; then
+                  continue
+                fi
+                echo "-> $entry_path :"
+                du -h --max-depth=1 "$entry_path" 2>/dev/null | sort -hr | head -n 5 || true
+              done < <(du -h --max-depth=1 "$CACHE_PATH" 2>/dev/null | sort -hr | head -n 10)
+            fi
+            break
+          done <<< "$SEARCH_LIST"
+          
+          if [ "$FOUND" = "false" ]; then
+              echo "⚠️ No cache matches found for bucket '$CACHE_BUCKET'. Proceeding with fresh run."
+              MATCHED_KEYS+=("none")
+              CACHE_SIZES+=("0B")
+          fi
+        done
+        
+        # Output results
+        if [ "$OVERALL_HIT" = "true" ]; then
+          echo "cache-hit=true" >> $GITHUB_OUTPUT
+        else
+          echo "cache-hit=false" >> $GITHUB_OUTPUT
+        fi
+        echo "cache-key=$(IFS=,; echo "${MATCHED_KEYS[*]}")" >> $GITHUB_OUTPUT
+        echo "cache-size=$(IFS=,; echo "${CACHE_SIZES[*]}")" >> $GITHUB_OUTPUT

+ 92 - 196
.github/actions/cache-save/action.yml

@@ -1,43 +1,35 @@
 name: 'Save Cache'
-description: 'Archives and uploads cache to GitHub Releases. Supports both single and multiple cache paths/keys/buckets.'
 
 inputs:
   cache_path:
-    description: 'Single path where cache is saved (use cache_paths for multiple)'
-    required: false
-    type: string
-  cache_paths:
-    description: 'Multiline list of paths where caches are saved (one per line)'
-    required: false
+    description: 'Path where cache is saved'
+    required: true
     type: string
-    default: ""
   cache_key:
-    description: 'Unique key for the cache (use cache_keys for multiple)'
-    required: false
-    type: string
-  cache_keys:
-    description: 'Multiline list of unique keys for caches (one per line, must match cache_paths)'
-    required: false
+    description: 'Unique key for the cache'
+    required: true
     type: string
-    default: ""
   cache_bucket:
-    description: 'Single tag name for the release (use cache_buckets for multiple)'
-    required: false
+    description: 'The tag name for the release (e.g., general-cache)'
+    required: true
     default: 'general-cache'
-  cache_buckets:
-    description: 'Multiline list of tag names for releases (one per line, must match cache_paths)'
-    required: false
-    type: string
-    default: ""
+  github_token:
+    description: 'GitHub Token'
+    required: true
   compression_level:
     description: 'Compression level (0-9). 0 = No compression, 1 = Fast, 19 = Best.'
     required: false
     type: number
     default: 6
-  github_token:
-    description: 'GitHub token for release upload authentication'
-    required: true
+  working_dir:
+    description: 'Path where .git folder exists (use only if you clone multiple repos)'
+    required: false
     type: string
+  debug:
+    description: 'Enable Logs'
+    required: false
+    type: boolean
+    default: false
 
 runs:
   using: 'composite'
@@ -45,214 +37,118 @@ runs:
     - name: Archive, Split, and Upload Cache
       shell: bash
       env:
-        GITHUB_TOKEN: ${{ inputs.github_token }}
         GH_TOKEN: ${{ inputs.github_token }}
         TARGET_REPO: "${{ github.repository }}"
       run: |
-        set -euo pipefail
-
-        # Validate and parse inputs
-        PATHS_INPUT="${{ inputs.cache_paths }}"
-        KEYS_INPUT="${{ inputs.cache_keys }}"
-        BUCKETS_INPUT="${{ inputs.cache_buckets }}"
+        # Archive, Split, and Upload Cache
+        if [ "${{ inputs.debug }}" = "true" ]; then set -x; fi
         
-        # If multiple inputs are empty, fall back to single inputs
-        if [ -z "$PATHS_INPUT" ]; then
-          PATHS_INPUT="${{ inputs.cache_path }}"
-        fi
-        if [ -z "$KEYS_INPUT" ]; then
-          KEYS_INPUT="${{ inputs.cache_key }}"
-        fi
-        if [ -z "$BUCKETS_INPUT" ]; then
-          BUCKETS_INPUT="${{ inputs.cache_bucket }}"
+        if [ "${{ inputs.working_dir }}" != "" ]; then 
+          cd "${{ inputs.working_dir }}"
         fi
+        git config user.name "github-actions[bot]"
+        git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
         
-        # Split inputs into arrays
-        IFS=$'\n' read -rd '' -a PATHS_ARRAY <<<"$PATHS_INPUT" || true
-        IFS=$'\n' read -rd '' -a KEYS_ARRAY <<<"$KEYS_INPUT" || true
-        IFS=$'\n' read -rd '' -a BUCKETS_ARRAY <<<"$BUCKETS_INPUT" || true
+        TAG_NAME="${{ inputs.cache_bucket }}"
+        BASE_FILENAME="cache-${{ inputs.cache_key }}"
         
-        # Validate array lengths match
-        if [[ ${#PATHS_ARRAY[@]} -ne ${#KEYS_ARRAY[@]} ]] || [[ ${#PATHS_ARRAY[@]} -ne ${#BUCKETS_ARRAY[@]} ]]; then
-          echo "::error::Mismatch in array lengths: paths=${#PATHS_ARRAY[@]}, keys=${#KEYS_ARRAY[@]}, buckets=${#BUCKETS_ARRAY[@]}"
-          exit 1
+        if [ "${{ inputs.compression_level }}" -eq 0 ]; then
+          FILENAME="$BASE_FILENAME.tar"
+        else
+          FILENAME="$BASE_FILENAME.tzst"
         fi
         
-        # Git configuration for releases
-        git config user.name "github-actions[bot]"
-        git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
-
-        TARGET_COMMIT=$(git rev-parse HEAD)
+        echo "::group:: Checking Release State"
         
-        # Helper function to handle release creation
-        ensure_release() {
-          local tag_name="$1"
-          local attempts=0
-          
-          if gh release view "$tag_name" --repo "$TARGET_REPO" >/dev/null 2>&1; then
-            echo "Release '$tag_name' already exists. Ready for upload."
-            gh release edit "$tag_name" --latest=false --repo "$TARGET_REPO" >/dev/null 2>&1 || true
-            return 0
-          fi
-
-          echo "Tag '$tag_name' not found or release is missing. Creating release..."
-          gh release create "$tag_name" \
-            --target "$TARGET_COMMIT" \
-            --title "Build Cache" \
-            --notes "Automated storage for build assets" \
-            --latest=false \
-            --repo "$TARGET_REPO"
-
-          until gh release view "$tag_name" --repo "$TARGET_REPO" >/dev/null 2>&1; do
-            attempts=$((attempts + 1))
-            if [ "$attempts" -ge 5 ]; then
-              echo "::error::Release '$tag_name' is still not visible after creation."
-              return 1
-            fi
-            sleep 3
-          done
-
-          gh release edit "$tag_name" --latest=false --repo "$TARGET_REPO" >/dev/null 2>&1 || true
-        }
+        git fetch --tags --force
         
-        upload_with_retry() {
-          local file=$1
-          local tag_name=$2
-          local max_attempts=3
-          local timeout_duration="30m"  # Increased timeout for large files
-          
-          for ((i=1; i<=max_attempts; i++)); do
-            echo "  Attempt $i for $file..."
-            
-            if timeout $timeout_duration gh release upload "$tag_name" "$file" --clobber --repo "$TARGET_REPO"; then
-              echo "  ✅ Successfully uploaded $file"
-              return 0
-            else
-              local exit_code=$?
-              if [ $exit_code -eq 124 ]; then
-                echo "  ⚠ Attempt $i timed out (timeout after $timeout_duration). Retrying..."
-              else
-                echo "  ⚠ Attempt $i failed with exit code $exit_code. Retrying..."
-              fi
-            fi
-            
-            if [ $i -lt $max_attempts ]; then
-              sleep 5
-            fi
-          done
-          return 1
-        }
-        
-        # Process each cache entry
-        for ((idx=0; idx<${#PATHS_ARRAY[@]}; idx++)); do
-          CACHE_PATH="${PATHS_ARRAY[$idx]}"
-          CACHE_KEY="${KEYS_ARRAY[$idx]}"
-          CACHE_BUCKET="${BUCKETS_ARRAY[$idx]}"
-          
-          [ -z "$CACHE_PATH" ] && continue
-          
-          TAG_NAME="$CACHE_BUCKET"
-          BASE_FILENAME="cache-$CACHE_KEY"
-          
-          if [ "${{ inputs.compression_level }}" -eq 0 ]; then
-            FILENAME="$BASE_FILENAME.tar"
+        if ! git rev-parse "$TAG_NAME" >/dev/null 2>&1; then
+          echo "Tag '$TAG_NAME' not found. Creating backdated release..."
+          GIT_COMMITTER_DATE="2015-01-01T12:00:00" git tag -a "$TAG_NAME" -m "General Cache Storage"
+          if git push origin "$TAG_NAME" --force; then
+            gh release create "$TAG_NAME" --title "General Cache" --notes "Automated storage for build assets" --repo "$TARGET_REPO" || true
           else
-            FILENAME="$BASE_FILENAME.tzst"
+            echo "Push failed, tag might have been created by a parallel job. Continuing..."
           fi
-          
-          echo "::group:: [$((idx+1))/${#PATHS_ARRAY[@]}] Checking Release State ($CACHE_BUCKET)"
-          ensure_release "$TAG_NAME"
+        else
+          echo "Release '$TAG_NAME' already exists. Ready for upload."
+        fi
+        echo "::endgroup::"
+        
+        echo "::group:: Archiving Path"
+        echo "Target: ${{ inputs.cache_path }}"
+        
+        if [ ! -d "${{ inputs.cache_path }}" ] && [ ! -f "${{ inputs.cache_path }}" ]; then
+          echo "⚠️ Target path not found. Skipping cache save."
           echo "::endgroup::"
+        else
+          DIR_NAME=$(dirname "${{ inputs.cache_path }}")
+          BASE_NAME=$(basename "${{ inputs.cache_path }}")
           
-          echo "::group:: [$((idx+1))/${#PATHS_ARRAY[@]}] Archiving Path: $CACHE_PATH"
-          
-          if [ ! -d "$CACHE_PATH" ] && [ ! -f "$CACHE_PATH" ]; then
-            echo "⚠️ Target path not found. Skipping cache save."
-            echo "::endgroup::"
-            continue
-          fi
-
-          # Print concise summary: total size, top-level entries, and one-level-deep for largest entries
-          summarize_dir() {
-            local path="$1"
-            local top_n=${2:-10}
-            local sub_n=${3:-5}
-            if [ ! -e "$path" ]; then
-              echo "Path $path not found"
-              return
+          if [ "${{ inputs.debug }}" = "true" ]; then
+            if [ "${{ inputs.compression_level }}" -eq 0 ]; then
+              tar --posix -h -cvf "$FILENAME" -C "$DIR_NAME" "$BASE_NAME"
+            else
+              tar --posix -h -I "zstd -T0 -${{ inputs.compression_level }}" -cvf "$FILENAME" -C "$DIR_NAME" "$BASE_NAME"
             fi
-            echo "--- Cache save summary for: $path ---"
-            du -sh "$path" || true
-            du -h --max-depth=1 "$path" 2>/dev/null | sort -hr | head -n $top_n || true
-            echo "Showing one-level-deep details for top entries (up to $sub_n each):"
-            while read -r line; do
-              entry_path=$(echo "$line" | awk '{print $2}')
-              [ -z "$entry_path" ] && continue
-              if [ "$entry_path" = "$path" ]; then
-                continue
-              fi
-              echo "-> $entry_path :"
-              du -h --max-depth=1 "$entry_path" 2>/dev/null | sort -hr | head -n $sub_n || true
-            done < <(du -h --max-depth=1 "$path" 2>/dev/null | sort -hr | head -n $top_n)
-            echo "-------------------------------------"
-          }
-
-          summarize_dir "$CACHE_PATH" 10 5 || true
-          
-          DIR_NAME=$(dirname "$CACHE_PATH")
-          BASE_NAME=$(basename "$CACHE_PATH")
-          
-          if [ "${{ inputs.compression_level }}" -eq 0 ]; then
-            tar --posix -h -cvf "$FILENAME" -C "$DIR_NAME" "$BASE_NAME" > /dev/null 2>&1
           else
-            tar --posix -h -I "zstd -T0 -${{ inputs.compression_level }}" -cvf "$FILENAME" -C "$DIR_NAME" "$BASE_NAME" > /dev/null 2>&1
+            if [ "${{ inputs.compression_level }}" -eq 0 ]; then
+              tar --posix -h -cvf "$FILENAME" -C "$DIR_NAME" "$BASE_NAME" > /dev/null 2>&1
+            else
+              tar --posix -h -I "zstd -T0 -${{ inputs.compression_level }}" -cvf "$FILENAME" -C "$DIR_NAME" "$BASE_NAME" > /dev/null 2>&1
+            fi
           fi
           
           FILE_SIZE=$(stat -c%s "$FILENAME")
           echo "Archive created: $FILENAME ($FILE_SIZE bytes)"
           echo "::endgroup::"
           
-          echo "::group:: [$((idx+1))/${#PATHS_ARRAY[@]}] Uploading Assets ($CACHE_BUCKET)"
+          echo "::group:: Uploading Assets"
           MAX_SIZE=2000000000 # ~1.86GB
           
-          # Check if cache has changed by comparing with previous release asset
-          SKIP_UPLOAD=false
-          if gh release view "$TAG_NAME" --repo "$TARGET_REPO" >/dev/null 2>&1; then
-            echo "Checking if cache has changed..."
-            # Try to get the size of the previous asset with the same name
-            PREV_SIZE=$(gh release view "$TAG_NAME" --repo "$TARGET_REPO" --json assets --jq ".assets[] | select(.name == \"$FILENAME\") | .size" 2>/dev/null || echo "0")
-            if [ "$PREV_SIZE" = "$FILE_SIZE" ]; then
-              echo "⚠️ Cache size unchanged ($FILE_SIZE bytes). Skipping upload to save bandwidth."
-              SKIP_UPLOAD=true
-            fi
-          fi
-          
-          if [ "$SKIP_UPLOAD" = "true" ]; then
-            rm -f "$FILENAME"
-            echo "::endgroup::"
-            continue
-          fi
+          upload_with_retry() {
+            local file=$1
+            local max_attempts=3
+            for ((i=1; i<=max_attempts; i++)); do
+              echo "  Attempt $i for $file..."
+              
+              if [ "${{ inputs.debug }}" = "true" ]; then
+                if timeout 10m gh release upload "$TAG_NAME" "$file" --clobber --repo "$TARGET_REPO"; then
+                  echo "  Successfully uploaded $file"
+                  return 0
+                fi
+              else
+                if timeout 10m gh release upload "$TAG_NAME" "$file" --clobber --repo "$TARGET_REPO" > /dev/null 2>&1; then
+                  echo "  Successfully uploaded $file"
+                  return 0
+                fi
+              fi
+              
+              echo "  ⚠ Attempt $i failed or timed out. Retrying..."
+              sleep 5
+            done
+            return 1
+          }
           
           if [ "$FILE_SIZE" -gt "$MAX_SIZE" ]; then
-            echo "⚠️ File > 2GB. Splitting into 1.5GB chunks..."
+            echo "⚠ File > 2GB. Splitting..."
             split -b 1500M -a 2 "$FILENAME" "${FILENAME}.part"
             for part in "${FILENAME}".part*; do
-              echo "  Uploading $part..."
-              if ! upload_with_retry "$part" "$TAG_NAME"; then
-                echo "::error::Failed to upload part $part after 3 attempts."
+              echo "Uploading $part..."
+              if ! upload_with_retry "$part"; then
+                echo "::error::Failed to upload part $part after multiple attempts."
                 exit 1
               fi
               rm -f "$part"
             done
             rm -rf "$FILENAME"
           else
-            echo "  File size: $((FILE_SIZE / 1024 / 1024))MB"
-            echo "  Uploading $FILENAME..."
-            if ! upload_with_retry "$FILENAME" "$TAG_NAME"; then
-              echo "::error::Failed to upload $FILENAME after 3 attempts."
+            echo "Uploading $FILENAME..."
+            if ! upload_with_retry "$FILENAME"; then
+              echo "::error::Failed to upload $FILENAME after multiple attempts."
               exit 1
             fi
             rm -f "$FILENAME"
           fi
           echo "::endgroup::"
-        done
+        fi

+ 258 - 0
.github/actions/cache-save/action.yml.bak

@@ -0,0 +1,258 @@
+name: 'Save Cache'
+description: 'Archives and uploads cache to GitHub Releases. Supports both single and multiple cache paths/keys/buckets.'
+
+inputs:
+  cache_path:
+    description: 'Single path where cache is saved (use cache_paths for multiple)'
+    required: false
+    type: string
+  cache_paths:
+    description: 'Multiline list of paths where caches are saved (one per line)'
+    required: false
+    type: string
+    default: ""
+  cache_key:
+    description: 'Unique key for the cache (use cache_keys for multiple)'
+    required: false
+    type: string
+  cache_keys:
+    description: 'Multiline list of unique keys for caches (one per line, must match cache_paths)'
+    required: false
+    type: string
+    default: ""
+  cache_bucket:
+    description: 'Single tag name for the release (use cache_buckets for multiple)'
+    required: false
+    default: 'general-cache'
+  cache_buckets:
+    description: 'Multiline list of tag names for releases (one per line, must match cache_paths)'
+    required: false
+    type: string
+    default: ""
+  compression_level:
+    description: 'Compression level (0-9). 0 = No compression, 1 = Fast, 19 = Best.'
+    required: false
+    type: number
+    default: 6
+  github_token:
+    description: 'GitHub token for release upload authentication'
+    required: true
+    type: string
+
+runs:
+  using: 'composite'
+  steps:
+    - name: Archive, Split, and Upload Cache
+      shell: bash
+      env:
+        GITHUB_TOKEN: ${{ inputs.github_token }}
+        GH_TOKEN: ${{ inputs.github_token }}
+        TARGET_REPO: "${{ github.repository }}"
+      run: |
+        set -euo pipefail
+
+        # Validate and parse inputs
+        PATHS_INPUT="${{ inputs.cache_paths }}"
+        KEYS_INPUT="${{ inputs.cache_keys }}"
+        BUCKETS_INPUT="${{ inputs.cache_buckets }}"
+        
+        # If multiple inputs are empty, fall back to single inputs
+        if [ -z "$PATHS_INPUT" ]; then
+          PATHS_INPUT="${{ inputs.cache_path }}"
+        fi
+        if [ -z "$KEYS_INPUT" ]; then
+          KEYS_INPUT="${{ inputs.cache_key }}"
+        fi
+        if [ -z "$BUCKETS_INPUT" ]; then
+          BUCKETS_INPUT="${{ inputs.cache_bucket }}"
+        fi
+        
+        # Split inputs into arrays
+        IFS=$'\n' read -rd '' -a PATHS_ARRAY <<<"$PATHS_INPUT" || true
+        IFS=$'\n' read -rd '' -a KEYS_ARRAY <<<"$KEYS_INPUT" || true
+        IFS=$'\n' read -rd '' -a BUCKETS_ARRAY <<<"$BUCKETS_INPUT" || true
+        
+        # Validate array lengths match
+        if [[ ${#PATHS_ARRAY[@]} -ne ${#KEYS_ARRAY[@]} ]] || [[ ${#PATHS_ARRAY[@]} -ne ${#BUCKETS_ARRAY[@]} ]]; then
+          echo "::error::Mismatch in array lengths: paths=${#PATHS_ARRAY[@]}, keys=${#KEYS_ARRAY[@]}, buckets=${#BUCKETS_ARRAY[@]}"
+          exit 1
+        fi
+        
+        # Git configuration for releases
+        git config user.name "github-actions[bot]"
+        git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
+
+        TARGET_COMMIT=$(git rev-parse HEAD)
+        
+        # Helper function to handle release creation
+        ensure_release() {
+          local tag_name="$1"
+          local attempts=0
+          
+          if gh release view "$tag_name" --repo "$TARGET_REPO" >/dev/null 2>&1; then
+            echo "Release '$tag_name' already exists. Ready for upload."
+            gh release edit "$tag_name" --latest=false --repo "$TARGET_REPO" >/dev/null 2>&1 || true
+            return 0
+          fi
+
+          echo "Tag '$tag_name' not found or release is missing. Creating release..."
+          gh release create "$tag_name" \
+            --target "$TARGET_COMMIT" \
+            --title "Build Cache" \
+            --notes "Automated storage for build assets" \
+            --latest=false \
+            --repo "$TARGET_REPO"
+
+          until gh release view "$tag_name" --repo "$TARGET_REPO" >/dev/null 2>&1; do
+            attempts=$((attempts + 1))
+            if [ "$attempts" -ge 5 ]; then
+              echo "::error::Release '$tag_name' is still not visible after creation."
+              return 1
+            fi
+            sleep 3
+          done
+
+          gh release edit "$tag_name" --latest=false --repo "$TARGET_REPO" >/dev/null 2>&1 || true
+        }
+        
+        upload_with_retry() {
+          local file=$1
+          local tag_name=$2
+          local max_attempts=3
+          local timeout_duration="30m"  # Increased timeout for large files
+          
+          for ((i=1; i<=max_attempts; i++)); do
+            echo "  Attempt $i for $file..."
+            
+            if timeout $timeout_duration gh release upload "$tag_name" "$file" --clobber --repo "$TARGET_REPO"; then
+              echo "  ✅ Successfully uploaded $file"
+              return 0
+            else
+              local exit_code=$?
+              if [ $exit_code -eq 124 ]; then
+                echo "  ⚠ Attempt $i timed out (timeout after $timeout_duration). Retrying..."
+              else
+                echo "  ⚠ Attempt $i failed with exit code $exit_code. Retrying..."
+              fi
+            fi
+            
+            if [ $i -lt $max_attempts ]; then
+              sleep 5
+            fi
+          done
+          return 1
+        }
+        
+        # Process each cache entry
+        for ((idx=0; idx<${#PATHS_ARRAY[@]}; idx++)); do
+          CACHE_PATH="${PATHS_ARRAY[$idx]}"
+          CACHE_KEY="${KEYS_ARRAY[$idx]}"
+          CACHE_BUCKET="${BUCKETS_ARRAY[$idx]}"
+          
+          [ -z "$CACHE_PATH" ] && continue
+          
+          TAG_NAME="$CACHE_BUCKET"
+          BASE_FILENAME="cache-$CACHE_KEY"
+          
+          if [ "${{ inputs.compression_level }}" -eq 0 ]; then
+            FILENAME="$BASE_FILENAME.tar"
+          else
+            FILENAME="$BASE_FILENAME.tzst"
+          fi
+          
+          echo "::group:: [$((idx+1))/${#PATHS_ARRAY[@]}] Checking Release State ($CACHE_BUCKET)"
+          ensure_release "$TAG_NAME"
+          echo "::endgroup::"
+          
+          echo "::group:: [$((idx+1))/${#PATHS_ARRAY[@]}] Archiving Path: $CACHE_PATH"
+          
+          if [ ! -d "$CACHE_PATH" ] && [ ! -f "$CACHE_PATH" ]; then
+            echo "⚠️ Target path not found. Skipping cache save."
+            echo "::endgroup::"
+            continue
+          fi
+
+          # Print concise summary: total size, top-level entries, and one-level-deep for largest entries
+          summarize_dir() {
+            local path="$1"
+            local top_n=${2:-10}
+            local sub_n=${3:-5}
+            if [ ! -e "$path" ]; then
+              echo "Path $path not found"
+              return
+            fi
+            echo "--- Cache save summary for: $path ---"
+            du -sh "$path" || true
+            du -h --max-depth=1 "$path" 2>/dev/null | sort -hr | head -n $top_n || true
+            echo "Showing one-level-deep details for top entries (up to $sub_n each):"
+            while read -r line; do
+              entry_path=$(echo "$line" | awk '{print $2}')
+              [ -z "$entry_path" ] && continue
+              if [ "$entry_path" = "$path" ]; then
+                continue
+              fi
+              echo "-> $entry_path :"
+              du -h --max-depth=1 "$entry_path" 2>/dev/null | sort -hr | head -n $sub_n || true
+            done < <(du -h --max-depth=1 "$path" 2>/dev/null | sort -hr | head -n $top_n)
+            echo "-------------------------------------"
+          }
+
+          summarize_dir "$CACHE_PATH" 10 5 || true
+          
+          DIR_NAME=$(dirname "$CACHE_PATH")
+          BASE_NAME=$(basename "$CACHE_PATH")
+          
+          if [ "${{ inputs.compression_level }}" -eq 0 ]; then
+            tar --posix -h -cvf "$FILENAME" -C "$DIR_NAME" "$BASE_NAME" > /dev/null 2>&1
+          else
+            tar --posix -h -I "zstd -T0 -${{ inputs.compression_level }}" -cvf "$FILENAME" -C "$DIR_NAME" "$BASE_NAME" > /dev/null 2>&1
+          fi
+          
+          FILE_SIZE=$(stat -c%s "$FILENAME")
+          echo "Archive created: $FILENAME ($FILE_SIZE bytes)"
+          echo "::endgroup::"
+          
+          echo "::group:: [$((idx+1))/${#PATHS_ARRAY[@]}] Uploading Assets ($CACHE_BUCKET)"
+          MAX_SIZE=2000000000 # ~1.86GB
+          
+          # Check if cache has changed by comparing with previous release asset
+          SKIP_UPLOAD=false
+          if gh release view "$TAG_NAME" --repo "$TARGET_REPO" >/dev/null 2>&1; then
+            echo "Checking if cache has changed..."
+            # Try to get the size of the previous asset with the same name
+            PREV_SIZE=$(gh release view "$TAG_NAME" --repo "$TARGET_REPO" --json assets --jq ".assets[] | select(.name == \"$FILENAME\") | .size" 2>/dev/null || echo "0")
+            if [ "$PREV_SIZE" = "$FILE_SIZE" ]; then
+              echo "⚠️ Cache size unchanged ($FILE_SIZE bytes). Skipping upload to save bandwidth."
+              SKIP_UPLOAD=true
+            fi
+          fi
+          
+          if [ "$SKIP_UPLOAD" = "true" ]; then
+            rm -f "$FILENAME"
+            echo "::endgroup::"
+            continue
+          fi
+          
+          if [ "$FILE_SIZE" -gt "$MAX_SIZE" ]; then
+            echo "⚠️ File > 2GB. Splitting into 1.5GB chunks..."
+            split -b 1500M -a 2 "$FILENAME" "${FILENAME}.part"
+            for part in "${FILENAME}".part*; do
+              echo "  Uploading $part..."
+              if ! upload_with_retry "$part" "$TAG_NAME"; then
+                echo "::error::Failed to upload part $part after 3 attempts."
+                exit 1
+              fi
+              rm -f "$part"
+            done
+            rm -rf "$FILENAME"
+          else
+            echo "  File size: $((FILE_SIZE / 1024 / 1024))MB"
+            echo "  Uploading $FILENAME..."
+            if ! upload_with_retry "$FILENAME" "$TAG_NAME"; then
+              echo "::error::Failed to upload $FILENAME after 3 attempts."
+              exit 1
+            fi
+            rm -f "$FILENAME"
+          fi
+          echo "::endgroup::"
+        done