Przeglądaj źródła

Split cache-save into cache-archive and cache-upload sub-actions

TheWildJames 1 miesiąc temu
rodzic
commit
d125d1aef1

+ 147 - 0
.github/actions/cache-archive/action.yml

@@ -0,0 +1,147 @@
+name: 'Archive Cache'
+description: 'Create cache archive and prepare for upload'
+inputs:
+  cache_path:
+    description: 'Path where cache is saved'
+    required: true
+    type: string
+  cache_key:
+    description: 'Unique key for the cache'
+    required: true
+    type: string
+  cache_bucket:
+    description: 'The tag name for the release'
+    required: true
+    default: 'general-cache'
+  compression_level:
+    description: 'Compression level (0-9)'
+    required: false
+    type: number
+    default: 6
+  working_dir:
+    description: 'Path where .git folder exists'
+    required: false
+    type: string
+  debug:
+    description: 'Enable Logs'
+    required: false
+    type: boolean
+    default: false
+
+runs:
+  using: 'composite'
+  steps:
+    - name: Archive and Split Cache
+      shell: bash
+      env:
+        GH_TOKEN: ${{ inputs.github_token }}
+        TARGET_REPO: "${{ github.repository }}"
+      run: |
+        set -euo pipefail
+
+        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"
+
+        TAG_NAME="${{ inputs.cache_bucket }}"
+        BASE_FILENAME="cache-${{ inputs.cache_key }}"
+
+        if [ "${{ inputs.compression_level }}" -eq 0 ]; then
+          FILENAME="$BASE_FILENAME.tar"
+        else
+          FILENAME="$BASE_FILENAME.tzst"
+        fi
+
+        # Check release state
+        git fetch --tags --force
+
+        if ! git rev-parse "$TAG_NAME" >/dev/null 2>&1; then
+          echo "Creating release: $TAG_NAME"
+          GIT_COMMITTER_DATE="2015-01-01T12:00:00" git tag -a "$TAG_NAME" -m "General Cache Storage"
+          git push origin "$TAG_NAME" --force || true
+          gh release create "$TAG_NAME" --title "General Cache" --notes "Automated storage for build assets" --repo "$TARGET_REPO" || true
+        fi
+
+        # Check if path exists
+        if [ ! -d "${{ inputs.cache_path }}" ] && [ ! -f "${{ inputs.cache_path }}" ]; then
+          echo "⚠️ Target path not found. Skipping cache save."
+          exit 0
+        fi
+
+        DIR_NAME=$(dirname "${{ inputs.cache_path }}")
+        BASE_NAME=$(basename "${{ inputs.cache_path }}")
+
+        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
+        else
+          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)"
+
+        # Determine if splitting is needed
+        MAX_SIZE=2000000000
+        chunk_size="1500M"
+        split_required=false
+
+        if [ "$FILE_SIZE" -gt "$MAX_SIZE" ]; then
+          split_required=true
+        fi
+
+        if [ "$split_required" = "true" ]; then
+          echo "Splitting into ${chunk_size} chunks..."
+          split -b "$chunk_size" -a 2 "$FILENAME" "${FILENAME}.part"
+          NEW_PARTS=(${FILENAME}.part*)
+          NEW_PARTS_COUNT=${#NEW_PARTS[@]}
+          echo "Split complete, $NEW_PARTS_COUNT parts created"
+
+          # Remove old single-file asset if switching to split parts
+          ALL_ASSETS=$(curl -sL -A "Mozilla/5.0" -w "%{http_code}" "https://github.com/${TARGET_REPO}/releases/expanded_assets/$TAG_NAME" -o assets.html 2>/dev/null || echo "0")
+          if [ "$ALL_ASSETS" = "200" ]; then
+            ALL_ASSETS=$(grep -oP "download/$TAG_NAME/\K[\"' ]+" assets.html 2>/dev/null || echo "")
+            if echo "$ALL_ASSETS" | grep -xF "${FILENAME}" >/dev/null 2>&1; then
+              gh release delete-asset "$TAG_NAME" "${FILENAME}" --repo "$TARGET_REPO" -y || true
+            fi
+            # Clean up excess trailing parts from previous builds
+            OLD_PARTS=$(echo "$ALL_ASSETS" | grep -F "${FILENAME}.part" 2>/dev/null || echo "")
+            if [ -n "$OLD_PARTS" ]; then
+              OLD_PARTS_COUNT=$(echo "$OLD_PARTS" | wc -l)
+              if [ "$OLD_PARTS_COUNT" -gt "$NEW_PARTS_COUNT" ]; then
+                EXT_PARTS=$(echo "$OLD_PARTS" | awk -v skip="$NEW_PARTS_COUNT" 'NR>skip')
+                for asset in $EXT_PARTS; do
+                  gh release delete-asset "$TAG_NAME" "$asset" --repo "$TARGET_REPO" -y || true
+                done
+              fi
+            fi
+          fi
+          rm -f assets.html
+        else
+          # Remove old split parts if new build fits in a single file
+          ALL_ASSETS=$(curl -sL -A "Mozilla/5.0" -w "%{http_code}" "https://github.com/${TARGET_REPO}/releases/expanded_assets/$TAG_NAME" -o assets.html 2>/dev/null || echo "0")
+          if [ "$ALL_ASSETS" = "200" ]; then
+            OLD_PARTS=$(grep -F "${FILENAME}.part" assets.html 2>/dev/null || echo "")
+            if [ -n "$OLD_PARTS" ]; then
+              while read -r asset; do
+                [ -z "$asset" ] && continue
+                gh release delete-asset "$TAG_NAME" "$asset" --repo "$TARGET_REPO" -y || true
+              done <<< "$OLD_PARTS"
+            fi
+          fi
+          rm -f assets.html
+        fi
+
+        echo "CACHE_FILE=$FILENAME" >> $GITHUB_OUTPUT
+        echo "CACHE_SPLIT=$split_required" >> $GITHUB_OUTPUT
+        echo "CACHE_PARTS=${NEW_PARTS_COUNT:-1}" >> $GITHUB_OUTPUT

+ 19 - 265
.github/actions/cache-save/action.yml

@@ -1,6 +1,5 @@
 name: 'Save Cache'
-description: ''
-
+description: 'Archive and upload cache to GitHub Releases'
 inputs:
   cache_path:
     description: 'Path where cache is saved'
@@ -35,266 +34,21 @@ inputs:
 runs:
   using: 'composite'
   steps:
-    - name: Archive, Split, and Upload Cache
-      shell: bash
-      env:
-        GH_TOKEN: ${{ inputs.github_token }}
-        TARGET_REPO: "${{ github.repository }}"
-      run: |
-        set -euo pipefail
-
-        # Archive, Split, and Upload Cache
-        echo "🔍 DEBUG: cache-save action started"
-        echo "🔍 DEBUG: inputs.cache_path=${{ inputs.cache_path }}"
-        echo "🔍 DEBUG: inputs.cache_key=${{ inputs.cache_key }}"
-        echo "🔍 DEBUG: inputs.cache_bucket=${{ inputs.cache_bucket }}"
-        echo "🔍 DEBUG: inputs.compression_level=${{ inputs.compression_level }}"
-        echo "🔍 DEBUG: inputs.working_dir=${{ inputs.working_dir }}"
-        echo "🔍 DEBUG: inputs.debug=${{ inputs.debug }}"
-        echo "🔍 DEBUG: GITHUB_WORKSPACE=$GITHUB_WORKSPACE"
-        echo "🔍 DEBUG: TARGET_REPO=$TARGET_REPO"
-
-        if [ "${{ inputs.working_dir }}" != "" ]; then
-          echo "🔍 DEBUG: Changing to working_dir: ${{ inputs.working_dir }}"
-          cd "${{ inputs.working_dir }}"
-          echo "🔍 DEBUG: Current directory after change: $(pwd)"
-        else
-          echo "🔍 DEBUG: No working_dir specified, using GITHUB_WORKSPACE"
-        fi
-        git config user.name "github-actions[bot]"
-        git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
-        echo "🔍 DEBUG: Git config set for github-actions[bot]"
-
-        TAG_NAME="${{ inputs.cache_bucket }}"
-        BASE_FILENAME="cache-${{ inputs.cache_key }}"
-
-        if [ "${{ inputs.compression_level }}" -eq 0 ]; then
-          FILENAME="$BASE_FILENAME.tar"
-        else
-          FILENAME="$BASE_FILENAME.tzst"
-        fi
-
-        echo "🔍 DEBUG: TAG_NAME=$TAG_NAME"
-        echo "🔍 DEBUG: BASE_FILENAME=$BASE_FILENAME"
-        echo "🔍 DEBUG: FILENAME=$FILENAME"
-        echo "🔍 DEBUG: compression_level=${{ inputs.compression_level }}"
-
-        echo "::group:: Checking Release State"
-        echo "🔍 DEBUG: Checking if git tag '$TAG_NAME' exists"
-
-        git fetch --tags --force
-        echo "🔍 DEBUG: Git tags fetched successfully"
-
-        if ! git rev-parse "$TAG_NAME" >/dev/null 2>&1; then
-          echo "🔍 DEBUG: Tag '$TAG_NAME' not found, creating new release"
-          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"
-          echo "🔍 DEBUG: Git tag created, attempting push"
-          if git push origin "$TAG_NAME" --force; then
-            echo "🔍 DEBUG: Push successful, creating GitHub release"
-            gh release create "$TAG_NAME" --title "General Cache" --notes "Automated storage for build assets" --repo "$TARGET_REPO" || true
-          else
-            echo "🔍 DEBUG: Push failed (may be parallel job conflict)"
-            echo "Push failed, tag might have been created by a parallel job. Continuing..."
-          fi
-        else
-          echo "🔍 DEBUG: Tag '$TAG_NAME' already exists"
-          echo "Release '$TAG_NAME' already exists. Ready for upload."
-        fi
-        echo "::endgroup::"
-        echo "🔍 DEBUG: Release state check complete"
-
-        echo "::group:: Archiving Path"
-        echo "🔍 DEBUG: Archiving process started"
-        echo "Target: ${{ inputs.cache_path }}"
-        echo "🔍 DEBUG: Checking if target path exists"
-
-        if [ ! -d "${{ inputs.cache_path }}" ] && [ ! -f "${{ inputs.cache_path }}" ]; then
-          echo "🔍 DEBUG: Target path does not exist"
-          echo "⚠️ Target path not found. Skipping cache save."
-          echo "::endgroup::"
-        else
-          echo "🔍 DEBUG: Target path exists, proceeding with archive"
-          DIR_NAME=$(dirname "${{ inputs.cache_path }}")
-          BASE_NAME=$(basename "${{ inputs.cache_path }}")
-          echo "🔍 DEBUG: DIR_NAME=$DIR_NAME"
-          echo "🔍 DEBUG: BASE_NAME=$BASE_NAME"
-
-          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
-          else
-            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:: Uploading Assets"
-          echo "🔍 DEBUG: Upload process started"
-          MAX_SIZE=2000000000 # ~1.86GB
-          echo "🔍 DEBUG: MAX_SIZE=$MAX_SIZE bytes ($(( MAX_SIZE / 1024 / 1024 )) MB)"
-
-          upload_with_retry() {
-            local file=$1
-            local max_attempts=3
-            local timeout_duration="10m"
-            echo "🔍 DEBUG: upload_with_retry() called for file=$file"
-
-            for ((i=1; i<=max_attempts; i++)); do
-              echo "  Attempt $i for $file..."
-              echo "🔍 DEBUG: Upload attempt $i for $file"
-
-              if [ "${{ inputs.debug }}" = "true" ]; then
-                echo "🔍 DEBUG: Running gh release upload in debug mode"
-                if timeout "$timeout_duration" gh release upload "$TAG_NAME" "$file" --clobber --repo "$TARGET_REPO"; then
-                  echo "  Successfully uploaded $file"
-                  echo "🔍 DEBUG: Upload successful for $file"
-                  return 0
-                fi
-              else
-                echo "🔍 DEBUG: Running gh release upload in silent mode"
-                if timeout "$timeout_duration" gh release upload "$TAG_NAME" "$file" --clobber --repo "$TARGET_REPO" > /dev/null 2>&1; then
-                  echo "  Successfully uploaded $file"
-                  echo "🔍 DEBUG: Upload successful for $file"
-                  return 0
-                fi
-              fi
-
-              echo "  ⚠ Attempt $i failed or timed out after $timeout_duration. Retrying..."
-              echo "🔍 DEBUG: Upload attempt $i failed, timeout=$timeout_duration"
-              if [ "$i" -lt "$max_attempts" ]; then
-                echo "🔍 DEBUG: Sleeping 5 seconds before retry"
-                sleep 5
-              fi
-            done
-            echo "🔍 DEBUG: All $max_attempts attempts exhausted for $file"
-            return 1
-          }
-
-          # Fetch existing assets to clean up stale entries
-          echo "🔍 DEBUG: Fetching existing assets for cleanup"
-          ASSETS_URL="https://github.com/${TARGET_REPO}/releases/expanded_assets/$TAG_NAME"
-          echo "🔍 DEBUG: ASSETS_URL=$ASSETS_URL"
-          HTTP_RESPONSE=$(curl -sL -A "Mozilla/5.0" -w "%{http_code}" "$ASSETS_URL" -o assets.html || echo "404")
-          echo "🔍 DEBUG: HTTP_RESPONSE=$HTTP_RESPONSE"
-          if [ "$HTTP_RESPONSE" -eq 200 ]; then
-            ALL_ASSETS=$(grep -oP "download/$TAG_NAME/\K[^\"' ]+" assets.html | sort || echo "")
-            echo "🔍 DEBUG: Parsed ALL_ASSETS, count=$(echo "$ALL_ASSETS" | wc -l)"
-          else
-            ALL_ASSETS=""
-            echo "🔍 DEBUG: Failed to fetch assets, ALL_ASSETS is empty"
-          fi
-          rm -f assets.html
-          echo "🔍 DEBUG: assets.html removed"
-
-          chunk_size="1500M"
-          split_required=false
-          echo "🔍 DEBUG: Evaluating split requirement"
-          echo "🔍 DEBUG: FILE_SIZE=$FILE_SIZE, MAX_SIZE=$MAX_SIZE"
-
-          if [ "$FILE_SIZE" -gt "$MAX_SIZE" ]; then
-            split_required=true
-            echo "🔍 DEBUG: Split required (FILE_SIZE > MAX_SIZE)"
-          else
-            echo "🔍 DEBUG: No split required (FILE_SIZE <= MAX_SIZE)"
-          fi
-
-          if [ "$split_required" = "true" ]; then
-            echo "⚠ Splitting into ${chunk_size} chunks..."
-            echo "🔍 DEBUG: Executing split command with chunk_size=1500M"
-            split -b "$chunk_size" -a 2 "$FILENAME" "${FILENAME}.part"
-
-            NEW_PARTS=(${FILENAME}.part*)
-            NEW_PARTS_COUNT=${#NEW_PARTS[@]}
-            echo "🔍 DEBUG: Split complete, NEW_PARTS_COUNT=$NEW_PARTS_COUNT"
-
-            # Remove old single-file asset if switching to split parts
-            if echo "$ALL_ASSETS" | grep -xF "${FILENAME}" >/dev/null 2>&1; then
-              echo "🔍 DEBUG: Found old single-file asset, removing it"
-              echo "Removing old single asset to make way for split parts: ${FILENAME}"
-              gh release delete-asset "$TAG_NAME" "${FILENAME}" --repo "$TARGET_REPO" -y || true
-            else
-              echo "🔍 DEBUG: No old single-file asset to remove"
-            fi
-
-            # Clean up excess trailing parts from previous builds
-            OLD_PARTS=$(echo "$ALL_ASSETS" | grep -F "${FILENAME}.part" || echo "")
-            echo "🔍 DEBUG: OLD_PARTS count=$(echo "$OLD_PARTS" | grep -c . || echo 0)"
-            if [ -n "$OLD_PARTS" ]; then
-              OLD_PARTS_COUNT=$(echo "$OLD_PARTS" | wc -l)
-              echo "🔍 DEBUG: OLD_PARTS_COUNT=$OLD_PARTS_COUNT, NEW_PARTS_COUNT=$NEW_PARTS_COUNT"
-              if [ "$OLD_PARTS_COUNT" -gt "$NEW_PARTS_COUNT" ]; then
-                echo "Old build had $OLD_PARTS_COUNT parts, new has $NEW_PARTS_COUNT. Cleaning up excess parts..."
-                EXT_PARTS=$(echo "$OLD_PARTS" | awk -v skip="$NEW_PARTS_COUNT" 'NR>skip')
-                for asset in $EXT_PARTS; do
-                  echo "🔍 DEBUG: Removing old part: $asset"
-                  echo "Removing leftover trailing part: $asset"
-                  gh release delete-asset "$TAG_NAME" "$asset" --repo "$TARGET_REPO" -y || true
-                done
-              else
-                echo "🔍 DEBUG: No excess parts to clean up"
-              fi
-            fi
-
-            for part in "${FILENAME}".part*; do
-              echo "🔍 DEBUG: Uploading part: $part"
-              echo "Uploading $part..."
-              if ! upload_with_retry "$part"; then
-                echo "::error::Failed to upload part $part after multiple attempts."
-                echo "🔍 DEBUG: CRITICAL - Upload failed for $part"
-                exit 1
-              fi
-              echo "🔍 DEBUG: Successfully uploaded $part, removing local copy"
-              rm -f "$part"
-            done
-            echo "🔍 DEBUG: All parts uploaded, removing original archive"
-            rm -rf "$FILENAME"
-          else
-            # Remove old split parts if new build fits in a single file
-            echo "🔍 DEBUG: Checking for old split parts to clean up"
-            OLD_PARTS=$(echo "$ALL_ASSETS" | grep -F "${FILENAME}.part" || echo "")
-            if [ -n "$OLD_PARTS" ]; then
-              echo "New build shrunk to single file. Cleaning up old split parts for ${FILENAME}..."
-              echo "🔍 DEBUG: Found old split parts, removing them"
-              while read -r asset; do
-                [ -z "$asset" ] && continue
-                echo "🔍 DEBUG: Removing ghost split part: $asset"
-                echo "Removing ghost split part: $asset"
-                gh release delete-asset "$TAG_NAME" "$asset" --repo "$TARGET_REPO" -y || true
-              done <<< "$OLD_PARTS"
-            else
-              echo "🔍 DEBUG: No old split parts to clean up"
-            fi
-
-            echo "🔍 DEBUG: Uploading single file: $FILENAME"
-            echo "Uploading $FILENAME..."
-            if ! upload_with_retry "$FILENAME"; then
-              echo "::error::Failed to upload $FILENAME after multiple attempts."
-              echo "🔍 DEBUG: CRITICAL - Upload failed for $FILENAME"
-              exit 1
-            fi
-            echo "🔍 DEBUG: Successfully uploaded $FILENAME, removing local copy"
-            rm -f "$FILENAME"
-          fi
-          # Cleanup any leftover archive artifacts (single-file or split parts)
-          echo "🔍 DEBUG: Final cleanup phase"
-          if [ "${{ inputs.debug }}" = "true" ]; then
-            echo "Cleaning archive artifacts: ${BASE_FILENAME}*"
-            echo "🔍 DEBUG: Listing files matching ${BASE_FILENAME}*"
-            ls -la "${BASE_FILENAME}"* 2>/dev/null || true
-          fi
-          rm -f "${BASE_FILENAME}"* || true
-          echo "🔍 DEBUG: Cleanup complete"
-
-          echo "::endgroup::"
-          echo "🔍 DEBUG: cache-save action finished successfully"
-        fi
+    - name: Archive Cache
+      uses: ./.github/actions/cache-archive
+      with:
+        cache_path: ${{ inputs.cache_path }}
+        cache_key: ${{ inputs.cache_key }}
+        cache_bucket: ${{ inputs.cache_bucket }}
+        compression_level: ${{ inputs.compression_level }}
+        working_dir: ${{ inputs.working_dir }}
+        debug: ${{ inputs.debug }}
+
+    - name: Upload Cache
+      uses: ./.github/actions/cache-upload
+      with:
+        cache_key: ${{ inputs.cache_key }}
+        cache_bucket: ${{ inputs.cache_bucket }}
+        github_token: ${{ inputs.github_token }}
+        compression_level: ${{ inputs.compression_level }}
+        debug: ${{ inputs.debug }}

+ 95 - 0
.github/actions/cache-upload/action.yml

@@ -0,0 +1,95 @@
+name: 'Upload Cache to GitHub Releases'
+description: 'Upload cache archive(s) to GitHub Releases with retry logic'
+inputs:
+  cache_key:
+    description: 'Unique key for the cache'
+    required: true
+    type: string
+  cache_bucket:
+    description: 'The tag name for the release'
+    required: true
+    default: 'general-cache'
+  github_token:
+    description: 'GitHub Token'
+    required: true
+  compression_level:
+    description: 'Compression level (0-9)'
+    required: false
+    type: number
+    default: 6
+  debug:
+    description: 'Enable Logs'
+    required: false
+    type: boolean
+    default: false
+
+runs:
+  using: 'composite'
+  steps:
+    - name: Upload Cache Asset(s)
+      shell: bash
+      env:
+        GH_TOKEN: ${{ inputs.github_token }}
+        TARGET_REPO: "${{ github.repository }}"
+      run: |
+        set -euo pipefail
+
+        TAG_NAME="${{ inputs.cache_bucket }}"
+        BASE_FILENAME="cache-${{ inputs.cache_key }}"
+
+        if [ "${{ inputs.compression_level }}" -eq 0 ]; then
+          FILENAME="$BASE_FILENAME.tar"
+        else
+          FILENAME="$BASE_FILENAME.tzst"
+        fi
+
+        upload_with_retry() {
+          local file=$1
+          local max_attempts=3
+          local timeout_duration="10m"
+
+          for ((i=1; i<=max_attempts; i++)); do
+            echo "  Attempt $i for $file..."
+            if [ "${{ inputs.debug }}" = "true" ]; then
+              if timeout "$timeout_duration" gh release upload "$TAG_NAME" "$file" --clobber --repo "$TARGET_REPO"; then
+                echo "  Successfully uploaded $file"
+                return 0
+              fi
+            else
+              if timeout "$timeout_duration" 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. Retrying..."
+            if [ "$i" -lt "$max_attempts" ]; then
+              sleep 5
+            fi
+          done
+          echo "::error::Failed to upload $file after multiple attempts."
+          return 1
+        }
+
+        # Upload single file or split parts
+        if [ -f "$FILENAME" ]; then
+          if ! upload_with_retry "$FILENAME"; then
+            exit 1
+          fi
+          rm -f "$FILENAME"
+        else
+          # Upload split parts
+          for part in "${FILENAME}".part*; do
+            if [ ! -f "$part" ]; then
+              continue
+            fi
+            if ! upload_with_retry "$part"; then
+              exit 1
+            fi
+            rm -f "$part"
+          done
+          rm -rf "$FILENAME"
+        fi
+
+        # Final cleanup
+        rm -f "${BASE_FILENAME}"* || true
+        echo "✅ Cache upload complete"