|
| 1 | +name: Wait for deploy preview |
| 2 | +description: Polls a GitHub commit status context until a deploy preview URL is available |
| 3 | + |
| 4 | +inputs: |
| 5 | + status-context: |
| 6 | + description: The commit status context to wait for (e.g. "netlify/edge/deploy-preview") |
| 7 | + required: true |
| 8 | + max-attempts: |
| 9 | + description: Maximum number of polling attempts |
| 10 | + required: false |
| 11 | + default: '60' |
| 12 | + poll-interval: |
| 13 | + description: Seconds between polling attempts |
| 14 | + required: false |
| 15 | + default: '30' |
| 16 | + |
| 17 | +outputs: |
| 18 | + deploy-url: |
| 19 | + description: The deploy preview URL |
| 20 | + value: ${{ steps.poll.outputs.deploy-url }} |
| 21 | + deploy-host: |
| 22 | + description: The deploy preview host (without protocol or path) |
| 23 | + value: ${{ steps.poll.outputs.deploy-host }} |
| 24 | + |
| 25 | +runs: |
| 26 | + using: composite |
| 27 | + steps: |
| 28 | + - id: poll |
| 29 | + shell: bash |
| 30 | + env: |
| 31 | + STATUS_CONTEXT: ${{ inputs.status-context }} |
| 32 | + SHA: ${{ github.event.pull_request.head.sha }} |
| 33 | + REPO: ${{ github.repository }} |
| 34 | + MAX_ATTEMPTS: ${{ inputs.max-attempts }} |
| 35 | + POLL_INTERVAL: ${{ inputs.poll-interval }} |
| 36 | + run: | |
| 37 | + echo "Waiting for commit status '${STATUS_CONTEXT}' on ${SHA}..." |
| 38 | +
|
| 39 | + for i in $(seq 1 $MAX_ATTEMPTS); do |
| 40 | + # All status entries for this context on this commit, newest first. We need the full |
| 41 | + # history (not the rolled-up combined status) because the per-deploy id only appears on |
| 42 | + # the earlier "processing" entry, not on the final "ready" one. |
| 43 | + STATUS_LIST=$(gh api "repos/${REPO}/statuses/${SHA}" --jq "[.[] | select(.context == \"${STATUS_CONTEXT}\")]") |
| 44 | +
|
| 45 | + STATUS_STATE=$(echo "$STATUS_LIST" | jq -r 'first | .state // empty') |
| 46 | + # target_url of the latest entry: once ready, this is the PR-level alias |
| 47 | + # (deploy-preview-<pr>--<site>.<domain>), which is shared by every commit in the PR. |
| 48 | + ALIAS_URL=$(echo "$STATUS_LIST" | jq -r 'first | .target_url // empty') |
| 49 | +
|
| 50 | + if [ "$STATUS_STATE" = "success" ]; then |
| 51 | + echo "Deploy preview is ready!" |
| 52 | + echo " State: ${STATUS_STATE}" |
| 53 | + echo " Alias URL: ${ALIAS_URL}" |
| 54 | +
|
| 55 | + # The "processing" entry carries a per-deploy URL of the form |
| 56 | + # https://app.netlify.com/projects/<site>/deploys/<deploy-id>. The deploy id is unique |
| 57 | + # per commit, so we build a permalink from it to avoid the caching issues caused by |
| 58 | + # reusing the shared deploy-preview-<pr>--<site> alias across commits. |
| 59 | + DEPLOY_ID=$(echo "$STATUS_LIST" \ |
| 60 | + | jq -r 'map(select(.target_url // "" | test("/deploys/"))) | first | .target_url // empty' \ |
| 61 | + | sed -E 's|.*/deploys/||; s|[/?#].*||') |
| 62 | +
|
| 63 | + # Strip protocol and any path, leaving just the host (portable, no sed regex quirks). |
| 64 | + ALIAS_HOST=${ALIAS_URL#*://} |
| 65 | + ALIAS_HOST=${ALIAS_HOST%%/*} |
| 66 | +
|
| 67 | + if [ -n "$DEPLOY_ID" ] && printf '%s' "$ALIAS_HOST" | grep -q -- '--'; then |
| 68 | + # Swap the alias label (before "--") for the deploy id, keeping the domain suffix so |
| 69 | + # this works across environments (netlify.app, netlifystg.app, ...). |
| 70 | + DOMAIN_SUFFIX=${ALIAS_HOST#*--} |
| 71 | + DEPLOY_HOST="${DEPLOY_ID}--${DOMAIN_SUFFIX}" |
| 72 | + DEPLOY_URL="https://${DEPLOY_HOST}" |
| 73 | + echo " Deploy id: ${DEPLOY_ID}" |
| 74 | + else |
| 75 | + echo " Could not derive a permalink (deploy id: '${DEPLOY_ID:-none}'), falling back to alias URL" |
| 76 | + DEPLOY_HOST="$ALIAS_HOST" |
| 77 | + DEPLOY_URL="$ALIAS_URL" |
| 78 | + fi |
| 79 | +
|
| 80 | + echo " Deploy URL: ${DEPLOY_URL}" |
| 81 | + echo " Deploy host: ${DEPLOY_HOST}" |
| 82 | +
|
| 83 | + echo "deploy-url=${DEPLOY_URL}" >> "$GITHUB_OUTPUT" |
| 84 | + echo "deploy-host=${DEPLOY_HOST}" >> "$GITHUB_OUTPUT" |
| 85 | + exit 0 |
| 86 | + elif [ "$STATUS_STATE" = "failure" ] || [ "$STATUS_STATE" = "error" ]; then |
| 87 | + echo "Deploy preview failed with state: ${STATUS_STATE}" |
| 88 | + echo " Target URL: ${ALIAS_URL}" |
| 89 | + exit 1 |
| 90 | + fi |
| 91 | +
|
| 92 | + echo "Attempt ${i}/${MAX_ATTEMPTS}: status is '${STATUS_STATE:-not found}', waiting ${POLL_INTERVAL}s..." |
| 93 | + sleep $POLL_INTERVAL |
| 94 | + done |
| 95 | +
|
| 96 | + echo "Timed out waiting for deploy preview after $((MAX_ATTEMPTS * POLL_INTERVAL))s" |
| 97 | + exit 1 |
0 commit comments