Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 98 additions & 16 deletions .github/workflows/auto-docs.yml
Original file line number Diff line number Diff line change
@@ -1,35 +1,117 @@
name: Trigger webhook for feat PRs
name: Auto-docs dry-run intake

# Trigger on merged feature PRs, or manually with a specific PR number.
# IMPORTANT: This workflow intentionally does NOT check out PR code and does NOT
# execute any code from the PR branch. It only reads PR metadata and forwards it
# to a webhook. This is required for safe use of pull_request_target on a public repo.
on:
pull_request:
pull_request_target:
types: [closed]
workflow_dispatch:
inputs:
pr_number:
description: "PR number to test"
required: true

# Minimal permissions: read repo contents (for gh api) and PR metadata only.
permissions:
contents: read
pull-requests: read

# One run per PR at a time. Does not cancel in progress — let the current run
# finish so we don't drop events when a PR is quickly closed/reopened/closed.
concurrency:
group: auto-docs-${{ github.event.pull_request.number || inputs.pr_number }}
cancel-in-progress: false

jobs:
call-webhook:
call-valtown:
if: >
github.event.pull_request.merged == true &&
(startsWith(github.event.pull_request.title, 'feat:') ||
startsWith(github.event.pull_request.title, 'feat('))
github.event_name == 'workflow_dispatch' ||
(
github.event.pull_request.merged == true &&
(
startsWith(github.event.pull_request.title, 'feat:') ||
startsWith(github.event.pull_request.title, 'feat(')
)
)

runs-on: ubuntu-latest
timeout-minutes: 5

steps:
- name: Send webhook safely
# On manual dispatch, fetch real PR metadata via the GitHub API so that
# manual runs are full end-to-end tests rather than "ping only" runs.
# The fetched fields are written to GITHUB_ENV and picked up by the next
# step via the `env.TITLE` etc. fallbacks in its `env:` block.
- name: Fetch PR metadata (manual dispatch)
if: github.event_name == 'workflow_dispatch'
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ inputs.pr_number }}
REPO: ${{ github.repository }}
run: |
gh api "repos/$REPO/pulls/$PR_NUMBER" > pr.json
{
echo "TITLE=$(jq -r .title pr.json)"
echo "BODY<<__EOF__"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: PR bodies can break out of the fixed GITHUB_ENV heredoc

The PR body is untrusted input, and a body containing a line equal to __EOF__ terminates this heredoc early and lets following lines be interpreted as additional GITHUB_ENV commands for later steps. Avoid writing arbitrary PR text to GITHUB_ENV with a static delimiter; build payload.json directly from pr.json, or use a generated delimiter that cannot appear in the value.

jq -r '.body // ""' pr.json
echo "__EOF__"
echo "AUTHOR=$(jq -r .user.login pr.json)"
echo "MERGED_AT=$(jq -r '.merged_at // ""' pr.json)"
echo "PR_URL=$(jq -r .html_url pr.json)"
} >> "$GITHUB_ENV"

- name: Build webhook payload
env:
EVENT_NAME: ${{ github.event_name }}
REPO: ${{ github.repository }}
# For pull_request_target the left-hand side is populated by the event.
# For workflow_dispatch it evaluates to empty, so we fall back to the
# values written to GITHUB_ENV by the "Fetch PR metadata" step above.
PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number }}
TITLE: ${{ github.event.pull_request.title || env.TITLE }}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Manual-dispatch metadata can be overwritten with empty values

Values written via $GITHUB_ENV are available to the next step's shell environment, but this step immediately redefines TITLE, BODY, AUTHOR, MERGED_AT, and PR_URL through its own env: block. On workflow_dispatch, ${{ env.TITLE }} is not the runtime value appended in the previous step, so the manual test path can send empty metadata instead of the fetched PR fields.

BODY: ${{ github.event.pull_request.body || env.BODY }}
AUTHOR: ${{ github.event.pull_request.user.login || env.AUTHOR }}
MERGED_AT: ${{ github.event.pull_request.merged_at || env.MERGED_AT }}
PR_URL: ${{ github.event.pull_request.html_url || env.PR_URL }}
run: |
payload=$(jq -n \
--arg repo "${GITHUB_REPOSITORY}" \
--arg pr_number "${{ github.event.pull_request.number }}" \
--arg title "${{ github.event.pull_request.title }}" \
--arg body "${{ github.event.pull_request.body }}" \
--arg author "${{ github.event.pull_request.user.login }}" \
--arg merged_at "${{ github.event.pull_request.merged_at }}" \
--arg event_name "$EVENT_NAME" \
--arg repo "$REPO" \
--arg pr_number "$PR_NUMBER" \
--arg title "$TITLE" \
--arg body "$BODY" \
--arg author "$AUTHOR" \
--arg merged_at "$MERGED_AT" \
--arg pr_url "$PR_URL" \
'{
event_name: $event_name,
repo: $repo,
pr_number: $pr_number,
title: $title,
description: $body,
author: $author,
merged_at: $merged_at
merged_at: $merged_at,
pr_url: $pr_url
}'
)
curl --fail-with-body -X POST "${{ secrets.DOC_WEBHOOK_URL }}" \

echo "$payload" > payload.json
echo "Payload:"
cat payload.json

- name: Send webhook to Val Town
env:
DOC_WEBHOOK_URL: ${{ secrets.DOC_WEBHOOK_URL }}
DOC_WEBHOOK_SECRET: ${{ secrets.DOC_WEBHOOK_SECRET }}
run: |
if [ -z "$DOC_WEBHOOK_URL" ]; then
echo "DOC_WEBHOOK_URL secret is not set"
exit 1
fi

curl --fail-with-body -sS -X POST "$DOC_WEBHOOK_URL" \
-H "Content-Type: application/json" \
--data-raw "$payload"
-H "X-Docs-Webhook-Secret: $DOC_WEBHOOK_SECRET" \
--data-binary @payload.json
Loading