Deploy docs preview for PR #52
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Deploy docs preview | ||
|
Check warning on line 1 in .github/workflows/site-preview.yml
|
||
| run-name: Deploy docs preview for PR #${{ github.event.pull_request.number || inputs.pull_request_number }} | ||
| on: | ||
| pull_request_target: | ||
| types: [opened, reopened, synchronize, labeled, unlabeled] | ||
| workflow_dispatch: | ||
| inputs: | ||
| pull_request_number: | ||
| description: Open pull request to preview | ||
| required: true | ||
| type: string | ||
| permissions: | ||
| contents: read | ||
| pull-requests: write | ||
| concurrency: | ||
| group: nimbus-site-preview-${{ github.event.pull_request.number || inputs.pull_request_number }} | ||
| cancel-in-progress: true | ||
| jobs: | ||
| preview: | ||
| name: Build on Vercel | ||
| if: >- | ||
| github.event_name == 'workflow_dispatch' || | ||
| (github.event.action != 'labeled' && github.event.action != 'unlabeled') || | ||
| startsWith(github.event.label.name, 'docs-translations-') | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 60 | ||
| steps: | ||
| # A superseding run may start after GitHub terminated the previous runner | ||
| # before its EXIT trap completed. Clear that stale state before any | ||
| # validation in this run can fail. | ||
| - name: Clear an interrupted preview status | ||
| continue-on-error: true | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| GH_REPO: ${{ github.repository }} | ||
| PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number || inputs.pull_request_number }} | ||
| run: | | ||
| marker="<!-- nimbus-site-preview -->" | ||
| comment_id="$({ | ||
| gh api --paginate "repos/$GH_REPO/issues/$PULL_REQUEST_NUMBER/comments" \ | ||
| --jq ".[] | select(.user.login == \"github-actions[bot]\") | select(.body | contains(\"$marker\")) | .id" | ||
| } | tail -n 1)" | ||
| if [[ -z "$comment_id" ]]; then | ||
| exit 0 | ||
| fi | ||
| body="$(gh api "repos/$GH_REPO/issues/comments/$comment_id" --jq .body)" | ||
| if [[ "$body" != *"🟡 Building"* ]]; then | ||
| exit 0 | ||
| fi | ||
| updated_at="$(date -u '+%Y-%m-%d %H:%M UTC')" | ||
| body="$(jq -nr --arg body "$body" --arg updated_at "$updated_at" \ | ||
| '$body | gsub("🟡 Building"; "🔴 Failed") | sub("\\| [^|\\n]+ \\|$"; "| \($updated_at) |")')" | ||
| gh api --method PATCH "repos/$GH_REPO/issues/comments/$comment_id" -f body="$body" >/dev/null | ||
| - name: Require a trusted manual invocation | ||
| if: github.event_name == 'workflow_dispatch' | ||
| env: | ||
| DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} | ||
| run: | | ||
| if [[ "$GITHUB_REF_NAME" != "$DEFAULT_BRANCH" ]]; then | ||
| echo "Run this workflow from the repository's default branch." >&2 | ||
| exit 1 | ||
| fi | ||
| - name: Resolve the pull request revision | ||
| id: pull-request | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number || inputs.pull_request_number }} | ||
| run: | | ||
| state="$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PULL_REQUEST_NUMBER" --jq .state)" | ||
| if [[ "$state" != "open" ]]; then | ||
| echo "Pull request $PULL_REQUEST_NUMBER is not open." >&2 | ||
| exit 1 | ||
| fi | ||
| head_sha="$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PULL_REQUEST_NUMBER" --jq .head.sha)" | ||
| head_repository="$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PULL_REQUEST_NUMBER" --jq .head.repo.full_name)" | ||
| if [[ ! "$head_sha" =~ ^[0-9a-f]{40}$ ]]; then | ||
| echo "GitHub returned an invalid pull request head SHA." >&2 | ||
| exit 1 | ||
| fi | ||
| if [[ ! "$head_repository" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]]; then | ||
| echo "GitHub returned an invalid pull request head repository." >&2 | ||
| exit 1 | ||
| fi | ||
| merge_ref="refs/pull/$PULL_REQUEST_NUMBER/merge" | ||
| merge_sha="$({ | ||
| git ls-remote "https://github.com/$GITHUB_REPOSITORY.git" "$merge_ref" | ||
| } | awk 'NR == 1 { print $1 }')" | ||
| if [[ ! "$merge_sha" =~ ^[0-9a-f]{40}$ ]]; then | ||
| echo "GitHub did not expose a merge revision for pull request $PULL_REQUEST_NUMBER." >&2 | ||
| echo "Resolve any merge conflicts before requesting a preview." >&2 | ||
| exit 1 | ||
| fi | ||
| if [[ "$head_repository" == "$GITHUB_REPOSITORY" ]]; then | ||
| trust="trusted-branch" | ||
| else | ||
| trust="untrusted-fork" | ||
| fi | ||
| echo "head_sha=$head_sha" >> "$GITHUB_OUTPUT" | ||
| echo "head_repository=$head_repository" >> "$GITHUB_OUTPUT" | ||
| echo "merge_ref=$merge_ref" >> "$GITHUB_OUTPUT" | ||
| echo "merge_sha=$merge_sha" >> "$GITHUB_OUTPUT" | ||
| echo "trust=$trust" >> "$GITHUB_OUTPUT" | ||
| - name: Select translated collections from pull request labels | ||
| id: translations | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number || inputs.pull_request_number }} | ||
| run: | | ||
| mapfile -t labels < <( | ||
| gh api --paginate "repos/$GITHUB_REPOSITORY/issues/$PULL_REQUEST_NUMBER/labels" --jq '.[].name' | ||
| ) | ||
| known_locales=(ar es fr ja ko pt-BR ru zh) | ||
| include_translations=false | ||
| for label in "${labels[@]}"; do | ||
| normalized="${label,,}" | ||
| if [[ "$normalized" == "docs-translations-all" ]]; then | ||
| include_translations=true | ||
| continue | ||
| fi | ||
| if [[ "$normalized" != docs-translations-* ]]; then | ||
| continue | ||
| fi | ||
| matched=false | ||
| for locale in "${known_locales[@]}"; do | ||
| if [[ "$normalized" == "docs-translations-${locale,,}" ]]; then | ||
| include_translations=true | ||
| matched=true | ||
| break | ||
| fi | ||
| done | ||
| if [[ "$matched" != true ]]; then | ||
| echo "Unknown translation preview label: $label" >&2 | ||
| echo "Use docs-translations-all or docs-translations-{ar,es,fr,ja,ko,pt-BR,ru,zh}." >&2 | ||
| exit 1 | ||
| fi | ||
| done | ||
| if [[ "$include_translations" == true ]]; then | ||
| locale_scope="all" | ||
| locale_summary="English and all translations" | ||
| else | ||
| locale_scope="none" | ||
| locale_summary="English only" | ||
| fi | ||
| echo "scope=$locale_scope" >> "$GITHUB_OUTPUT" | ||
| echo "summary=$locale_summary" >> "$GITHUB_OUTPUT" | ||
| # GitHub owns refs/pull/<number>/merge in the primary repository, even | ||
| # when the pull request head is in a fork. Vercel fetches that immutable | ||
| # revision through the project's Git connection; Actions never checks | ||
| # out, executes, archives, or uploads pull-request-controlled files. | ||
| - name: Trigger and wait for the Vercel preview | ||
| id: deploy | ||
| env: | ||
| APPROVED_SHA: ${{ steps.pull-request.outputs.merge_sha }} | ||
| GIT_REF: ${{ steps.pull-request.outputs.merge_ref }} | ||
| HEAD_REPOSITORY: ${{ steps.pull-request.outputs.head_repository }} | ||
| HEAD_SHA: ${{ steps.pull-request.outputs.head_sha }} | ||
| PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number || inputs.pull_request_number }} | ||
| TRANSLATION_SCOPE: ${{ steps.translations.outputs.scope }} | ||
| TRANSLATION_SUMMARY: ${{ steps.translations.outputs.summary }} | ||
| GH_TOKEN: ${{ github.token }} | ||
| GH_REPO: ${{ github.repository }} | ||
| TRUST: ${{ steps.pull-request.outputs.trust }} | ||
| VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} | ||
| VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} | ||
| VERCEL_TRANSLATIONS_PROJECT_ID: ${{ secrets.VERCEL_TRANSLATIONS_PROJECT_ID }} | ||
| VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} | ||
| run: | | ||
| repository_owner="${GITHUB_REPOSITORY%%/*}" | ||
| repository_name="${GITHUB_REPOSITORY#*/}" | ||
| update_preview_comment() { | ||
| local deployment_status="$1" | ||
| local updated_at="$(date -u '+%Y-%m-%d %H:%M UTC')" | ||
| local marker="<!-- nimbus-site-preview -->" | ||
| local docs_url="${preview_url%/}/docs" | ||
| local components="English: $english_state; translations: Production fallback" | ||
| if [[ -n "${translations_url:-}" ]]; then | ||
| components="English: $english_state; [translations]($translations_url): $translations_state" | ||
| fi | ||
| local body | ||
| local comment_id | ||
| body="$(printf '🕵 %s\n\n| Preview | Deployment | Components | Updated (UTC) |\n| --- | --- | --- | --- |\n| [Docs preview](%s) | [%s](%s) | %s | %s |' \ | ||
| "$marker" \ | ||
| "$docs_url" \ | ||
| "$deployment_status" \ | ||
| "$preview_url" \ | ||
| "$components" \ | ||
| "$updated_at")" | ||
| if ! comment_id="$({ | ||
| gh api --paginate "repos/$GH_REPO/issues/$PULL_REQUEST_NUMBER/comments" \ | ||
| --jq ".[] | select(.user.login == \"github-actions[bot]\") | select(.body | contains(\"$marker\")) | .id" | ||
| } | tail -n 1)"; then | ||
| return 1 | ||
| fi | ||
| if [[ -n "$comment_id" ]]; then | ||
| gh api --method PATCH "repos/$GH_REPO/issues/comments/$comment_id" -f body="$body" >/dev/null | ||
| else | ||
| gh pr comment "$PULL_REQUEST_NUMBER" --body "$body" | ||
| fi | ||
| } | ||
| update_preview_comment_with_retry() { | ||
| local deployment_status="$1" | ||
| local attempt | ||
| for attempt in 1 2 3; do | ||
| if update_preview_comment "$deployment_status"; then | ||
| return 0 | ||
| fi | ||
| echo "::warning::Unable to update the preview comment (attempt $attempt of 3)." | ||
| if (( attempt < 3 )); then | ||
| sleep 2 | ||
| fi | ||
| done | ||
| return 1 | ||
| } | ||
| english_id="" | ||
| translations_id="" | ||
| preview_url="" | ||
| translations_url="" | ||
| english_state="Not started" | ||
| translations_state="Production fallback" | ||
| preview_finished=false | ||
| cancel_vercel_deployment() { | ||
| local deployment_id="$1" | ||
| local deployment_label="$2" | ||
| local cancel_response | ||
| local cancel_exit_code | ||
| local deployment_response | ||
| local ready_state | ||
| if cancel_response="$( | ||
| curl --silent --show-error --fail-with-body \ | ||
| --request PATCH \ | ||
| --header "Authorization: Bearer $VERCEL_TOKEN" \ | ||
| "https://api.vercel.com/v12/deployments/$deployment_id/cancel?teamId=$VERCEL_ORG_ID" | ||
| )"; then | ||
| echo "Canceled $deployment_label Vercel deployment $deployment_id." | ||
| return 0 | ||
| else | ||
| cancel_exit_code=$? | ||
| fi | ||
| # A deployment can finish between being listed and receiving the | ||
| # cancellation request. Vercel returns HTTP 400 in that case, so | ||
| # verify that it is terminal rather than hiding an API failure. | ||
| if deployment_response="$( | ||
| curl --silent --show-error --fail-with-body \ | ||
| --header "Authorization: Bearer $VERCEL_TOKEN" \ | ||
| "https://api.vercel.com/v13/deployments/$deployment_id?teamId=$VERCEL_ORG_ID" | ||
| )"; then | ||
| ready_state="$(jq -er .readyState <<< "$deployment_response")" | ||
| case "$ready_state" in | ||
| READY|ERROR|CANCELED|DELETED|BLOCKED) | ||
| echo "$deployment_label Vercel deployment $deployment_id already reached $ready_state." | ||
| return 0 | ||
| ;; | ||
| esac | ||
| fi | ||
| printf '%s\n' "$cancel_response" >&2 | ||
| echo "Unable to cancel $deployment_label Vercel deployment $deployment_id." >&2 | ||
| return "$cancel_exit_code" | ||
| } | ||
| cancel_superseded_deployments() { | ||
| local project_id="$1" | ||
| local build_scope="$2" | ||
| local deployment_label="$3" | ||
| local ready_state | ||
| local until="" | ||
| local list_response | ||
| local next | ||
| local deployment_id | ||
| local -a request_args | ||
| local -a deployment_ids | ||
| if [[ -z "$project_id" ]]; then | ||
| return 0 | ||
| fi | ||
| for ready_state in QUEUED INITIALIZING BUILDING; do | ||
| until="" | ||
| while true; do | ||
| request_args=( | ||
| --silent | ||
| --show-error | ||
| --fail-with-body | ||
| --get | ||
| --header "Authorization: Bearer $VERCEL_TOKEN" | ||
| --data-urlencode "projectId=$project_id" | ||
| --data-urlencode "state=$ready_state" | ||
| --data-urlencode "limit=100" | ||
| --data-urlencode "teamId=$VERCEL_ORG_ID" | ||
| ) | ||
| if [[ -n "$until" ]]; then | ||
| request_args+=(--data-urlencode "until=$until") | ||
| fi | ||
| if ! list_response="$( | ||
| curl "${request_args[@]}" "https://api.vercel.com/v7/deployments" | ||
| )"; then | ||
| printf '%s\n' "$list_response" >&2 | ||
| echo "Unable to list $deployment_label Vercel deployments." >&2 | ||
| return 1 | ||
| fi | ||
| mapfile -t deployment_ids < <( | ||
| jq -r \ | ||
| --arg build_scope "$build_scope" \ | ||
| --arg pull_request "$PULL_REQUEST_NUMBER" \ | ||
| '.deployments[] | ||
| | select((.meta.pullRequest? // "") == $pull_request) | ||
| | select((.meta.buildScope? // "") == $build_scope) | ||
| | (.uid // .id)' \ | ||
| <<< "$list_response" | ||
| ) | ||
| for deployment_id in "${deployment_ids[@]}"; do | ||
| cancel_vercel_deployment "$deployment_id" "superseded $deployment_label" || return 1 | ||
| done | ||
| next="$(jq -er '.pagination.next // ""' <<< "$list_response")" | ||
| if [[ -z "$next" ]]; then | ||
| break | ||
| fi | ||
| if [[ "$next" == "$until" ]]; then | ||
| echo "Vercel returned a repeated pagination cursor while listing $deployment_label deployments." >&2 | ||
| return 1 | ||
| fi | ||
| until="$next" | ||
| done | ||
| done | ||
| } | ||
| finalize_preview_comment() { | ||
| local exit_code=$? | ||
| local cleanup_failed=false | ||
| trap - EXIT INT TERM | ||
| if [[ "$preview_finished" != true ]]; then | ||
| if [[ -n "$english_id" ]] && ! cancel_vercel_deployment "$english_id" "English"; then | ||
| cleanup_failed=true | ||
| fi | ||
| if [[ -n "$translations_id" ]] && ! cancel_vercel_deployment "$translations_id" "translations"; then | ||
| cleanup_failed=true | ||
| fi | ||
| if [[ -n "$preview_url" ]] && ! update_preview_comment_with_retry "🔴 Failed"; then | ||
| echo "::warning::Unable to mark the preview comment as failed." | ||
| fi | ||
| fi | ||
| if [[ "$cleanup_failed" == true && "$exit_code" -eq 0 ]]; then | ||
| exit_code=1 | ||
| fi | ||
| exit "$exit_code" | ||
| } | ||
| # GitHub's concurrency group stops the superseded Actions run, but | ||
| # Vercel continues any deployment already created by that runner. | ||
| # Cancel matching in-progress deployments before creating this run's | ||
| # replacements. This also covers abrupt runner termination where the | ||
| # old run's EXIT trap did not execute. | ||
| cancel_superseded_deployments "$VERCEL_PROJECT_ID" "english-preview" "English" | ||
| if [[ "$TRANSLATION_SCOPE" == "all" ]]; then | ||
| cancel_superseded_deployments "$VERCEL_TRANSLATIONS_PROJECT_ID" "translations-preview" "translations" | ||
| fi | ||
| trap finalize_preview_comment EXIT | ||
| trap 'exit 130' INT | ||
| trap 'exit 143' TERM | ||
| if ! project_response="$( | ||
| curl --silent --show-error --fail-with-body \ | ||
| --header "Authorization: Bearer $VERCEL_TOKEN" \ | ||
| "https://api.vercel.com/v9/projects/$VERCEL_PROJECT_ID?teamId=$VERCEL_ORG_ID" | ||
| )"; then | ||
| printf '%s\n' "$project_response" >&2 | ||
| exit 1 | ||
| fi | ||
| project_name="$(jq -er .name <<< "$project_response")" | ||
| english_payload="$( | ||
| jq --null-input \ | ||
| --arg approved_sha "$APPROVED_SHA" \ | ||
| --arg git_ref "$GIT_REF" \ | ||
| --arg head_repository "$HEAD_REPOSITORY" \ | ||
| --arg head_sha "$HEAD_SHA" \ | ||
| --arg project_id "$VERCEL_PROJECT_ID" \ | ||
| --arg project_name "$project_name" \ | ||
| --arg pull_request "$PULL_REQUEST_NUMBER" \ | ||
| --arg repository "$repository_name" \ | ||
| --arg repository_owner "$repository_owner" \ | ||
| --arg trust "$TRUST" \ | ||
| '{ | ||
| name: $project_name, | ||
| project: $project_id, | ||
| gitSource: { | ||
| type: "github", | ||
| org: $repository_owner, | ||
| repo: $repository, | ||
| ref: $git_ref, | ||
| sha: $approved_sha | ||
| }, | ||
| build: {env: { | ||
| DOCS_DEPLOY_TARGET: "english", | ||
| DOCS_LOCALES: "none", | ||
| DOCS_AVAILABLE_LOCALES: "all", | ||
| DOCS_REMOTES: "none" | ||
| }}, | ||
| meta: { | ||
| buildScope: "english-preview", | ||
| sourceRepository: ($repository_owner + "/" + $repository), | ||
| pullRequestHeadRepository: $head_repository, | ||
| pullRequestHeadSha: $head_sha, | ||
| pullRequest: $pull_request, | ||
| approvedSha: $approved_sha, | ||
| trust: $trust | ||
| } | ||
| }' | ||
| )" | ||
| if ! english_response="$( | ||
| curl --silent --show-error --fail-with-body \ | ||
| --request POST \ | ||
| --header "Authorization: Bearer $VERCEL_TOKEN" \ | ||
| --header "Content-Type: application/json" \ | ||
| --data "$english_payload" \ | ||
| "https://api.vercel.com/v13/deployments?forceNew=1&teamId=$VERCEL_ORG_ID" | ||
| )"; then | ||
| printf '%s\n' "$english_response" >&2 | ||
| exit 1 | ||
| fi | ||
| english_id="$(jq -er .id <<< "$english_response")" | ||
| deployment_url="$(jq -er .url <<< "$english_response")" | ||
| preview_url="https://${deployment_url#https://}" | ||
| english_state="🟡 Building" | ||
| translations_state="Production fallback" | ||
| echo "preview_url=$preview_url" >> "$GITHUB_OUTPUT" | ||
| echo "English Vercel deployment: $preview_url" | ||
| if [[ ! "$preview_url" =~ ^https://[^[:space:]]+$ ]]; then | ||
| echo "Vercel returned an invalid preview URL." >&2 | ||
| exit 1 | ||
| fi | ||
| if [[ "$TRANSLATION_SCOPE" == "all" ]]; then | ||
| if [[ -z "$VERCEL_TRANSLATIONS_PROJECT_ID" ]]; then | ||
| echo "docs-translations-all requires the VERCEL_TRANSLATIONS_PROJECT_ID repository secret." >&2 | ||
| exit 1 | ||
| fi | ||
| if ! translations_project_response="$( | ||
| curl --silent --show-error --fail-with-body \ | ||
| --header "Authorization: Bearer $VERCEL_TOKEN" \ | ||
| "https://api.vercel.com/v9/projects/$VERCEL_TRANSLATIONS_PROJECT_ID?teamId=$VERCEL_ORG_ID" | ||
| )"; then | ||
| printf '%s\n' "$translations_project_response" >&2 | ||
| exit 1 | ||
| fi | ||
| translations_project_name="$(jq -er .name <<< "$translations_project_response")" | ||
| translations_payload="$( | ||
| jq --null-input \ | ||
| --arg approved_sha "$APPROVED_SHA" \ | ||
| --arg git_ref "$GIT_REF" \ | ||
| --arg head_repository "$HEAD_REPOSITORY" \ | ||
| --arg head_sha "$HEAD_SHA" \ | ||
| --arg project_id "$VERCEL_TRANSLATIONS_PROJECT_ID" \ | ||
| --arg project_name "$translations_project_name" \ | ||
| --arg pull_request "$PULL_REQUEST_NUMBER" \ | ||
| --arg repository "$repository_name" \ | ||
| --arg repository_owner "$repository_owner" \ | ||
| --arg trust "$TRUST" \ | ||
| '{ | ||
| name: $project_name, | ||
| project: $project_id, | ||
| gitSource: { | ||
| type: "github", | ||
| org: $repository_owner, | ||
| repo: $repository, | ||
| ref: $git_ref, | ||
| sha: $approved_sha | ||
| }, | ||
| build: {env: { | ||
| DOCS_DEPLOY_TARGET: "translations", | ||
| DOCS_LOCALES: "all", | ||
| DOCS_AVAILABLE_LOCALES: "all", | ||
| DOCS_REMOTES: "none" | ||
| }}, | ||
| meta: { | ||
| buildScope: "translations-preview", | ||
| sourceRepository: ($repository_owner + "/" + $repository), | ||
| pullRequestHeadRepository: $head_repository, | ||
| pullRequestHeadSha: $head_sha, | ||
| pullRequest: $pull_request, | ||
| approvedSha: $approved_sha, | ||
| trust: $trust, | ||
| translations: "all" | ||
| } | ||
| }' | ||
| )" | ||
| if ! translations_response="$( | ||
| curl --silent --show-error --fail-with-body \ | ||
| --request POST \ | ||
| --header "Authorization: Bearer $VERCEL_TOKEN" \ | ||
| --header "Content-Type: application/json" \ | ||
| --data "$translations_payload" \ | ||
| "https://api.vercel.com/v13/deployments?forceNew=1&teamId=$VERCEL_ORG_ID" | ||
| )"; then | ||
| printf '%s\n' "$translations_response" >&2 | ||
| exit 1 | ||
| fi | ||
| translations_id="$(jq -er .id <<< "$translations_response")" | ||
| translations_deployment_url="$(jq -er .url <<< "$translations_response")" | ||
| translations_url="https://${translations_deployment_url#https://}" | ||
| translations_state="🟡 Building" | ||
| echo "translations_url=$translations_url" >> "$GITHUB_OUTPUT" | ||
| echo "Translations Vercel deployment: $translations_url" | ||
| fi | ||
| if ! update_preview_comment_with_retry "🟡 Building"; then | ||
| echo "::warning::Unable to mark the preview comment as building." | ||
| fi | ||
| # Leave five minutes for the EXIT trap to update the comment before | ||
| # the 60-minute job timeout terminates the runner. | ||
| for _ in {1..330}; do | ||
| if [[ "$english_state" != "🟢 Ready" ]]; then | ||
| english_response="$(curl --silent --show-error --fail-with-body \ | ||
| --header "Authorization: Bearer $VERCEL_TOKEN" \ | ||
| "https://api.vercel.com/v13/deployments/$english_id?teamId=$VERCEL_ORG_ID")" | ||
| ready_state="$(jq -er .readyState <<< "$english_response")" | ||
| case "$ready_state" in | ||
| READY) english_state="🟢 Ready" ;; | ||
| ERROR|CANCELED|DELETED|BLOCKED) | ||
| jq -r '(.errorCode // "deployment_failed") + ": " + (.errorMessage // "English deployment did not complete")' <<< "$english_response" >&2 | ||
| exit 1 | ||
| ;; | ||
| QUEUED|INITIALIZING|BUILDING) ;; | ||
| *) echo "Unexpected English deployment state: $ready_state" >&2; exit 1 ;; | ||
| esac | ||
| fi | ||
| if [[ -n "$translations_id" && "$translations_state" != "🟢 Ready" ]]; then | ||
| translations_response="$(curl --silent --show-error --fail-with-body \ | ||
| --header "Authorization: Bearer $VERCEL_TOKEN" \ | ||
| "https://api.vercel.com/v13/deployments/$translations_id?teamId=$VERCEL_ORG_ID")" | ||
| ready_state="$(jq -er .readyState <<< "$translations_response")" | ||
| case "$ready_state" in | ||
| READY) translations_state="🟢 Ready" ;; | ||
| ERROR|CANCELED|DELETED|BLOCKED) | ||
| jq -r '(.errorCode // "deployment_failed") + ": " + (.errorMessage // "Translations deployment did not complete")' <<< "$translations_response" >&2 | ||
| exit 1 | ||
| ;; | ||
| QUEUED|INITIALIZING|BUILDING) ;; | ||
| *) echo "Unexpected translations deployment state: $ready_state" >&2; exit 1 ;; | ||
| esac | ||
| fi | ||
| if [[ "$english_state" == "🟢 Ready" && ( -z "$translations_id" || "$translations_state" == "🟢 Ready" ) ]]; then | ||
| break | ||
| fi | ||
| sleep 10 | ||
| done | ||
| if [[ "$english_state" != "🟢 Ready" || ( -n "$translations_id" && "$translations_state" != "🟢 Ready" ) ]]; then | ||
| echo "Timed out waiting for the Vercel preview deployments." >&2 | ||
| exit 1 | ||
| fi | ||
| if ! update_preview_comment_with_retry "🟢 Ready"; then | ||
| echo "::warning::Unable to mark the preview comment as ready." | ||
| fi | ||
| preview_finished=true | ||