mirror of
https://github.com/CopilotKit/CopilotKit.git
synced 2026-09-14 16:26:20 +08:00
465 lines
22 KiB
YAML
465 lines
22 KiB
YAML
name: "Showcase: Docs Sync"
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
paths:
|
|
- "docs/content/docs/**"
|
|
- "docs/snippets/**"
|
|
workflow_dispatch:
|
|
|
|
concurrency:
|
|
group: showcase-docs-sync
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
sync-docs:
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 15
|
|
permissions:
|
|
contents: write
|
|
pull-requests: write
|
|
env:
|
|
SHOWCASE_BRANCH: main
|
|
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_OSS_ALERTS }}
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ env.SHOWCASE_BRANCH }}
|
|
fetch-depth: 0
|
|
|
|
- uses: pnpm/action-setup@v4
|
|
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 20
|
|
cache: pnpm
|
|
|
|
- name: Install dependencies
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
- name: Fetch main branch
|
|
run: git fetch origin main
|
|
|
|
- name: Run docs sync
|
|
id: sync
|
|
run: |
|
|
set +e
|
|
npx tsx showcase/scripts/sync-docs-from-main.ts > sync-output.txt 2>&1
|
|
EXIT_CODE=$?
|
|
set -e
|
|
|
|
cat sync-output.txt
|
|
|
|
if [ $EXIT_CODE -eq 2 ]; then
|
|
echo "No changes to sync"
|
|
echo "action=none" >> "$GITHUB_OUTPUT"
|
|
elif [ $EXIT_CODE -eq 0 ]; then
|
|
echo "Clean transforms only"
|
|
echo "action=auto_push" >> "$GITHUB_OUTPUT"
|
|
elif [ $EXIT_CODE -eq 3 ]; then
|
|
echo "Has review items + clean transforms"
|
|
echo "action=push_and_pr" >> "$GITHUB_OUTPUT"
|
|
# The sync script writes review-items.txt and emits
|
|
# review_items_file=<abs-path> directly to GITHUB_OUTPUT.
|
|
# Verify the file actually exists so downstream steps don't
|
|
# silently read an empty path.
|
|
if [ ! -f review-items.txt ]; then
|
|
echo "::warning::review-items.txt not found despite exit code 3 — sync script may have a bug"
|
|
fi
|
|
else
|
|
echo "Sync failed with exit code $EXIT_CODE"
|
|
exit 1
|
|
fi
|
|
|
|
# Generate a token from copilotkit-devops-bot (bypass actor on branch protection)
|
|
- name: Generate bot token
|
|
id: bot-token
|
|
if: steps.sync.outputs.action == 'auto_push' || steps.sync.outputs.action == 'push_and_pr'
|
|
uses: actions/create-github-app-token@v2
|
|
with:
|
|
app-id: 1108748
|
|
private-key: ${{ secrets.DEVOPS_BOT_PRIVATE_KEY }}
|
|
|
|
# Create PR. For action=auto_push (no review items) the PR is
|
|
# auto-merged via the devops bot (bypasses branch protection). For
|
|
# action=push_and_pr the sync script has already written best-effort
|
|
# 3-way merged content to disk — this step commits it and opens a PR
|
|
# tagged [NEEDS REVIEW]. Auto-merge is DISABLED for needs-review PRs
|
|
# so a human reconciles any upstream-wins overrides.
|
|
- name: Create PR for docs sync
|
|
id: push
|
|
if: steps.sync.outputs.action == 'auto_push' || steps.sync.outputs.action == 'push_and_pr'
|
|
env:
|
|
GH_TOKEN: ${{ steps.bot-token.outputs.token }}
|
|
ACTION: ${{ steps.sync.outputs.action }}
|
|
REVIEW_ITEMS_FILE: ${{ steps.sync.outputs.review_items_file }}
|
|
run: |
|
|
# Trap-based cleanup so temp files are removed even on signal kill.
|
|
CLEANUP_FILES=()
|
|
cleanup() {
|
|
for f in "${CLEANUP_FILES[@]}"; do
|
|
[ -n "$f" ] && [ -e "$f" ] && rm -f "$f" || true
|
|
done
|
|
}
|
|
trap cleanup EXIT INT TERM
|
|
|
|
git config user.name "copilotkit-devops-bot[bot]"
|
|
git config user.email "copilotkit-devops-bot[bot]@users.noreply.github.com"
|
|
|
|
SHORT_SHA=$(git rev-parse --short origin/main)
|
|
if [ "$ACTION" = "push_and_pr" ]; then
|
|
# Dedupe against open needs-review PRs. If one already exists,
|
|
# skip PR creation and let the Slack alert point at the existing
|
|
# one (simpler than re-pushing to its branch).
|
|
#
|
|
# NOTE: `gh pr list --search "head:..."` is NOT supported —
|
|
# GitHub's PR search syntax has no `head:` qualifier, so that
|
|
# query always returns empty. Use jq on the full list instead
|
|
# to filter by headRefName prefix client-side.
|
|
# Fail the step on gh API failure rather than swallowing it —
|
|
# `|| echo ""` would silently treat a transient 5xx as "no
|
|
# existing PR" and open a duplicate. Retry once with backoff
|
|
# to absorb blips; if both attempts fail, exit non-zero.
|
|
gh_pr_list_existing() {
|
|
gh pr list \
|
|
--state open \
|
|
--base "$SHOWCASE_BRANCH" \
|
|
--json number,url,headRefName \
|
|
--jq '[.[] | select(.headRefName | startswith("docs-sync/needs-review/"))] | .[0].url'
|
|
}
|
|
if ! EXISTING_PR=$(gh_pr_list_existing 2>gh-err.txt); then
|
|
echo "::warning::gh pr list failed on first attempt — retrying after 5s"
|
|
cat gh-err.txt || true
|
|
sleep 5
|
|
if ! EXISTING_PR=$(gh_pr_list_existing 2>gh-err.txt); then
|
|
echo "::error::gh pr list failed twice — aborting to avoid duplicate PR creation"
|
|
cat gh-err.txt || true
|
|
rm -f gh-err.txt
|
|
exit 1
|
|
fi
|
|
fi
|
|
rm -f gh-err.txt
|
|
if [ -n "$EXISTING_PR" ]; then
|
|
echo "Open needs-review PR already exists: $EXISTING_PR"
|
|
echo "Skipping new PR — Slack alert will point at the existing one."
|
|
echo "files_changed=0" >> "$GITHUB_OUTPUT"
|
|
echo "pr_url=${EXISTING_PR}" >> "$GITHUB_OUTPUT"
|
|
echo "pr_opened=false" >> "$GITHUB_OUTPUT"
|
|
echo "needs_review=true" >> "$GITHUB_OUTPUT"
|
|
echo "existing_pr=true" >> "$GITHUB_OUTPUT"
|
|
exit 0
|
|
fi
|
|
|
|
BRANCH="docs-sync/needs-review/${SHORT_SHA}-$(date +%s)"
|
|
COMMIT_SUBJECT="chore: docs sync from main — needs review ($(date +%Y-%m-%d))"
|
|
else
|
|
BRANCH="docs-sync/auto/${SHORT_SHA}-$(date +%s)"
|
|
COMMIT_SUBJECT="chore: auto-sync docs from main ($(date +%Y-%m-%d))"
|
|
fi
|
|
|
|
git checkout -b "$BRANCH"
|
|
|
|
# For push_and_pr: apply the conflict manifest FIRST (upstream-wins
|
|
# content for conflicted files, written ONLY to the PR branch so
|
|
# the main branch worktree stays as-is and future sync runs still
|
|
# flag those files for review until this PR is merged).
|
|
#
|
|
# CRITICAL: This MUST run BEFORE `git add` below. `git add` stages
|
|
# the current working tree, so any manifest files written after
|
|
# that point would be left un-staged and never make it into the
|
|
# commit.
|
|
if [ "$ACTION" = "push_and_pr" ] && [ -f conflict-manifest.json ]; then
|
|
CLEANUP_FILES+=("conflict-manifest.json")
|
|
node -e '
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
const manifest = JSON.parse(fs.readFileSync("conflict-manifest.json", "utf-8"));
|
|
for (const entry of manifest) {
|
|
const target = path.resolve(entry.showcasePath);
|
|
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
fs.writeFileSync(target, entry.content);
|
|
console.log("Applied upstream-wins to PR branch: " + entry.showcasePath);
|
|
}
|
|
'
|
|
fi
|
|
|
|
# Stage AFTER manifest has been applied so conflicted files land
|
|
# in the commit.
|
|
git add showcase/shell-docs/src/content/ showcase/shell-docs/.docs-sync-sha
|
|
CHANGED=$(git diff --cached --name-only | wc -l | tr -d ' ')
|
|
|
|
if [ "$CHANGED" = "0" ]; then
|
|
echo "Nothing to commit — no PR will be opened (deliberate)"
|
|
echo "files_changed=0" >> "$GITHUB_OUTPUT"
|
|
# Explicit signal: no PR opened, and this is the intended outcome
|
|
# (not an error path). Downstream alerts key on this.
|
|
echo "pr_opened=false" >> "$GITHUB_OUTPUT"
|
|
# Set needs_review explicitly so downstream `needs_review != 'true'`
|
|
# gates (notify-auto-sync) don't misfire on this deliberate
|
|
# no-op. Value depends on why we got here: push_and_pr path =
|
|
# review still pending; auto_push path = no review needed.
|
|
if [ "$ACTION" = "push_and_pr" ]; then
|
|
echo "needs_review=true" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "needs_review=false" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
exit 0
|
|
fi
|
|
|
|
git commit --no-verify -m "$COMMIT_SUBJECT"
|
|
|
|
# Override git credential to use bot token (checkout configured GITHUB_TOKEN)
|
|
git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git"
|
|
git push origin "$BRANCH"
|
|
|
|
if [ "$ACTION" = "push_and_pr" ]; then
|
|
# Build the PR body from review-items.txt plus a clear callout.
|
|
REVIEW_ITEMS_CONTENT="(no review-items file produced)"
|
|
if [ -n "${REVIEW_ITEMS_FILE:-}" ] && [ -f "$REVIEW_ITEMS_FILE" ]; then
|
|
REVIEW_ITEMS_CONTENT=$(cat "$REVIEW_ITEMS_FILE")
|
|
fi
|
|
PR_BODY_FILE=$(mktemp)
|
|
CLEANUP_FILES+=("$PR_BODY_FILE")
|
|
{
|
|
printf '%s\n' ':warning: **Docs sync — MANUAL REVIEW REQUIRED**'
|
|
printf '\n'
|
|
printf '%s\n' 'This PR was auto-opened because the docs-sync script detected'
|
|
printf '%s\n' 'showcase-local modifications overlapping with upstream changes.'
|
|
printf '\n'
|
|
printf '%s\n' 'The script attempted a best-effort 3-way merge:'
|
|
printf '\n'
|
|
printf '%s\n' '- Where `git merge-file` produced a clean merge, the merged content was written.'
|
|
printf '%s\n' '- Where `git merge-file` produced conflict markers, **upstream content was written as-is** and showcase-local modifications were overridden. **Manual review required.**'
|
|
printf '\n'
|
|
printf '%s\n' '### Review items'
|
|
printf '\n'
|
|
printf '%s\n' '```'
|
|
# printf '%s\n' avoids running command substitution / backticks
|
|
# embedded in review-items content (cat "$REVIEW_ITEMS_FILE"
|
|
# would also work; printf is equivalent here since we already
|
|
# captured the content).
|
|
printf '%s\n' "$REVIEW_ITEMS_CONTENT"
|
|
printf '%s\n' '```'
|
|
printf '\n'
|
|
printf '%s\n' '### Source'
|
|
printf '\n'
|
|
printf '%s\n' "- Upstream ref: [\`${SHORT_SHA}\`](https://github.com/${{ github.repository }}/commit/${SHORT_SHA})"
|
|
printf '%s\n' "- Workflow run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
|
printf '\n'
|
|
printf '%s\n' '**Review before merging.** Auto-merge is intentionally disabled'
|
|
printf '%s\n' 'for `needs-review` PRs — confirm the upstream-wins sections'
|
|
printf '%s\n' 'preserve any intentional showcase-local divergence you want to'
|
|
printf '%s\n' 'keep, then merge manually.'
|
|
} > "$PR_BODY_FILE"
|
|
|
|
PR_URL=$(gh pr create \
|
|
--title "docs-sync(needs-review): sync from main (${SHORT_SHA}) [NEEDS REVIEW]" \
|
|
--body-file "$PR_BODY_FILE" \
|
|
--base "$SHOWCASE_BRANCH" \
|
|
--head "$BRANCH")
|
|
|
|
echo "Created NEEDS-REVIEW PR: $PR_URL"
|
|
echo "files_changed=${CHANGED}" >> "$GITHUB_OUTPUT"
|
|
echo "pr_url=${PR_URL}" >> "$GITHUB_OUTPUT"
|
|
# Only set after URL captured — error paths leave this unset so
|
|
# downstream alerts fall through to failure().
|
|
echo "pr_opened=true" >> "$GITHUB_OUTPUT"
|
|
echo "needs_review=true" >> "$GITHUB_OUTPUT"
|
|
# Intentionally no `gh pr merge` — human must review & merge.
|
|
else
|
|
PR_URL=$(gh pr create \
|
|
--title "chore: auto-sync docs from main (${SHORT_SHA})" \
|
|
--body "Automated docs sync. Clean transforms / clean 3-way merges only." \
|
|
--base "$SHOWCASE_BRANCH" \
|
|
--head "$BRANCH")
|
|
|
|
echo "Created PR: $PR_URL"
|
|
echo "files_changed=${CHANGED}" >> "$GITHUB_OUTPUT"
|
|
echo "pr_url=${PR_URL}" >> "$GITHUB_OUTPUT"
|
|
echo "pr_opened=true" >> "$GITHUB_OUTPUT"
|
|
echo "needs_review=false" >> "$GITHUB_OUTPUT"
|
|
|
|
gh pr merge "$PR_URL" --merge
|
|
fi
|
|
|
|
# Build all Slack payloads via jq into tmpfiles. This guarantees any
|
|
# review-item filename containing ", \, or control chars is safely
|
|
# JSON-escaped (never string-interpolated into a JSON literal).
|
|
- name: Build Slack payloads
|
|
id: payloads
|
|
if: always()
|
|
env:
|
|
RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
PR_URL: ${{ steps.push.outputs.pr_url }}
|
|
FILES_CHANGED: ${{ steps.push.outputs.files_changed }}
|
|
REVIEW_ITEMS_FILE: ${{ steps.sync.outputs.review_items_file }}
|
|
SYNC_OUTCOME: ${{ steps.sync.outcome }}
|
|
BOT_TOKEN_OUTCOME: ${{ steps.bot-token.outcome }}
|
|
PUSH_OUTCOME: ${{ steps.push.outcome }}
|
|
run: |
|
|
set -euo pipefail
|
|
mkdir -p slack-payloads
|
|
|
|
# auto-sync success (PR merged)
|
|
jq -n \
|
|
--arg pr_url "${PR_URL:-}" \
|
|
--arg files "${FILES_CHANGED:-?}" \
|
|
--arg run_url "$RUN_URL" \
|
|
'{text: (":arrows_counterclockwise: *Docs sync*: auto-merged " + $files + " file(s)\n" + $pr_url + "\n<" + $run_url + "|View run>")}' \
|
|
> slack-payloads/auto-sync.json
|
|
|
|
# merge failed (PR exists but gh pr merge failed)
|
|
jq -n \
|
|
--arg pr_url "${PR_URL:-}" \
|
|
--arg run_url "$RUN_URL" \
|
|
'{text: (":warning: *Docs sync*: PR created but auto-merge FAILED — needs manual merge\n" + $pr_url + "\n<" + $run_url + "|View run>")}' \
|
|
> slack-payloads/merge-failed.json
|
|
|
|
# review-needed payloads: read items from file, let jq handle escaping
|
|
REVIEW_ITEMS=""
|
|
if [ -n "${REVIEW_ITEMS_FILE:-}" ] && [ -f "$REVIEW_ITEMS_FILE" ]; then
|
|
REVIEW_ITEMS=$(cat "$REVIEW_ITEMS_FILE")
|
|
fi
|
|
|
|
jq -n \
|
|
--arg items "$REVIEW_ITEMS" \
|
|
--arg pr_url "${PR_URL:-}" \
|
|
--arg run_url "$RUN_URL" \
|
|
'{text: (":warning: *Docs sync*: auto-opened *NEEDS REVIEW* PR (best-effort 3-way merge; upstream-wins where conflicts) — human must review + merge\n```" + $items + "```\nReview: " + $pr_url + "\n<" + $run_url + "|View run>")}' \
|
|
> slack-payloads/review-with-pr.json
|
|
|
|
# Collision path: new review items flagged but an existing open
|
|
# needs-review PR already covers the territory; we did NOT open a
|
|
# new PR. Point reviewers at the existing one.
|
|
jq -n \
|
|
--arg items "$REVIEW_ITEMS" \
|
|
--arg pr_url "${PR_URL:-}" \
|
|
--arg run_url "$RUN_URL" \
|
|
'{text: (":warning: *Docs sync*: new review items detected, but an open *NEEDS REVIEW* PR already exists — skipped new PR creation to avoid collision. Please resolve the existing PR.\n```" + $items + "```\nExisting PR: " + $pr_url + "\n<" + $run_url + "|View run>")}' \
|
|
> slack-payloads/review-existing-pr.json
|
|
|
|
# Fallback path: review items flagged but no PR opened (e.g. 3-way
|
|
# merge produced bit-for-bit identical content to what's already on
|
|
# disk so nothing to commit). Keep an alert so it's visible.
|
|
jq -n \
|
|
--arg items "$REVIEW_ITEMS" \
|
|
--arg run_url "$RUN_URL" \
|
|
'{text: (":warning: *Docs sync*: review items flagged but produced no diff (no PR opened)\n```" + $items + "```\n<" + $run_url + "|View run>")}' \
|
|
> slack-payloads/review-no-pr.json
|
|
|
|
# failure alert
|
|
FAILED_STEP="unknown"
|
|
if [ "${SYNC_OUTCOME:-}" = "failure" ]; then
|
|
FAILED_STEP="sync-docs script"
|
|
elif [ "${BOT_TOKEN_OUTCOME:-}" = "failure" ]; then
|
|
FAILED_STEP="bot token generation (check DEVOPS_BOT_PRIVATE_KEY secret)"
|
|
elif [ "${PUSH_OUTCOME:-}" = "failure" ]; then
|
|
FAILED_STEP="push/PR creation"
|
|
fi
|
|
jq -n \
|
|
--arg failed_step "$FAILED_STEP" \
|
|
--arg run_url "$RUN_URL" \
|
|
'{text: (":x: *Docs sync*: workflow failed\n*Failed step:* " + $failed_step + " | <" + $run_url + "|View run>")}' \
|
|
> slack-payloads/failure.json
|
|
|
|
- name: Notify Slack (auto-sync)
|
|
id: notify-auto-sync
|
|
if: always() && env.SLACK_WEBHOOK != '' && steps.push.outcome == 'success' && steps.push.outputs.files_changed != '0' && steps.push.outputs.needs_review != 'true'
|
|
uses: slackapi/slack-github-action@v2.1.0
|
|
with:
|
|
webhook: ${{ secrets.SLACK_WEBHOOK_OSS_ALERTS }}
|
|
webhook-type: incoming-webhook
|
|
payload-file-path: slack-payloads/auto-sync.json
|
|
|
|
- name: Notify Slack (merge failed)
|
|
id: notify-merge-failed
|
|
if: failure() && env.SLACK_WEBHOOK != '' && steps.push.outputs.pr_opened == 'true' && steps.push.outputs.needs_review != 'true'
|
|
uses: slackapi/slack-github-action@v2.1.0
|
|
with:
|
|
webhook: ${{ secrets.SLACK_WEBHOOK_OSS_ALERTS }}
|
|
webhook-type: incoming-webhook
|
|
payload-file-path: slack-payloads/merge-failed.json
|
|
|
|
# Review items = files the sync script flagged but did NOT write to disk
|
|
# (local modifications, files deleted on main). A PR was opened for the
|
|
# clean-transform portion — link it so reviewers can click through.
|
|
# Gated on pr_opened == 'true' so it only fires when we actually have a
|
|
# PR URL (not on any error path that leaves pr_url empty).
|
|
- name: Notify Slack (review needed, with PR)
|
|
id: notify-review-with-pr
|
|
if: always() && env.SLACK_WEBHOOK != '' && steps.push.outputs.needs_review == 'true' && steps.push.outputs.pr_opened == 'true'
|
|
uses: slackapi/slack-github-action@v2.1.0
|
|
with:
|
|
webhook: ${{ secrets.SLACK_WEBHOOK_OSS_ALERTS }}
|
|
webhook-type: incoming-webhook
|
|
payload-file-path: slack-payloads/review-with-pr.json
|
|
|
|
# Review items but the clean-transform portion was empty so no PR was
|
|
# opened. Gated on pr_opened == 'false' (deliberate no-PR path) — not
|
|
# empty pr_url, which would also match error paths. Explicitly excludes
|
|
# the existing-PR collision path, which gets its own step below.
|
|
- name: Notify Slack (review needed, no PR)
|
|
id: notify-review-no-pr
|
|
if: always() && env.SLACK_WEBHOOK != '' && steps.sync.outputs.action == 'push_and_pr' && steps.push.outputs.pr_opened == 'false' && steps.push.outputs.existing_pr != 'true'
|
|
uses: slackapi/slack-github-action@v2.1.0
|
|
with:
|
|
webhook: ${{ secrets.SLACK_WEBHOOK_OSS_ALERTS }}
|
|
webhook-type: incoming-webhook
|
|
payload-file-path: slack-payloads/review-no-pr.json
|
|
|
|
# Collision path: new review items flagged but an existing open
|
|
# needs-review PR already exists — point reviewers at it.
|
|
- name: Notify Slack (review needed, existing PR)
|
|
id: notify-review-existing-pr
|
|
if: always() && env.SLACK_WEBHOOK != '' && steps.push.outputs.existing_pr == 'true'
|
|
uses: slackapi/slack-github-action@v2.1.0
|
|
with:
|
|
webhook: ${{ secrets.SLACK_WEBHOOK_OSS_ALERTS }}
|
|
webhook-type: incoming-webhook
|
|
payload-file-path: slack-payloads/review-existing-pr.json
|
|
|
|
- name: Notify Slack (failure)
|
|
id: notify-failure
|
|
if: failure() && env.SLACK_WEBHOOK != '' && steps.push.outputs.pr_opened != 'true'
|
|
uses: slackapi/slack-github-action@v2.1.0
|
|
with:
|
|
webhook: ${{ secrets.SLACK_WEBHOOK_OSS_ALERTS }}
|
|
webhook-type: incoming-webhook
|
|
payload-file-path: slack-payloads/failure.json
|
|
|
|
# Unconditional fallback: if any notify-* step above failed (webhook 5xx,
|
|
# rate limit, malformed payload), fire a plain-text alert so we never
|
|
# silently lose a review-needed or failure notification. Uses curl
|
|
# directly so it doesn't share failure modes with the slackapi action.
|
|
# Payload is inlined (not read from slack-payloads/) so this fallback
|
|
# has no dependency on the Build Slack payloads step succeeding — if
|
|
# that step broke (jq missing, mkdir failed, etc), every notify-* step
|
|
# would fail AND the fallback could not read its file. RUN_URL is
|
|
# constructed from GitHub-controlled env vars only (no user input), so
|
|
# direct string interpolation into the JSON literal is safe — the
|
|
# values cannot contain " or \.
|
|
- name: Notify Slack (alert machinery failed)
|
|
if: >-
|
|
always() && env.SLACK_WEBHOOK != '' && (
|
|
steps.notify-auto-sync.outcome == 'failure' ||
|
|
steps.notify-merge-failed.outcome == 'failure' ||
|
|
steps.notify-review-with-pr.outcome == 'failure' ||
|
|
steps.notify-review-no-pr.outcome == 'failure' ||
|
|
steps.notify-review-existing-pr.outcome == 'failure' ||
|
|
steps.notify-failure.outcome == 'failure'
|
|
)
|
|
env:
|
|
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_OSS_ALERTS }}
|
|
run: |
|
|
set -eu
|
|
if [ -z "${SLACK_WEBHOOK:-}" ]; then
|
|
echo "::warning::SLACK_WEBHOOK_OSS_ALERTS not set — cannot post fallback alert"
|
|
exit 0
|
|
fi
|
|
RUN_URL="https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
|
|
curl -sS -X POST \
|
|
-H "Content-Type: application/json" \
|
|
--data "{\"text\": \":rotating_light: *Docs sync*: review/alert machinery failed — check Actions UI ${RUN_URL}\"}" \
|
|
"$SLACK_WEBHOOK" || echo "::warning::fallback Slack post also failed"
|