From 333211c4d7dbd0d213c8b05ab2939bfd853771ee Mon Sep 17 00:00:00 2001 From: ShaneK Date: Wed, 23 Sep 2026 13:20:45 -0700 Subject: [PATCH] chore(ci): retry the package lock bump until npm resolves --- .github/workflows/release.yml | 91 +++++++++++++++++++++++++++++++---- 1 file changed, 81 insertions(+), 10 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bb552039012..5536a4e8923 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -85,9 +85,17 @@ jobs: # possible for them to push at the same time. needs: [finalize-release] runs-on: ubuntu-latest + # This is a backstop for the retry loop in + # Resolve Package Locks, which fails with its + # own message first. + timeout-minutes: 30 permissions: contents: write id-token: write + outputs: + # This says the new versions resolved on npm, + # which is not the same as the job passing. + resolved: ${{ steps.resolve.outputs.resolved }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 # Pull the latest version of the reference @@ -108,25 +116,74 @@ jobs: git config user.email hi@ionicframework.com shell: bash # Lerna does not automatically bump versions - # of Ionic dependencies that have changed, - # so we do that here. - - name: Bump Package Lock + # of Ionic dependencies that have changed, so + # we do that here. The install fails with + # ETARGET until the versions published earlier + # resolve on npm, so retry. The + # `--prefer-online` flag matters because npm + # caches the failed lookup, and an `npm view` + # probe would read a different cached document. + - name: Resolve Package Locks + id: resolve + env: + SLEEP_SECONDS: 15 + DEADLINE_SECONDS: 900 + run: | + set -euo pipefail + # We bound this on the wall clock so the loop reports its own + # error before `timeout-minutes` kills the job. The `timeout` + # call caps an attempt that hangs rather than fails, which would + # never reach the check below. + deadline=$(( SECONDS + DEADLINE_SECONDS )) + attempt=0 + until timeout --kill-after=30 300 lerna exec "npm install --package-lock-only --prefer-online"; do + attempt=$(( attempt + 1 )) + if [ "$SECONDS" -ge "$deadline" ]; then + echo "::error::npm install --package-lock-only failed $attempt times over ${SECONDS}s; see the last attempt above for the error" + exit 1 + fi + echo "Attempt $attempt failed, retrying in ${SLEEP_SECONDS}s" + sleep "$SLEEP_SECONDS" + done + echo "resolved=true" >> "$GITHUB_OUTPUT" + shell: bash + # This is split from the retry so `resolved` + # does not depend on the commit and the push + # working. + - name: Commit Package Locks + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - lerna exec "npm install --package-lock-only" + set -euo pipefail git add . + # Re-running this job after the locks were already committed + # would otherwise fail on an empty commit. + if git diff --cached --quiet; then + echo "No package lock changes to commit" + exit 0 + fi git commit -m "chore(): update package lock files" git push - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} shell: bash purge-cdn-cache: - needs: [release-ionic] + # This runs after `update-package-lock` so the + # new versions have resolved on npm. Purging + # earlier re-caches `@ionic/core@latest` at the + # previous release. A failed `finalize-release` + # skips this too, so a half-finished release + # that did reach npm needs a manual purge. + needs: [update-package-lock] + if: ${{ !cancelled() && needs.update-package-lock.outputs.resolved == 'true' }} runs-on: ubuntu-latest + timeout-minutes: 5 steps: - name: Purge JSDelivr Cache run: | - curl -X POST \ + set -euo pipefail + if ! response=$(curl -sS --max-time 60 --retry 3 --retry-all-errors \ + --retry-max-time 120 \ + -w '\n%{http_code}' -X POST \ https://purge.jsdelivr.net/ \ -H 'cache-control: no-cache' \ -H 'content-type: application/json' \ @@ -142,7 +199,21 @@ jobs: "/npm/@ionic/core@7/css/ionic.bundle.css", "/npm/@ionic/core@8/css/ionic.bundle.css", "/npm/@ionic/core@9/css/ionic.bundle.css", - "/npm/@ionic/core@latest/css/ionic.bundle.css" + "/npm/@ionic/core@latest/css/ionic.bundle.css", "/npm/@ionic/core@next/css/ionic.bundle.css" - ]}' + ]}'); then + echo "::warning::jsDelivr purge request failed" + exit 0 + fi + status=${response##*$'\n'} + echo "${response%$'\n'*}" + # `curl` exits 0 whatever jsDelivr answers, so check the status. + # A 4xx means we sent a bad payload, which is otherwise silent. + # Anything else is jsDelivr being down, which should not fail + # a release. + case "$status" in + 2*) ;; + 4*) echo "::error::jsDelivr rejected the purge with HTTP $status"; exit 1 ;; + *) echo "::warning::jsDelivr purge did not complete (HTTP $status)" ;; + esac shell: bash