ci: relay contributor card events to worker (#3113)

This commit is contained in:
Marc Chan 2026-05-27 22:48:01 +08:00 committed by GitHub
parent 74fa8a754a
commit bc44a8add3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -6,10 +6,8 @@ name: Contributor Card Bot
# #
# Intentionally NOT included: pull_request_review, pull_request_review_comment, # Intentionally NOT included: pull_request_review, pull_request_review_comment,
# issue_comment. GitHub withholds repository secrets from these events when # issue_comment. GitHub withholds repository secrets from these events when
# they originate on forked PRs, which is precisely the path most external # they originate on forked PRs, so the relay secret would fail-closed there too.
# contributor activity takes; the bot requires BOT_APP_* secrets to authenticate, # They can be re-added later via a workflow_run handoff.
# so wiring those events here would fail-closed exactly for the contributors we
# want to recognize. They can be re-added later via a workflow_run handoff.
on: on:
pull_request_target: pull_request_target:
types: [closed] types: [closed]
@ -24,21 +22,16 @@ on:
permissions: permissions:
contents: read contents: read
# Serialize all bot runs across the whole repository. The bot reads-then-writes # Serialize all relay runs across the whole repository. The Cloudflare worker
# `data/contributor-card-state.json`; running events in parallel let multiple # owns durable state now, but queuing still keeps identical GitHub events from
# runs read the same SHA and only the first PUT succeeds, the rest fail with a # racing each other and producing duplicate comments during bursty merge windows.
# 409 Conflict. They also let the same actor receive duplicate cards when a
# burst of events fires before the first state write lands. A single repo-wide
# group with `cancel-in-progress: false` queues runs and processes them in
# arrival order, so every event still gets a card and the state file is never
# stale on write.
concurrency: concurrency:
group: contributor-card-bot group: contributor-card-bot
cancel-in-progress: false cancel-in-progress: false
jobs: jobs:
recognize: recognize:
name: Render and post contributor card name: Relay contributor event to Cloudflare worker
if: | if: |
github.repository == 'nexu-io/open-design' && github.repository == 'nexu-io/open-design' &&
( (
@ -49,32 +42,46 @@ jobs:
github.event_name == 'workflow_dispatch' github.event_name == 'workflow_dispatch'
) )
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 8 timeout-minutes: 5
steps: steps:
- name: Checkout contributor bot - name: Relay event payload
uses: actions/checkout@v6.0.2
with:
repository: nexu-io/open-design-bot-sandbox
ref: main
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10
- name: Setup Node.js
uses: actions/setup-node@v6.0.0
with:
node-version: 22
cache: pnpm
- name: Install bot dependencies
run: pnpm install --frozen-lockfile
- name: Run contributor bot
env: env:
BOT_APP_ID: ${{ secrets.BOT_APP_ID }} CONTRIBUTOR_CARD_WORKER_URL: ${{ secrets.CONTRIBUTOR_CARD_WORKER_URL }}
BOT_APP_INSTALLATION_ID: ${{ secrets.BOT_APP_INSTALLATION_ID }} CONTRIBUTOR_CARD_WORKER_SECRET: ${{ secrets.CONTRIBUTOR_CARD_WORKER_SECRET }}
BOT_APP_PRIVATE_KEY: ${{ secrets.BOT_APP_PRIVATE_KEY }} GITHUB_EVENT_NAME: ${{ github.event_name }}
run: pnpm exec tsx scripts/action-handler.ts GITHUB_REPOSITORY: ${{ github.repository }}
GITHUB_EVENT_ACTION: ${{ github.event.action }}
GITHUB_RUN_ID: ${{ github.run_id }}
GITHUB_RUN_ATTEMPT: ${{ github.run_attempt }}
GITHUB_EVENT_PATH: ${{ github.event_path }}
run: |
python - <<'PY'
import json, os
from pathlib import Path
payload = json.loads(Path(os.environ["GITHUB_EVENT_PATH"]).read_text())
envelope = {
"repository": os.environ["GITHUB_REPOSITORY"],
"eventName": os.environ["GITHUB_EVENT_NAME"],
"action": os.environ.get("GITHUB_EVENT_ACTION") or payload.get("action"),
"deliveryId": f"{os.environ['GITHUB_RUN_ID']}-{os.environ['GITHUB_RUN_ATTEMPT']}",
"triggeredAt": __import__("datetime").datetime.utcnow().isoformat(timespec="milliseconds") + "Z",
"payload": payload,
}
Path("body.json").write_text(json.dumps(envelope, separators=(",", ":")))
PY
python - <<'PY'
import hashlib, hmac, os
from pathlib import Path
body = Path("body.json").read_bytes()
digest = hmac.new(os.environ["CONTRIBUTOR_CARD_WORKER_SECRET"].encode(), body, hashlib.sha256).hexdigest()
Path("signature.txt").write_text(f"sha256={digest}")
PY
curl --fail --silent --show-error \
-X POST \
-H "content-type: application/json" \
-H "x-open-design-signature: $(cat signature.txt)" \
--data-binary @body.json \
"$CONTRIBUTOR_CARD_WORKER_URL/api/github/events"