Skip to content

Preview Sweep

Preview Sweep #230

Workflow file for this run

name: Preview Sweep
on:
schedule:
- cron: '0 0 * * *' # Daily at midnight UTC
workflow_dispatch:
permissions: {}
jobs:
sweep:
name: Sweep Orphaned Previews
runs-on: ubuntu-latest
environment: preview
permissions:
pull-requests: write
steps:
- name: Sweep orphaned previews
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PLANETSCALE_SERVICE_TOKEN: ${{ secrets.PLANETSCALE_SERVICE_TOKEN }}
PLANETSCALE_SERVICE_TOKEN_ID: ${{ secrets.PLANETSCALE_SERVICE_TOKEN_ID }}
PLANETSCALE_ORG: ${{ secrets.PLANETSCALE_ORG }}
PLANETSCALE_DB: ${{ secrets.PLANETSCALE_DB }}
STRIPE_SECRET_KEY: ${{ secrets.STRIPE_SECRET_KEY }}
run: |
# List all preview workers
WORKERS=$(curl -sS \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/workers/scripts" \
| jq -r '.result[].id // empty' | grep '^curl-pr[0-9]*$' || true)
if [ -z "$WORKERS" ]; then
echo "No preview workers found"
exit 0
fi
echo "Found preview workers:"
echo "$WORKERS"
echo ""
for worker in $WORKERS; do
PR_NUM=$(echo "$worker" | sed 's/curl-pr//')
PR_JSON=$(gh api "repos/${{ github.repository }}/pulls/$PR_NUM" --jq '{state: .state, draft: .draft}' 2>/dev/null || echo '{"state":"unknown","draft":false}')
STATE=$(echo "$PR_JSON" | jq -r '.state')
DRAFT=$(echo "$PR_JSON" | jq -r '.draft')
if [ "$STATE" = "open" ] && [ "$DRAFT" != "true" ]; then
echo "Skipping $worker (PR #$PR_NUM is open and not draft)"
continue
fi
echo "Cleaning up $worker (PR #$PR_NUM state: $STATE, draft: $DRAFT)"
# Remove Worker from Queue consumers
echo " Removing worker from queue consumers..."
QUEUE_IDS=$(curl -sS \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/queues" \
| jq -r --arg prefix "-pr$PR_NUM" '.result[] | select(.queue_name | test($prefix + "(-dlq)?$")) | .queue_id')
for QUEUE_ID in $QUEUE_IDS; do
CONSUMER_ID=$(curl -sS \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/queues/$QUEUE_ID/consumers" \
| jq -r --arg name "$worker" '.result[] | select(.script == $name) | .consumer_id // empty')
if [ -n "$CONSUMER_ID" ]; then
curl -sS -X DELETE \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/queues/$QUEUE_ID/consumers/$CONSUMER_ID" \
|| true
fi
done
# Clear Worker bindings so queues can be deleted
echo " Clearing worker bindings..."
curl -sS -X PATCH \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-F 'settings={"bindings":[]};type=application/json' \
"https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/workers/scripts/$worker/settings" \
|| true
# Delete Worker
echo " Deleting worker..."
curl -sS -X DELETE \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/workers/scripts/$worker?force=true" \
|| true
# Delete Hyperdrive
echo " Deleting Hyperdrive..."
HD_ID=$(curl -sS \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/hyperdrive/configs" \
| jq -r --arg name "curl-hd-pr$PR_NUM" '.result[] | select(.name == $name) | .id // empty')
if [ -n "$HD_ID" ]; then
curl -sS -X DELETE \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/hyperdrive/configs/$HD_ID" || true
fi
# Delete PlanetScale branch
echo " Deleting PlanetScale branch..."
curl -sS -X DELETE \
-H "Authorization: $PLANETSCALE_SERVICE_TOKEN_ID:$PLANETSCALE_SERVICE_TOKEN" \
"https://api.planetscale.com/v1/organizations/$PLANETSCALE_ORG/databases/$PLANETSCALE_DB/branches/pr-$PR_NUM" \
|| true
# Delete KV namespace
echo " Deleting KV namespace..."
KV_ID=$(curl -sS \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/storage/kv/namespaces" \
| jq -r --arg name "curl-kv-pr$PR_NUM" '.result[] | select(.title == $name) | .id // empty')
if [ -n "$KV_ID" ]; then
curl -sS -X DELETE \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/storage/kv/namespaces/$KV_ID" || true
fi
# Delete Queue
echo " Deleting Queues..."
QUEUE_IDS=$(curl -sS \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/queues" \
| jq -r --arg prefix "-pr$PR_NUM" '.result[] | select(.queue_name | test($prefix + "(-dlq)?$")) | .queue_id')
for QUEUE_ID in $QUEUE_IDS; do
curl -sS -X DELETE \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/queues/$QUEUE_ID" || true
done
# Delete Stripe webhook endpoint
echo " Deleting Stripe webhook..."
APEX="pr${PR_NUM}"
ENDPOINT_ID=$(curl -sS https://api.stripe.com/v1/webhook_endpoints \
-u "$STRIPE_SECRET_KEY:" \
-G -d "limit=100" \
| jq -r --arg apex "$APEX" '.data[] | select(.metadata.preview == $apex) | .id')
if [ -n "$ENDPOINT_ID" ]; then
curl -sS -X DELETE "https://api.stripe.com/v1/webhook_endpoints/$ENDPOINT_ID" \
-u "$STRIPE_SECRET_KEY:" || true
fi
# Update PR comment to shut-down status
SHA=$(gh api "repos/${{ github.repository }}/pulls/$PR_NUM" --jq '.head.sha' 2>/dev/null || echo "")
COMMENT_ID=$(gh api "repos/${{ github.repository }}/issues/${PR_NUM}/comments" --paginate --jq '.[] | select(.body | contains("preview deployment")) | .id' 2>/dev/null || echo "")
if [ -n "$COMMENT_ID" ] && [ -n "$SHA" ]; then
SHA_SHORT=$(echo "$SHA" | cut -c1-7)
APEX="pr${PR_NUM}"
DASH_URL="https://dash.cloudflare.com/?to=/:account/workers/services/view/curl-pr${PR_NUM}/production"
RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
UPDATED=$(date -u +'%b %-d, %Y, %-I:%M %p')
BODY=$(printf '%s\n\n%s\n%s\n%s' \
"The latest preview deployment for ${SHA_SHORT}" \
"| Name | Status | Preview | Updated (UTC) |" \
"| :--- | :----- | :------ | :------------ |" \
"| [${APEX}](${DASH_URL}) | [![Shut Down](https://vercel.com/static/status/canceled.svg) Shut Down](${RUN_URL}) | [${APEX}.curl.md](https://${APEX}.curl.md) | ${UPDATED} |")
echo " Updating PR comment..."
gh api "repos/${{ github.repository }}/issues/comments/${COMMENT_ID}" \
-X PATCH \
-f body="$BODY" || true
fi
echo " Done"
echo ""
done