Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions .github/scripts/purge-jsdelivr.sh
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
#!/usr/bin/env bash
# Purges @latest and @main for each file given.
set -euo pipefail
# Purges @latest for each file given and prints the files whose purge failed. A throttled purge still
# answers HTTP 200, so the body decides.
set -uo pipefail
failed=0
for f in "$@"; do
for ref in latest main; do
url="https://purge.jsdelivr.net/gh/${GITHUB_REPOSITORY}@${ref}/${f}"
echo "Purging ${url}"
curl -sfS --retry 3 "${url}" > /dev/null || { echo "::error::Purge failed for ${url}"; exit 1; }
done
url="https://purge.jsdelivr.net/gh/${GITHUB_REPOSITORY}@latest/${f}"
body=$(curl -sfS --max-time 30 --retry 3 --retry-max-time 60 "${url}") || body=""
if jq -e '[.paths[] | .throttled == false and ([.providers[]] | all)] | all' <<< "${body}" > /dev/null 2>&1; then
echo "Purged ${url}" >&2
else
echo "::error::Purge failed or was throttled for ${url}: ${body:-no response}" >&2
echo "${f}"
failed=1
fi
done
exit "${failed}"
11 changes: 11 additions & 0 deletions .github/scripts/stale-jsdelivr-files.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
# Prints each file whose @latest copy is not version $1.
set -uo pipefail
version=$1
shift
for f in "$@"; do
v=$(curl -sfSI --max-time 20 "https://cdn.jsdelivr.net/gh/${GITHUB_REPOSITORY}@latest/${f}" \
| tr -d '\r' | awk -F': ' 'tolower($1) == "x-jsd-version" { print $2 }') || v=""
echo "${f}: ${v:-unavailable}" >&2
[ "${v}" = "${version}" ] || echo "${f}"
done
83 changes: 49 additions & 34 deletions .github/workflows/release-and-purge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,12 @@ env:
FILES: surface_tag.js surface_tag.min.js surface_embed_v1.js surface_embed_v1.min.js

jobs:
release-and-purge:
release:
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
version: ${{ steps.release.outputs.version }}
steps:
- uses: actions/checkout@v4

# jsDelivr @latest resolves to the highest SEMVER release, and this repo already uses v1.1.x — so
# bump the PATCH of the latest release (v1.1.8 -> v1.1.9). Date-based tags would break @latest:
# leading zeros (2026.09.02) are not valid semver, so jsDelivr would ignore them. target = the exact
Expand Down Expand Up @@ -79,41 +80,55 @@ jobs:
-f target_commitish="${GITHUB_SHA}" \
-f name="${RELEASE_TAG}" \
-f body="Surface Tag synced from surface-forms (packages/surface-scripts)."
echo "Released ${RELEASE_TAG}; jsDelivr @latest now resolves to this commit."
echo "Released ${RELEASE_TAG} at ${GITHUB_SHA}." | tee -a "${GITHUB_STEP_SUMMARY}"
echo "version=${RELEASE_TAG#v}" >> "${GITHUB_OUTPUT}"

# An edge that refills between the purge and jsDelivr picking up the new release caches the
# previous version for 12 hours. Purge, re-purge until every @latest file reports the new
# version, then purge once more.
# A separate job, so "Re-run failed jobs" repeats the purge without cutting another release.
purge:
needs: release
runs-on: ubuntu-latest
timeout-minutes: 20
env:
VERSION: ${{ needs.release.outputs.version }}
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false

- name: Purge jsDelivr cache
run: .github/scripts/purge-jsdelivr.sh ${FILES}
id: purge
run: |
failed=$(.github/scripts/purge-jsdelivr.sh ${FILES} | xargs) || true
echo "failed=${failed}" >> "${GITHUB_OUTPUT}"
echo "- Purge failed for: ${failed:-none}" | tee -a "${GITHUB_STEP_SUMMARY}"

- name: Wait for @latest to serve v${{ steps.release.outputs.version }}
# A file whose purge failed is retried even when this runner's CDN server already serves it fresh.
- name: Check @latest serves v${{ needs.release.outputs.version }}
id: check
env:
VERSION: ${{ steps.release.outputs.version }}
FAILED: ${{ steps.purge.outputs.failed }}
run: |
set -euo pipefail
served_version() {
curl -sfS -D - -o /dev/null "https://cdn.jsdelivr.net/gh/${GITHUB_REPOSITORY}@latest/$1" \
| tr -d '\r' | awk -F': ' 'tolower($1) == "x-jsd-version" { print $2 }'
}
for i in $(seq 1 30); do
sleep 10
stale=""
for f in ${FILES}; do
v=$(served_version "${f}" || true)
[ "${v}" = "${VERSION}" ] || stale="${stale} ${f}"
echo "${f}: ${v:-unavailable}"
done
if [ -z "${stale}" ]; then
echo "@latest serves ${VERSION} for every file." | tee -a "${GITHUB_STEP_SUMMARY}"
exit 0
fi
echo "Stale (${i}/30):${stale}"
.github/scripts/purge-jsdelivr.sh ${stale}
done
echo "::error::@latest still serves an older version after 5 minutes:${stale}"
exit 1
stale=$(.github/scripts/stale-jsdelivr-files.sh "${VERSION}" ${FILES} | xargs)
retry=$(printf '%s\n' ${stale} ${FAILED} | sort -u | xargs)
echo "retry=${retry}" >> "${GITHUB_OUTPUT}"
echo "- Stale after the first purge: ${stale:-none}" | tee -a "${GITHUB_STEP_SUMMARY}"

# jsDelivr caches a repo's version list for up to 10 minutes and a purge doesn't refresh it, so a
# purge before then refills with the previous release (jsdelivr/jsdelivr#18376).
- name: Wait 10 minutes, then purge those files again
if: ${{ steps.check.outputs.retry != '' }}
env:
RETRY: ${{ steps.check.outputs.retry }}
run: |
sleep 600
.github/scripts/purge-jsdelivr.sh ${RETRY} > /dev/null

- name: Purge jsDelivr cache again
run: .github/scripts/purge-jsdelivr.sh ${FILES}
- name: Check @latest serves v${{ needs.release.outputs.version }} again
if: ${{ steps.check.outputs.retry != '' }}
run: |
stale=$(.github/scripts/stale-jsdelivr-files.sh "${VERSION}" ${FILES} | xargs)
if [ -n "${stale}" ]; then
echo "::error::@latest still serves an older version of: ${stale}. Customer sites load these files. Purging again within the hour is throttled; re-run failed jobs after an hour."
exit 1
fi
echo "- Every @latest file serves ${VERSION}." | tee -a "${GITHUB_STEP_SUMMARY}"
Loading